Fix blocking DBus device reservation, so it plays nice with others
[jack2.git] / dbus / reserve.c
blobcae77e64e8aae20cbf2004cd3bee231382a05bad
1 /***
2 Copyright 2009 Lennart Poettering
4 Permission is hereby granted, free of charge, to any person
5 obtaining a copy of this software and associated documentation files
6 (the "Software"), to deal in the Software without restriction,
7 including without limitation the rights to use, copy, modify, merge,
8 publish, distribute, sublicense, and/or sell copies of the Software,
9 and to permit persons to whom the Software is furnished to do so,
10 subject to the following conditions:
12 The above copyright notice and this permission notice shall be
13 included in all copies or substantial portions of the Software.
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23 ***/
25 #include <string.h>
26 #include <unistd.h>
27 #include <errno.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <assert.h>
31 #include <stdint.h>
33 #include "reserve.h"
34 #include "jack/control.h"
36 #define RESERVE_ERROR_NO_MEMORY "org.freedesktop.ReserveDevice1.Error.NoMemory"
37 #define RESERVE_ERROR_PROTOCOL_VIOLATION "org.freedesktop.ReserveDevice1.Error.Protocol"
38 #define RESERVE_ERROR_RELEASE_DENIED "org.freedesktop.ReserveDevice1.Error.ReleaseDenied"
40 struct rd_device {
41 int ref;
43 char *device_name;
44 char *application_name;
45 char *application_device_name;
46 char *service_name;
47 char *object_path;
48 int32_t priority;
50 DBusConnection *connection;
52 int owning:1;
53 int registered:1;
54 int filtering:1;
55 int gave_up:1;
57 rd_request_cb_t request_cb;
58 void *userdata;
62 #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."
63 #define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/"
65 static const char introspection[] =
66 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
67 "<node>"
68 " <!-- If you are looking for documentation make sure to check out\n"
69 " http://git.0pointer.de/?p=reserve.git;a=blob;f=reserve.txt -->\n"
70 " <interface name=\"org.freedesktop.ReserveDevice1\">"
71 " <method name=\"RequestRelease\">"
72 " <arg name=\"priority\" type=\"i\" direction=\"in\"/>"
73 " <arg name=\"result\" type=\"b\" direction=\"out\"/>"
74 " </method>"
75 " <property name=\"Priority\" type=\"i\" access=\"read\"/>"
76 " <property name=\"ApplicationName\" type=\"s\" access=\"read\"/>"
77 " <property name=\"ApplicationDeviceName\" type=\"s\" access=\"read\"/>"
78 " </interface>"
79 " <interface name=\"org.freedesktop.DBus.Properties\">"
80 " <method name=\"Get\">"
81 " <arg name=\"interface\" direction=\"in\" type=\"s\"/>"
82 " <arg name=\"property\" direction=\"in\" type=\"s\"/>"
83 " <arg name=\"value\" direction=\"out\" type=\"v\"/>"
84 " </method>"
85 " </interface>"
86 " <interface name=\"org.freedesktop.DBus.Introspectable\">"
87 " <method name=\"Introspect\">"
88 " <arg name=\"data\" type=\"s\" direction=\"out\"/>"
89 " </method>"
90 " </interface>"
91 "</node>";
93 static dbus_bool_t add_variant(
94 DBusMessage *m,
95 int type,
96 const void *data) {
98 DBusMessageIter iter, sub;
99 char t[2];
101 t[0] = (char) type;
102 t[1] = 0;
104 dbus_message_iter_init_append(m, &iter);
106 if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, t, &sub))
107 return FALSE;
109 if (!dbus_message_iter_append_basic(&sub, type, data))
110 return FALSE;
112 if (!dbus_message_iter_close_container(&iter, &sub))
113 return FALSE;
115 return TRUE;
118 static DBusHandlerResult object_handler(
119 DBusConnection *c,
120 DBusMessage *m,
121 void *userdata) {
123 rd_device *d;
124 DBusError error;
125 DBusMessage *reply = NULL;
127 dbus_error_init(&error);
129 d = (rd_device*)userdata;
130 assert(d->ref >= 1);
132 if (dbus_message_is_method_call(
134 "org.freedesktop.ReserveDevice1",
135 "RequestRelease")) {
137 int32_t priority;
138 dbus_bool_t ret;
140 if (!dbus_message_get_args(
142 &error,
143 DBUS_TYPE_INT32, &priority,
144 DBUS_TYPE_INVALID))
145 goto invalid;
147 ret = FALSE;
149 if (priority > d->priority && d->request_cb) {
150 d->ref++;
152 if (d->request_cb(d, 0) > 0) {
153 ret = TRUE;
154 d->gave_up = 1;
157 rd_release(d);
160 if (!(reply = dbus_message_new_method_return(m)))
161 goto oom;
163 if (!dbus_message_append_args(
164 reply,
165 DBUS_TYPE_BOOLEAN, &ret,
166 DBUS_TYPE_INVALID))
167 goto oom;
169 if (!dbus_connection_send(c, reply, NULL))
170 goto oom;
172 dbus_message_unref(reply);
174 return DBUS_HANDLER_RESULT_HANDLED;
176 } else if (dbus_message_is_method_call(
178 "org.freedesktop.DBus.Properties",
179 "Get")) {
181 const char *interface, *property;
183 if (!dbus_message_get_args(
185 &error,
186 DBUS_TYPE_STRING, &interface,
187 DBUS_TYPE_STRING, &property,
188 DBUS_TYPE_INVALID))
189 goto invalid;
191 if (strcmp(interface, "org.freedesktop.ReserveDevice1") == 0) {
192 const char *empty = "";
194 if (strcmp(property, "ApplicationName") == 0 && d->application_name) {
195 if (!(reply = dbus_message_new_method_return(m)))
196 goto oom;
198 if (!add_variant(
199 reply,
200 DBUS_TYPE_STRING,
201 d->application_name ? (const char**) &d->application_name : &empty))
202 goto oom;
204 } else if (strcmp(property, "ApplicationDeviceName") == 0) {
205 if (!(reply = dbus_message_new_method_return(m)))
206 goto oom;
208 if (!add_variant(
209 reply,
210 DBUS_TYPE_STRING,
211 d->application_device_name ? (const char**) &d->application_device_name : &empty))
212 goto oom;
214 } else if (strcmp(property, "Priority") == 0) {
215 if (!(reply = dbus_message_new_method_return(m)))
216 goto oom;
218 if (!add_variant(
219 reply,
220 DBUS_TYPE_INT32,
221 &d->priority))
222 goto oom;
223 } else {
224 if (!(reply = dbus_message_new_error_printf(
226 DBUS_ERROR_UNKNOWN_METHOD,
227 "Unknown property %s",
228 property)))
229 goto oom;
232 if (!dbus_connection_send(c, reply, NULL))
233 goto oom;
235 dbus_message_unref(reply);
237 return DBUS_HANDLER_RESULT_HANDLED;
240 } else if (dbus_message_is_method_call(
242 "org.freedesktop.DBus.Introspectable",
243 "Introspect")) {
244 const char *i = introspection;
246 if (!(reply = dbus_message_new_method_return(m)))
247 goto oom;
249 if (!dbus_message_append_args(
250 reply,
251 DBUS_TYPE_STRING,
253 DBUS_TYPE_INVALID))
254 goto oom;
256 if (!dbus_connection_send(c, reply, NULL))
257 goto oom;
259 dbus_message_unref(reply);
261 return DBUS_HANDLER_RESULT_HANDLED;
264 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
266 invalid:
267 if (reply)
268 dbus_message_unref(reply);
270 if (!(reply = dbus_message_new_error(
272 DBUS_ERROR_INVALID_ARGS,
273 "Invalid arguments")))
274 goto oom;
276 if (!dbus_connection_send(c, reply, NULL))
277 goto oom;
279 dbus_message_unref(reply);
281 dbus_error_free(&error);
283 return DBUS_HANDLER_RESULT_HANDLED;
285 oom:
286 if (reply)
287 dbus_message_unref(reply);
289 dbus_error_free(&error);
291 return DBUS_HANDLER_RESULT_NEED_MEMORY;
294 static DBusHandlerResult filter_handler(
295 DBusConnection *c,
296 DBusMessage *m,
297 void *userdata) {
299 DBusMessage *reply;
300 rd_device *d;
301 DBusError error;
303 dbus_error_init(&error);
305 d = (rd_device*)userdata;
307 if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) {
308 const char *name;
310 if (!dbus_message_get_args(
312 &error,
313 DBUS_TYPE_STRING, &name,
314 DBUS_TYPE_INVALID))
315 goto invalid;
317 if (strcmp(name, d->service_name) == 0 && d->owning) {
318 d->owning = 0;
320 if (!d->gave_up) {
321 d->ref++;
323 if (d->request_cb)
324 d->request_cb(d, 1);
325 d->gave_up = 1;
327 rd_release(d);
330 return DBUS_HANDLER_RESULT_HANDLED;
334 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
336 invalid:
337 if (!(reply = dbus_message_new_error(
339 DBUS_ERROR_INVALID_ARGS,
340 "Invalid arguments")))
341 goto oom;
343 if (!dbus_connection_send(c, reply, NULL))
344 goto oom;
346 dbus_message_unref(reply);
348 dbus_error_free(&error);
350 return DBUS_HANDLER_RESULT_HANDLED;
352 oom:
353 if (reply)
354 dbus_message_unref(reply);
356 dbus_error_free(&error);
358 return DBUS_HANDLER_RESULT_NEED_MEMORY;
361 static DBusObjectPathVTable vtable;
363 int rd_acquire(
364 rd_device **_d,
365 DBusConnection *connection,
366 const char *device_name,
367 const char *application_name,
368 int32_t priority,
369 rd_request_cb_t request_cb,
370 DBusError *error) {
372 rd_device *d = NULL;
373 int r, k;
374 DBusError _error;
375 DBusMessage *m = NULL, *reply = NULL;
376 dbus_bool_t good;
377 vtable.message_function = object_handler;
379 if (!error) {
380 error = &_error;
381 dbus_error_init(error);
384 if (!_d) {
385 assert(0);
386 r = -EINVAL;
387 goto fail;
390 if (!connection) {
391 assert(0);
392 r = -EINVAL;
393 goto fail;
396 if (!device_name) {
397 assert(0);
398 r = -EINVAL;
399 goto fail;
402 if (!request_cb && priority != INT32_MAX) {
403 assert(0);
404 r = -EINVAL;
405 goto fail;
408 if (!(d = (rd_device *)calloc(sizeof(rd_device), 1))) {
409 dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for rd_device struct");
410 r = -ENOMEM;
411 goto fail;
414 d->ref = 1;
416 if (!(d->device_name = strdup(device_name))) {
417 dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot duplicate device name string");
418 r = -ENOMEM;
419 goto fail;
422 if (!(d->application_name = strdup(application_name))) {
423 dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot duplicate application name string");
424 r = -ENOMEM;
425 goto fail;
428 d->priority = priority;
429 d->connection = dbus_connection_ref(connection);
430 d->request_cb = request_cb;
432 if (!(d->service_name = (char*)malloc(sizeof(SERVICE_PREFIX) + strlen(device_name)))) {
433 dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for service name string");
434 r = -ENOMEM;
435 goto fail;
437 sprintf(d->service_name, SERVICE_PREFIX "%s", d->device_name);
439 if (!(d->object_path = (char*)malloc(sizeof(OBJECT_PREFIX) + strlen(device_name)))) {
440 dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for object path string");
441 r = -ENOMEM;
442 goto fail;
444 sprintf(d->object_path, OBJECT_PREFIX "%s", d->device_name);
446 if ((k = dbus_bus_request_name(
447 d->connection,
448 d->service_name,
449 DBUS_NAME_FLAG_DO_NOT_QUEUE|
450 (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0),
451 error)) < 0) {
452 jack_error("dbus_bus_request_name() failed. (1)");
453 r = -EIO;
454 goto fail;
457 switch (k) {
458 case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
459 case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
460 goto success;
461 case DBUS_REQUEST_NAME_REPLY_EXISTS:
462 break;
463 case DBUS_REQUEST_NAME_REPLY_IN_QUEUE : /* DBUS_NAME_FLAG_DO_NOT_QUEUE was specified */
464 default: /* unknown reply returned */
465 jack_error("request name reply with unexpected value %d.", k);
466 assert(0);
467 r = -EIO;
468 goto fail;
471 if (priority <= INT32_MIN) {
472 r = -EBUSY;
473 dbus_set_error(error, RESERVE_ERROR_RELEASE_DENIED, "Device reservation request with priority %"PRIi32" denied for \"%s\"", priority, device_name);
474 goto fail;
477 if (!(m = dbus_message_new_method_call(
478 d->service_name,
479 d->object_path,
480 "org.freedesktop.ReserveDevice1",
481 "RequestRelease"))) {
482 dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for RequestRelease method call");
483 r = -ENOMEM;
484 goto fail;
487 if (!dbus_message_append_args(
489 DBUS_TYPE_INT32, &d->priority,
490 DBUS_TYPE_INVALID)) {
491 dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot append args for RequestRelease method call");
492 r = -ENOMEM;
493 goto fail;
496 if (!(reply = dbus_connection_send_with_reply_and_block(
497 d->connection,
499 5000, /* 5s */
500 error))) {
502 if (dbus_error_has_name(error, DBUS_ERROR_TIMED_OUT) ||
503 dbus_error_has_name(error, DBUS_ERROR_UNKNOWN_METHOD) ||
504 dbus_error_has_name(error, DBUS_ERROR_NO_REPLY)) {
505 /* This must be treated as denied. */
506 jack_info("Device reservation request with priority %"PRIi32" denied for \"%s\": %s (%s)", priority, device_name, error->name, error->message);
507 r = -EBUSY;
508 goto fail;
511 jack_error("dbus_connection_send_with_reply_and_block(RequestRelease) failed.");
512 r = -EIO;
513 goto fail;
516 if (!dbus_message_get_args(
517 reply,
518 error,
519 DBUS_TYPE_BOOLEAN, &good,
520 DBUS_TYPE_INVALID)) {
521 jack_error("RequestRelease() reply is invalid.");
522 r = -EIO;
523 goto fail;
526 if (!good) {
527 dbus_set_error(error, RESERVE_ERROR_RELEASE_DENIED, "Device reservation request with priority %"PRIi32" denied for \"%s\" via RequestRelease()", priority, device_name);
528 r = -EBUSY;
529 goto fail;
532 if ((k = dbus_bus_request_name(
533 d->connection,
534 d->service_name,
535 DBUS_NAME_FLAG_DO_NOT_QUEUE|
536 (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0)|
537 DBUS_NAME_FLAG_REPLACE_EXISTING,
538 error)) < 0) {
539 jack_error("dbus_bus_request_name() failed. (2)");
540 r = -EIO;
541 goto fail;
544 if (k != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
545 /* this is racy, another contender may have acquired the device */
546 dbus_set_error(error, RESERVE_ERROR_PROTOCOL_VIOLATION, "request name reply is not DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER but %d.", k);
547 r = -EIO;
548 goto fail;
551 success:
552 d->owning = 1;
554 if (!(dbus_connection_try_register_object_path(
555 d->connection,
556 d->object_path,
557 &vtable,
559 error))) {
560 jack_error("cannot register object path \"%s\": %s", d->object_path, error->message);
561 r = -ENOMEM;
562 goto fail;
565 d->registered = 1;
567 if (!dbus_connection_add_filter(
568 d->connection,
569 filter_handler,
571 NULL)) {
572 dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot add filter");
573 r = -ENOMEM;
574 goto fail;
577 d->filtering = 1;
579 *_d = d;
580 return 0;
582 fail:
583 if (m)
584 dbus_message_unref(m);
586 if (reply)
587 dbus_message_unref(reply);
589 if (&_error == error)
590 dbus_error_free(&_error);
592 if (d)
593 rd_release(d);
595 return r;
598 void rd_release(
599 rd_device *d) {
601 if (!d)
602 return;
604 assert(d->ref > 0);
606 if (--d->ref)
607 return;
610 if (d->filtering)
611 dbus_connection_remove_filter(
612 d->connection,
613 filter_handler,
616 if (d->registered)
617 dbus_connection_unregister_object_path(
618 d->connection,
619 d->object_path);
621 if (d->owning) {
622 DBusError error;
623 dbus_error_init(&error);
625 dbus_bus_release_name(
626 d->connection,
627 d->service_name,
628 &error);
630 dbus_error_free(&error);
633 free(d->device_name);
634 free(d->application_name);
635 free(d->application_device_name);
636 free(d->service_name);
637 free(d->object_path);
639 if (d->connection)
640 dbus_connection_unref(d->connection);
642 free(d);
645 int rd_set_application_device_name(rd_device *d, const char *n) {
646 char *t;
648 if (!d)
649 return -EINVAL;
651 assert(d->ref > 0);
653 if (!(t = strdup(n)))
654 return -ENOMEM;
656 free(d->application_device_name);
657 d->application_device_name = t;
658 return 0;
661 void rd_set_userdata(rd_device *d, void *userdata) {
663 if (!d)
664 return;
666 assert(d->ref > 0);
667 d->userdata = userdata;
670 void* rd_get_userdata(rd_device *d) {
672 if (!d)
673 return NULL;
675 assert(d->ref > 0);
677 return d->userdata;