1 /***
2 Copyright 2009 Lennart Poettering
3
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:
11
12 The above copyright notice and this permission notice shall be
13 included in all copies or substantial portions of the Software.
14
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 ***/
24
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>
32
33 #include "reserve.h"
34 #include "jack/control.h"
35
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"
39
40 struct rd_device {
41 int ref;
42
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;
49
50 DBusConnection *connection;
51
52 int owning:1;
53 int registered:1;
54 int filtering:1;
55 int gave_up:1;
56
57 rd_request_cb_t request_cb;
58 void *userdata;
59 };
60
61
62 #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."
63 #define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/"
64
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>";
92
add_variant(DBusMessage * m,int type,const void * data)93 static dbus_bool_t add_variant(
94 DBusMessage *m,
95 int type,
96 const void *data) {
97
98 DBusMessageIter iter, sub;
99 char t[2];
100
101 t[0] = (char) type;
102 t[1] = 0;
103
104 dbus_message_iter_init_append(m, &iter);
105
106 if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, t, &sub))
107 return FALSE;
108
109 if (!dbus_message_iter_append_basic(&sub, type, data))
110 return FALSE;
111
112 if (!dbus_message_iter_close_container(&iter, &sub))
113 return FALSE;
114
115 return TRUE;
116 }
117
object_handler(DBusConnection * c,DBusMessage * m,void * userdata)118 static DBusHandlerResult object_handler(
119 DBusConnection *c,
120 DBusMessage *m,
121 void *userdata) {
122
123 rd_device *d;
124 DBusError error;
125 DBusMessage *reply = NULL;
126
127 dbus_error_init(&error);
128
129 d = (rd_device*)userdata;
130 assert(d->ref >= 1);
131
132 if (dbus_message_is_method_call(
133 m,
134 "org.freedesktop.ReserveDevice1",
135 "RequestRelease")) {
136
137 int32_t priority;
138 dbus_bool_t ret;
139
140 if (!dbus_message_get_args(
141 m,
142 &error,
143 DBUS_TYPE_INT32, &priority,
144 DBUS_TYPE_INVALID))
145 goto invalid;
146
147 ret = FALSE;
148
149 if (priority > d->priority && d->request_cb) {
150 d->ref++;
151
152 if (d->request_cb(d, 0) > 0) {
153 ret = TRUE;
154 d->gave_up = 1;
155 }
156
157 rd_release(d);
158 }
159
160 if (!(reply = dbus_message_new_method_return(m)))
161 goto oom;
162
163 if (!dbus_message_append_args(
164 reply,
165 DBUS_TYPE_BOOLEAN, &ret,
166 DBUS_TYPE_INVALID))
167 goto oom;
168
169 if (!dbus_connection_send(c, reply, NULL))
170 goto oom;
171
172 dbus_message_unref(reply);
173
174 return DBUS_HANDLER_RESULT_HANDLED;
175
176 } else if (dbus_message_is_method_call(
177 m,
178 "org.freedesktop.DBus.Properties",
179 "Get")) {
180
181 const char *interface, *property;
182
183 if (!dbus_message_get_args(
184 m,
185 &error,
186 DBUS_TYPE_STRING, &interface,
187 DBUS_TYPE_STRING, &property,
188 DBUS_TYPE_INVALID))
189 goto invalid;
190
191 if (strcmp(interface, "org.freedesktop.ReserveDevice1") == 0) {
192 const char *empty = "";
193
194 if (strcmp(property, "ApplicationName") == 0 && d->application_name) {
195 if (!(reply = dbus_message_new_method_return(m)))
196 goto oom;
197
198 if (!add_variant(
199 reply,
200 DBUS_TYPE_STRING,
201 d->application_name ? (const char**) &d->application_name : &empty))
202 goto oom;
203
204 } else if (strcmp(property, "ApplicationDeviceName") == 0) {
205 if (!(reply = dbus_message_new_method_return(m)))
206 goto oom;
207
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;
213
214 } else if (strcmp(property, "Priority") == 0) {
215 if (!(reply = dbus_message_new_method_return(m)))
216 goto oom;
217
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(
225 m,
226 DBUS_ERROR_UNKNOWN_METHOD,
227 "Unknown property %s",
228 property)))
229 goto oom;
230 }
231
232 if (!dbus_connection_send(c, reply, NULL))
233 goto oom;
234
235 dbus_message_unref(reply);
236
237 return DBUS_HANDLER_RESULT_HANDLED;
238 }
239
240 } else if (dbus_message_is_method_call(
241 m,
242 "org.freedesktop.DBus.Introspectable",
243 "Introspect")) {
244 const char *i = introspection;
245
246 if (!(reply = dbus_message_new_method_return(m)))
247 goto oom;
248
249 if (!dbus_message_append_args(
250 reply,
251 DBUS_TYPE_STRING,
252 &i,
253 DBUS_TYPE_INVALID))
254 goto oom;
255
256 if (!dbus_connection_send(c, reply, NULL))
257 goto oom;
258
259 dbus_message_unref(reply);
260
261 return DBUS_HANDLER_RESULT_HANDLED;
262 }
263
264 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
265
266 invalid:
267 if (reply)
268 dbus_message_unref(reply);
269
270 if (!(reply = dbus_message_new_error(
271 m,
272 DBUS_ERROR_INVALID_ARGS,
273 "Invalid arguments")))
274 goto oom;
275
276 if (!dbus_connection_send(c, reply, NULL))
277 goto oom;
278
279 dbus_message_unref(reply);
280
281 dbus_error_free(&error);
282
283 return DBUS_HANDLER_RESULT_HANDLED;
284
285 oom:
286 if (reply)
287 dbus_message_unref(reply);
288
289 dbus_error_free(&error);
290
291 return DBUS_HANDLER_RESULT_NEED_MEMORY;
292 }
293
filter_handler(DBusConnection * c,DBusMessage * m,void * userdata)294 static DBusHandlerResult filter_handler(
295 DBusConnection *c,
296 DBusMessage *m,
297 void *userdata) {
298
299 DBusMessage *reply;
300 rd_device *d;
301 DBusError error;
302
303 dbus_error_init(&error);
304
305 d = (rd_device*)userdata;
306
307 if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) {
308 const char *name;
309
310 if (!dbus_message_get_args(
311 m,
312 &error,
313 DBUS_TYPE_STRING, &name,
314 DBUS_TYPE_INVALID))
315 goto invalid;
316
317 if (strcmp(name, d->service_name) == 0 && d->owning) {
318 d->owning = 0;
319
320 if (!d->gave_up) {
321 d->ref++;
322
323 if (d->request_cb)
324 d->request_cb(d, 1);
325 d->gave_up = 1;
326
327 rd_release(d);
328 }
329
330 return DBUS_HANDLER_RESULT_HANDLED;
331 }
332 }
333
334 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
335
336 invalid:
337 if (!(reply = dbus_message_new_error(
338 m,
339 DBUS_ERROR_INVALID_ARGS,
340 "Invalid arguments")))
341 goto oom;
342
343 if (!dbus_connection_send(c, reply, NULL))
344 goto oom;
345
346 dbus_message_unref(reply);
347
348 dbus_error_free(&error);
349
350 return DBUS_HANDLER_RESULT_HANDLED;
351
352 oom:
353 if (reply)
354 dbus_message_unref(reply);
355
356 dbus_error_free(&error);
357
358 return DBUS_HANDLER_RESULT_NEED_MEMORY;
359 }
360
361 static DBusObjectPathVTable vtable;
362
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)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) {
371
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;
378
379 if (!error) {
380 error = &_error;
381 dbus_error_init(error);
382 }
383
384 if (!_d) {
385 assert(0);
386 r = -EINVAL;
387 goto fail;
388 }
389
390 if (!connection) {
391 assert(0);
392 r = -EINVAL;
393 goto fail;
394 }
395
396 if (!device_name) {
397 assert(0);
398 r = -EINVAL;
399 goto fail;
400 }
401
402 if (!request_cb && priority != INT32_MAX) {
403 assert(0);
404 r = -EINVAL;
405 goto fail;
406 }
407
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;
412 }
413
414 d->ref = 1;
415
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;
420 }
421
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;
426 }
427
428 d->priority = priority;
429 d->connection = dbus_connection_ref(connection);
430 d->request_cb = request_cb;
431
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;
436 }
437 sprintf(d->service_name, SERVICE_PREFIX "%s", d->device_name);
438
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;
443 }
444 sprintf(d->object_path, OBJECT_PREFIX "%s", d->device_name);
445
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;
455 }
456
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;
469 }
470
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;
475 }
476
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;
485 }
486
487 if (!dbus_message_append_args(
488 m,
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;
494 }
495
496 if (!(reply = dbus_connection_send_with_reply_and_block(
497 d->connection,
498 m,
499 5000, /* 5s */
500 error))) {
501
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;
509 }
510
511 jack_error("dbus_connection_send_with_reply_and_block(RequestRelease) failed.");
512 r = -EIO;
513 goto fail;
514 }
515
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;
524 }
525
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;
530 }
531
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;
542 }
543
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;
549 }
550
551 success:
552 d->owning = 1;
553
554 if (!(dbus_connection_try_register_object_path(
555 d->connection,
556 d->object_path,
557 &vtable,
558 d,
559 error))) {
560 jack_error("cannot register object path \"%s\": %s", d->object_path, error->message);
561 r = -ENOMEM;
562 goto fail;
563 }
564
565 d->registered = 1;
566
567 if (!dbus_connection_add_filter(
568 d->connection,
569 filter_handler,
570 d,
571 NULL)) {
572 dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot add filter");
573 r = -ENOMEM;
574 goto fail;
575 }
576
577 d->filtering = 1;
578
579 *_d = d;
580 return 0;
581
582 fail:
583 if (m)
584 dbus_message_unref(m);
585
586 if (reply)
587 dbus_message_unref(reply);
588
589 if (&_error == error)
590 dbus_error_free(&_error);
591
592 if (d)
593 rd_release(d);
594
595 return r;
596 }
597
rd_release(rd_device * d)598 void rd_release(
599 rd_device *d) {
600
601 if (!d)
602 return;
603
604 assert(d->ref > 0);
605
606 if (--d->ref)
607 return;
608
609
610 if (d->filtering)
611 dbus_connection_remove_filter(
612 d->connection,
613 filter_handler,
614 d);
615
616 if (d->registered)
617 dbus_connection_unregister_object_path(
618 d->connection,
619 d->object_path);
620
621 if (d->owning) {
622 DBusError error;
623 dbus_error_init(&error);
624
625 dbus_bus_release_name(
626 d->connection,
627 d->service_name,
628 &error);
629
630 dbus_error_free(&error);
631 }
632
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);
638
639 if (d->connection)
640 dbus_connection_unref(d->connection);
641
642 free(d);
643 }
644
rd_set_application_device_name(rd_device * d,const char * n)645 int rd_set_application_device_name(rd_device *d, const char *n) {
646 char *t;
647
648 if (!d)
649 return -EINVAL;
650
651 assert(d->ref > 0);
652
653 if (!(t = strdup(n)))
654 return -ENOMEM;
655
656 free(d->application_device_name);
657 d->application_device_name = t;
658 return 0;
659 }
660
rd_set_userdata(rd_device * d,void * userdata)661 void rd_set_userdata(rd_device *d, void *userdata) {
662
663 if (!d)
664 return;
665
666 assert(d->ref > 0);
667 d->userdata = userdata;
668 }
669
rd_get_userdata(rd_device * d)670 void* rd_get_userdata(rd_device *d) {
671
672 if (!d)
673 return NULL;
674
675 assert(d->ref > 0);
676
677 return d->userdata;
678 }
679