]> code.delx.au - pulseaudio/blob - src/modules/reserve.c
Merge branch 'master' of git://0pointer.de/pulseaudio into dbus-work
[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 DBusMessage *reply;
295 rd_device *d;
296 DBusError error;
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 d->owning = 0;
315
316 if (!d->gave_up) {
317 d->ref++;
318
319 if (d->request_cb)
320 d->request_cb(d, 1);
321 d->gave_up = 1;
322
323 rd_release(d);
324 }
325
326 return DBUS_HANDLER_RESULT_HANDLED;
327 }
328 }
329
330 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
331
332 invalid:
333 if (!(reply = dbus_message_new_error(
334 m,
335 DBUS_ERROR_INVALID_ARGS,
336 "Invalid arguments")))
337 goto oom;
338
339 if (!dbus_connection_send(c, reply, NULL))
340 goto oom;
341
342 dbus_message_unref(reply);
343
344 dbus_error_free(&error);
345
346 return DBUS_HANDLER_RESULT_HANDLED;
347
348 oom:
349 if (reply)
350 dbus_message_unref(reply);
351
352 dbus_error_free(&error);
353
354 return DBUS_HANDLER_RESULT_NEED_MEMORY;
355 }
356
357
358 static const struct DBusObjectPathVTable vtable ={
359 .message_function = object_handler
360 };
361
362 int rd_acquire(
363 rd_device **_d,
364 DBusConnection *connection,
365 const char *device_name,
366 const char *application_name,
367 int32_t priority,
368 rd_request_cb_t request_cb,
369 DBusError *error) {
370
371 rd_device *d = NULL;
372 int r, k;
373 DBusError _error;
374 DBusMessage *m = NULL, *reply = NULL;
375 dbus_bool_t good;
376
377 if (!error)
378 error = &_error;
379
380 dbus_error_init(error);
381
382 if (!_d)
383 return -EINVAL;
384
385 if (!connection)
386 return -EINVAL;
387
388 if (!device_name)
389 return -EINVAL;
390
391 if (!request_cb && priority != INT32_MAX)
392 return -EINVAL;
393
394 if (!(d = calloc(sizeof(rd_device), 1)))
395 return -ENOMEM;
396
397 d->ref = 1;
398
399 if (!(d->device_name = strdup(device_name))) {
400 r = -ENOMEM;
401 goto fail;
402 }
403
404 if (!(d->application_name = strdup(application_name))) {
405 r = -ENOMEM;
406 goto fail;
407 }
408
409 d->priority = priority;
410 d->connection = dbus_connection_ref(connection);
411 d->request_cb = request_cb;
412
413 if (!(d->service_name = malloc(sizeof(SERVICE_PREFIX) + strlen(device_name)))) {
414 r = -ENOMEM;
415 goto fail;
416 }
417 sprintf(d->service_name, SERVICE_PREFIX "%s", d->device_name);
418
419 if (!(d->object_path = malloc(sizeof(OBJECT_PREFIX) + strlen(device_name)))) {
420 r = -ENOMEM;
421 goto fail;
422 }
423 sprintf(d->object_path, OBJECT_PREFIX "%s", d->device_name);
424
425 if ((k = dbus_bus_request_name(
426 d->connection,
427 d->service_name,
428 DBUS_NAME_FLAG_DO_NOT_QUEUE|
429 (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0),
430 error)) < 0) {
431 r = -EIO;
432 goto fail;
433 }
434
435 if (k == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
436 goto success;
437
438 if (k != DBUS_REQUEST_NAME_REPLY_EXISTS) {
439 r = -EIO;
440 goto fail;
441 }
442
443 if (priority <= INT32_MIN) {
444 r = -EBUSY;
445 goto fail;
446 }
447
448 if (!(m = dbus_message_new_method_call(
449 d->service_name,
450 d->object_path,
451 "org.freedesktop.ReserveDevice1",
452 "RequestRelease"))) {
453 r = -ENOMEM;
454 goto fail;
455 }
456
457 if (!dbus_message_append_args(
458 m,
459 DBUS_TYPE_INT32, &d->priority,
460 DBUS_TYPE_INVALID)) {
461 r = -ENOMEM;
462 goto fail;
463 }
464
465 if (!(reply = dbus_connection_send_with_reply_and_block(
466 d->connection,
467 m,
468 5000, /* 5s */
469 error))) {
470
471 if (dbus_error_has_name(error, DBUS_ERROR_TIMED_OUT) ||
472 dbus_error_has_name(error, DBUS_ERROR_UNKNOWN_METHOD) ||
473 dbus_error_has_name(error, DBUS_ERROR_NO_REPLY)) {
474 /* This must be treated as denied. */
475 r = -EBUSY;
476 goto fail;
477 }
478
479 r = -EIO;
480 goto fail;
481 }
482
483 if (!dbus_message_get_args(
484 reply,
485 error,
486 DBUS_TYPE_BOOLEAN, &good,
487 DBUS_TYPE_INVALID)) {
488 r = -EIO;
489 goto fail;
490 }
491
492 if (!good) {
493 r = -EBUSY;
494 goto fail;
495 }
496
497 if ((k = dbus_bus_request_name(
498 d->connection,
499 d->service_name,
500 DBUS_NAME_FLAG_DO_NOT_QUEUE|
501 (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0)|
502 DBUS_NAME_FLAG_REPLACE_EXISTING,
503 error)) < 0) {
504 r = -EIO;
505 goto fail;
506 }
507
508 if (k != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
509 r = -EIO;
510 goto fail;
511 }
512
513 success:
514 d->owning = 1;
515
516 if (!(dbus_connection_register_object_path(
517 d->connection,
518 d->object_path,
519 &vtable,
520 d))) {
521 r = -ENOMEM;
522 goto fail;
523 }
524
525 d->registered = 1;
526
527 if (!dbus_connection_add_filter(
528 d->connection,
529 filter_handler,
530 d,
531 NULL)) {
532 r = -ENOMEM;
533 goto fail;
534 }
535
536 d->filtering = 1;
537
538 *_d = d;
539 return 0;
540
541 fail:
542 if (m)
543 dbus_message_unref(m);
544
545 if (reply)
546 dbus_message_unref(reply);
547
548 if (&_error == error)
549 dbus_error_free(&_error);
550
551 if (d)
552 rd_release(d);
553
554 return r;
555 }
556
557 void rd_release(
558 rd_device *d) {
559
560 if (!d)
561 return;
562
563 assert(d->ref > 0);
564
565 if (--d->ref > 0)
566 return;
567
568
569 if (d->filtering)
570 dbus_connection_remove_filter(
571 d->connection,
572 filter_handler,
573 d);
574
575 if (d->registered)
576 dbus_connection_unregister_object_path(
577 d->connection,
578 d->object_path);
579
580 if (d->owning)
581 dbus_bus_release_name(
582 d->connection,
583 d->service_name,
584 NULL);
585
586 free(d->device_name);
587 free(d->application_name);
588 free(d->application_device_name);
589 free(d->service_name);
590 free(d->object_path);
591
592 if (d->connection)
593 dbus_connection_unref(d->connection);
594
595 free(d);
596 }
597
598 int rd_set_application_device_name(rd_device *d, const char *n) {
599 char *t;
600
601 if (!d)
602 return -EINVAL;
603
604 assert(d->ref > 0);
605
606 if (!(t = strdup(n)))
607 return -ENOMEM;
608
609 free(d->application_device_name);
610 d->application_device_name = t;
611 return 0;
612 }
613
614 void rd_set_userdata(rd_device *d, void *userdata) {
615
616 if (!d)
617 return;
618
619 assert(d->ref > 0);
620 d->userdata = userdata;
621 }
622
623 void* rd_get_userdata(rd_device *d) {
624
625 if (!d)
626 return NULL;
627
628 assert(d->ref > 0);
629
630 return d->userdata;
631 }