]> code.delx.au - pulseaudio/blob - src/modules/reserve.c
alsa-mixer: Add surround 2.1 profile
[pulseaudio] / src / modules / reserve.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: t -*-*/
2
3 /***
4 Copyright 2009 Lennart Poettering
5
6 Permission is hereby granted, free of charge, to any person
7 obtaining a copy of this software and associated documentation files
8 (the "Software"), to deal in the Software without restriction,
9 including without limitation the rights to use, copy, modify, merge,
10 publish, distribute, sublicense, and/or sell copies of the Software,
11 and to permit persons to whom the Software is furnished to do so,
12 subject to the following conditions:
13
14 The above copyright notice and this permission notice shall be
15 included in all copies or substantial portions of the Software.
16
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25 ***/
26
27 #include <string.h>
28 #include <unistd.h>
29 #include <errno.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <assert.h>
33
34 #include "reserve.h"
35
36 struct rd_device {
37 int ref;
38
39 char *device_name;
40 char *application_name;
41 char *application_device_name;
42 char *service_name;
43 char *object_path;
44 int32_t priority;
45
46 DBusConnection *connection;
47
48 unsigned owning:1;
49 unsigned registered:1;
50 unsigned filtering:1;
51 unsigned gave_up:1;
52
53 rd_request_cb_t request_cb;
54 void *userdata;
55 };
56
57 #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."
58 #define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/"
59
60 static const char introspection[] =
61 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
62 "<node>"
63 " <!-- If you are looking for documentation make sure to check out\n"
64 " http://git.0pointer.de/?p=reserve.git;a=blob;f=reserve.txt -->\n"
65 " <interface name=\"org.freedesktop.ReserveDevice1\">"
66 " <method name=\"RequestRelease\">"
67 " <arg name=\"priority\" type=\"i\" direction=\"in\"/>"
68 " <arg name=\"result\" type=\"b\" direction=\"out\"/>"
69 " </method>"
70 " <property name=\"Priority\" type=\"i\" access=\"read\"/>"
71 " <property name=\"ApplicationName\" type=\"s\" access=\"read\"/>"
72 " <property name=\"ApplicationDeviceName\" type=\"s\" access=\"read\"/>"
73 " </interface>"
74 " <interface name=\"org.freedesktop.DBus.Properties\">"
75 " <method name=\"Get\">"
76 " <arg name=\"interface\" direction=\"in\" type=\"s\"/>"
77 " <arg name=\"property\" direction=\"in\" type=\"s\"/>"
78 " <arg name=\"value\" direction=\"out\" type=\"v\"/>"
79 " </method>"
80 " </interface>"
81 " <interface name=\"org.freedesktop.DBus.Introspectable\">"
82 " <method name=\"Introspect\">"
83 " <arg name=\"data\" type=\"s\" direction=\"out\"/>"
84 " </method>"
85 " </interface>"
86 "</node>";
87
88 static dbus_bool_t add_variant(
89 DBusMessage *m,
90 int type,
91 const void *data) {
92
93 DBusMessageIter iter, sub;
94 char t[2];
95
96 t[0] = (char) type;
97 t[1] = 0;
98
99 dbus_message_iter_init_append(m, &iter);
100
101 if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, t, &sub))
102 return FALSE;
103
104 if (!dbus_message_iter_append_basic(&sub, type, data))
105 return FALSE;
106
107 if (!dbus_message_iter_close_container(&iter, &sub))
108 return FALSE;
109
110 return TRUE;
111 }
112
113 static DBusHandlerResult object_handler(
114 DBusConnection *c,
115 DBusMessage *m,
116 void *userdata) {
117
118 rd_device *d;
119 DBusError error;
120 DBusMessage *reply = NULL;
121
122 dbus_error_init(&error);
123
124 d = userdata;
125 assert(d->ref >= 1);
126
127 if (dbus_message_is_method_call(
128 m,
129 "org.freedesktop.ReserveDevice1",
130 "RequestRelease")) {
131
132 int32_t priority;
133 dbus_bool_t ret;
134
135 if (!dbus_message_get_args(
136 m,
137 &error,
138 DBUS_TYPE_INT32, &priority,
139 DBUS_TYPE_INVALID))
140 goto invalid;
141
142 ret = FALSE;
143
144 if (priority > d->priority && d->request_cb) {
145 d->ref++;
146
147 if (d->request_cb(d, 0) > 0) {
148 ret = TRUE;
149 d->gave_up = 1;
150 }
151
152 rd_release(d);
153 }
154
155 if (!(reply = dbus_message_new_method_return(m)))
156 goto oom;
157
158 if (!dbus_message_append_args(
159 reply,
160 DBUS_TYPE_BOOLEAN, &ret,
161 DBUS_TYPE_INVALID))
162 goto oom;
163
164 if (!dbus_connection_send(c, reply, NULL))
165 goto oom;
166
167 dbus_message_unref(reply);
168
169 return DBUS_HANDLER_RESULT_HANDLED;
170
171 } else if (dbus_message_is_method_call(
172 m,
173 "org.freedesktop.DBus.Properties",
174 "Get")) {
175
176 const char *interface, *property;
177
178 if (!dbus_message_get_args(
179 m,
180 &error,
181 DBUS_TYPE_STRING, &interface,
182 DBUS_TYPE_STRING, &property,
183 DBUS_TYPE_INVALID))
184 goto invalid;
185
186 if (strcmp(interface, "org.freedesktop.ReserveDevice1") == 0) {
187 const char *empty = "";
188
189 if (strcmp(property, "ApplicationName") == 0 && d->application_name) {
190 if (!(reply = dbus_message_new_method_return(m)))
191 goto oom;
192
193 if (!add_variant(
194 reply,
195 DBUS_TYPE_STRING,
196 d->application_name ? (const char**) &d->application_name : &empty))
197 goto oom;
198
199 } else if (strcmp(property, "ApplicationDeviceName") == 0) {
200 if (!(reply = dbus_message_new_method_return(m)))
201 goto oom;
202
203 if (!add_variant(
204 reply,
205 DBUS_TYPE_STRING,
206 d->application_device_name ? (const char**) &d->application_device_name : &empty))
207 goto oom;
208
209 } else if (strcmp(property, "Priority") == 0) {
210 if (!(reply = dbus_message_new_method_return(m)))
211 goto oom;
212
213 if (!add_variant(
214 reply,
215 DBUS_TYPE_INT32,
216 &d->priority))
217 goto oom;
218 } else {
219 if (!(reply = dbus_message_new_error_printf(
220 m,
221 DBUS_ERROR_UNKNOWN_METHOD,
222 "Unknown property %s",
223 property)))
224 goto oom;
225 }
226
227 if (!dbus_connection_send(c, reply, NULL))
228 goto oom;
229
230 dbus_message_unref(reply);
231
232 return DBUS_HANDLER_RESULT_HANDLED;
233 }
234
235 } else if (dbus_message_is_method_call(
236 m,
237 "org.freedesktop.DBus.Introspectable",
238 "Introspect")) {
239 const char *i = introspection;
240
241 if (!(reply = dbus_message_new_method_return(m)))
242 goto oom;
243
244 if (!dbus_message_append_args(
245 reply,
246 DBUS_TYPE_STRING,
247 &i,
248 DBUS_TYPE_INVALID))
249 goto oom;
250
251 if (!dbus_connection_send(c, reply, NULL))
252 goto oom;
253
254 dbus_message_unref(reply);
255
256 return DBUS_HANDLER_RESULT_HANDLED;
257 }
258
259 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
260
261 invalid:
262 if (reply)
263 dbus_message_unref(reply);
264
265 if (!(reply = dbus_message_new_error(
266 m,
267 DBUS_ERROR_INVALID_ARGS,
268 "Invalid arguments")))
269 goto oom;
270
271 if (!dbus_connection_send(c, reply, NULL))
272 goto oom;
273
274 dbus_message_unref(reply);
275
276 dbus_error_free(&error);
277
278 return DBUS_HANDLER_RESULT_HANDLED;
279
280 oom:
281 if (reply)
282 dbus_message_unref(reply);
283
284 dbus_error_free(&error);
285
286 return DBUS_HANDLER_RESULT_NEED_MEMORY;
287 }
288
289 static DBusHandlerResult filter_handler(
290 DBusConnection *c,
291 DBusMessage *m,
292 void *userdata) {
293
294 rd_device *d;
295 DBusError error;
296 char *name_owner = NULL;
297
298 dbus_error_init(&error);
299
300 d = userdata;
301 assert(d->ref >= 1);
302
303 if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) {
304 const char *name;
305
306 if (!dbus_message_get_args(
307 m,
308 &error,
309 DBUS_TYPE_STRING, &name,
310 DBUS_TYPE_INVALID))
311 goto invalid;
312
313 if (strcmp(name, d->service_name) == 0 && d->owning) {
314 /* Verify the actual owner of the name to avoid leaked NameLost
315 * signals from previous reservations. The D-Bus daemon will send
316 * all messages asynchronously in the correct order, but we could
317 * potentially process them too late due to the pseudo-blocking
318 * call mechanism used during both acquisition and release. This
319 * can happen if we release the device and immediately after
320 * reacquire it before NameLost is processed. */
321 if (!d->gave_up) {
322 const char *un;
323
324 if ((un = dbus_bus_get_unique_name(c)) && rd_dbus_get_name_owner(c, d->service_name, &name_owner, &error) == 0)
325 if (strcmp(name_owner, un) == 0)
326 goto invalid; /* Name still owned by us */
327 }
328
329 d->owning = 0;
330
331 if (!d->gave_up) {
332 d->ref++;
333
334 if (d->request_cb)
335 d->request_cb(d, 1);
336 d->gave_up = 1;
337
338 rd_release(d);
339 }
340
341 }
342 }
343
344 invalid:
345 free(name_owner);
346 dbus_error_free(&error);
347
348 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
349 }
350
351
352 static const struct DBusObjectPathVTable vtable ={
353 .message_function = object_handler
354 };
355
356 int rd_acquire(
357 rd_device **_d,
358 DBusConnection *connection,
359 const char *device_name,
360 const char *application_name,
361 int32_t priority,
362 rd_request_cb_t request_cb,
363 DBusError *error) {
364
365 rd_device *d = NULL;
366 int r, k;
367 DBusError _error;
368 DBusMessage *m = NULL, *reply = NULL;
369 dbus_bool_t good;
370
371 if (!error)
372 error = &_error;
373
374 dbus_error_init(error);
375
376 if (!_d)
377 return -EINVAL;
378
379 if (!connection)
380 return -EINVAL;
381
382 if (!device_name)
383 return -EINVAL;
384
385 if (!request_cb && priority != INT32_MAX)
386 return -EINVAL;
387
388 if (!(d = calloc(sizeof(rd_device), 1)))
389 return -ENOMEM;
390
391 d->ref = 1;
392
393 if (!(d->device_name = strdup(device_name))) {
394 r = -ENOMEM;
395 goto fail;
396 }
397
398 if (!(d->application_name = strdup(application_name))) {
399 r = -ENOMEM;
400 goto fail;
401 }
402
403 d->priority = priority;
404 d->connection = dbus_connection_ref(connection);
405 d->request_cb = request_cb;
406
407 if (!(d->service_name = malloc(sizeof(SERVICE_PREFIX) + strlen(device_name)))) {
408 r = -ENOMEM;
409 goto fail;
410 }
411 sprintf(d->service_name, SERVICE_PREFIX "%s", d->device_name);
412
413 if (!(d->object_path = malloc(sizeof(OBJECT_PREFIX) + strlen(device_name)))) {
414 r = -ENOMEM;
415 goto fail;
416 }
417 sprintf(d->object_path, OBJECT_PREFIX "%s", d->device_name);
418
419 if ((k = dbus_bus_request_name(
420 d->connection,
421 d->service_name,
422 DBUS_NAME_FLAG_DO_NOT_QUEUE|
423 (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0),
424 error)) < 0) {
425 r = -EIO;
426 goto fail;
427 }
428
429 if (k == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
430 goto success;
431
432 if (k != DBUS_REQUEST_NAME_REPLY_EXISTS) {
433 r = -EIO;
434 goto fail;
435 }
436
437 if (priority <= INT32_MIN) {
438 r = -EBUSY;
439 goto fail;
440 }
441
442 if (!(m = dbus_message_new_method_call(
443 d->service_name,
444 d->object_path,
445 "org.freedesktop.ReserveDevice1",
446 "RequestRelease"))) {
447 r = -ENOMEM;
448 goto fail;
449 }
450
451 if (!dbus_message_append_args(
452 m,
453 DBUS_TYPE_INT32, &d->priority,
454 DBUS_TYPE_INVALID)) {
455 r = -ENOMEM;
456 goto fail;
457 }
458
459 if (!(reply = dbus_connection_send_with_reply_and_block(
460 d->connection,
461 m,
462 5000, /* 5s */
463 error))) {
464
465 if (dbus_error_has_name(error, DBUS_ERROR_TIMED_OUT) ||
466 dbus_error_has_name(error, DBUS_ERROR_UNKNOWN_METHOD) ||
467 dbus_error_has_name(error, DBUS_ERROR_NO_REPLY)) {
468 /* This must be treated as denied. */
469 r = -EBUSY;
470 goto fail;
471 }
472
473 r = -EIO;
474 goto fail;
475 }
476
477 if (!dbus_message_get_args(
478 reply,
479 error,
480 DBUS_TYPE_BOOLEAN, &good,
481 DBUS_TYPE_INVALID)) {
482 r = -EIO;
483 goto fail;
484 }
485
486 if (!good) {
487 r = -EBUSY;
488 goto fail;
489 }
490
491 if ((k = dbus_bus_request_name(
492 d->connection,
493 d->service_name,
494 DBUS_NAME_FLAG_DO_NOT_QUEUE|
495 (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0)|
496 DBUS_NAME_FLAG_REPLACE_EXISTING,
497 error)) < 0) {
498 r = -EIO;
499 goto fail;
500 }
501
502 if (k != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
503 r = -EIO;
504 goto fail;
505 }
506
507 success:
508 d->owning = 1;
509
510 if (!(dbus_connection_register_object_path(
511 d->connection,
512 d->object_path,
513 &vtable,
514 d))) {
515 r = -ENOMEM;
516 goto fail;
517 }
518
519 d->registered = 1;
520
521 if (!dbus_connection_add_filter(
522 d->connection,
523 filter_handler,
524 d,
525 NULL)) {
526 r = -ENOMEM;
527 goto fail;
528 }
529
530 d->filtering = 1;
531
532 *_d = d;
533 return 0;
534
535 fail:
536 if (m)
537 dbus_message_unref(m);
538
539 if (reply)
540 dbus_message_unref(reply);
541
542 if (&_error == error)
543 dbus_error_free(&_error);
544
545 if (d)
546 rd_release(d);
547
548 return r;
549 }
550
551 void rd_release(
552 rd_device *d) {
553
554 if (!d)
555 return;
556
557 assert(d->ref > 0);
558
559 if (--d->ref > 0)
560 return;
561
562
563 if (d->filtering)
564 dbus_connection_remove_filter(
565 d->connection,
566 filter_handler,
567 d);
568
569 if (d->registered)
570 dbus_connection_unregister_object_path(
571 d->connection,
572 d->object_path);
573
574 if (d->owning)
575 dbus_bus_release_name(
576 d->connection,
577 d->service_name,
578 NULL);
579
580 free(d->device_name);
581 free(d->application_name);
582 free(d->application_device_name);
583 free(d->service_name);
584 free(d->object_path);
585
586 if (d->connection)
587 dbus_connection_unref(d->connection);
588
589 free(d);
590 }
591
592 int rd_set_application_device_name(rd_device *d, const char *n) {
593 char *t;
594
595 if (!d)
596 return -EINVAL;
597
598 assert(d->ref > 0);
599
600 if (!(t = strdup(n)))
601 return -ENOMEM;
602
603 free(d->application_device_name);
604 d->application_device_name = t;
605 return 0;
606 }
607
608 void rd_set_userdata(rd_device *d, void *userdata) {
609
610 if (!d)
611 return;
612
613 assert(d->ref > 0);
614 d->userdata = userdata;
615 }
616
617 void* rd_get_userdata(rd_device *d) {
618
619 if (!d)
620 return NULL;
621
622 assert(d->ref > 0);
623
624 return d->userdata;
625 }
626
627 int rd_dbus_get_name_owner(
628 DBusConnection *connection,
629 const char *name,
630 char **name_owner,
631 DBusError *error) {
632
633 DBusMessage *msg, *reply;
634 int r;
635
636 *name_owner = NULL;
637
638 if (!(msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner"))) {
639 r = -ENOMEM;
640 goto fail;
641 }
642
643 if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)) {
644 r = -ENOMEM;
645 goto fail;
646 }
647
648 reply = dbus_connection_send_with_reply_and_block(connection, msg, DBUS_TIMEOUT_USE_DEFAULT, error);
649 dbus_message_unref(msg);
650 msg = NULL;
651
652 if (reply) {
653 if (!dbus_message_get_args(reply, error, DBUS_TYPE_STRING, name_owner, DBUS_TYPE_INVALID)) {
654 dbus_message_unref(reply);
655 r = -EIO;
656 goto fail;
657 }
658
659 *name_owner = strdup(*name_owner);
660 dbus_message_unref(reply);
661
662 if (!*name_owner) {
663 r = -ENOMEM;
664 goto fail;
665 }
666
667 } else if (dbus_error_has_name(error, "org.freedesktop.DBus.Error.NameHasNoOwner"))
668 dbus_error_free(error);
669 else {
670 r = -EIO;
671 goto fail;
672 }
673
674 return 0;
675
676 fail:
677 if (msg)
678 dbus_message_unref(msg);
679
680 return r;
681 }