]> code.delx.au - pulseaudio/blob - src/modules/reserve.c
dbusiface-stream: Implement about a half of the Stream D-Bus interface.
[pulseaudio] / src / modules / reserve.c
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
32 #include "reserve.h"
33
34 struct rd_device {
35 int ref;
36
37 char *device_name;
38 char *application_name;
39 char *application_device_name;
40 char *service_name;
41 char *object_path;
42 int32_t priority;
43
44 DBusConnection *connection;
45
46 unsigned owning:1;
47 unsigned registered:1;
48 unsigned filtering:1;
49 unsigned gave_up:1;
50
51 rd_request_cb_t request_cb;
52 void *userdata;
53 };
54
55 #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."
56 #define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/"
57
58 static const char introspection[] =
59 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
60 "<node>"
61 " <!-- If you are looking for documentation make sure to check out\n"
62 " http://git.0pointer.de/?p=reserve.git;a=blob;f=reserve.txt -->\n"
63 " <interface name=\"org.freedesktop.ReserveDevice1\">"
64 " <method name=\"RequestRelease\">"
65 " <arg name=\"priority\" type=\"i\" direction=\"in\"/>"
66 " <arg name=\"result\" type=\"b\" direction=\"out\"/>"
67 " </method>"
68 " <property name=\"Priority\" type=\"i\" access=\"read\"/>"
69 " <property name=\"ApplicationName\" type=\"s\" access=\"read\"/>"
70 " <property name=\"ApplicationDeviceName\" type=\"s\" access=\"read\"/>"
71 " </interface>"
72 " <interface name=\"org.freedesktop.DBus.Properties\">"
73 " <method name=\"Get\">"
74 " <arg name=\"interface\" direction=\"in\" type=\"s\"/>"
75 " <arg name=\"property\" direction=\"in\" type=\"s\"/>"
76 " <arg name=\"value\" direction=\"out\" type=\"v\"/>"
77 " </method>"
78 " </interface>"
79 " <interface name=\"org.freedesktop.DBus.Introspectable\">"
80 " <method name=\"Introspect\">"
81 " <arg name=\"data\" type=\"s\" direction=\"out\"/>"
82 " </method>"
83 " </interface>"
84 "</node>";
85
86 static dbus_bool_t add_variant(
87 DBusMessage *m,
88 int type,
89 const void *data) {
90
91 DBusMessageIter iter, sub;
92 char t[2];
93
94 t[0] = (char) type;
95 t[1] = 0;
96
97 dbus_message_iter_init_append(m, &iter);
98
99 if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, t, &sub))
100 return FALSE;
101
102 if (!dbus_message_iter_append_basic(&sub, type, data))
103 return FALSE;
104
105 if (!dbus_message_iter_close_container(&iter, &sub))
106 return FALSE;
107
108 return TRUE;
109 }
110
111 static DBusHandlerResult object_handler(
112 DBusConnection *c,
113 DBusMessage *m,
114 void *userdata) {
115
116 rd_device *d;
117 DBusError error;
118 DBusMessage *reply = NULL;
119
120 dbus_error_init(&error);
121
122 d = userdata;
123 assert(d->ref >= 1);
124
125 if (dbus_message_is_method_call(
126 m,
127 "org.freedesktop.ReserveDevice1",
128 "RequestRelease")) {
129
130 int32_t priority;
131 dbus_bool_t ret;
132
133 if (!dbus_message_get_args(
134 m,
135 &error,
136 DBUS_TYPE_INT32, &priority,
137 DBUS_TYPE_INVALID))
138 goto invalid;
139
140 ret = FALSE;
141
142 if (priority > d->priority && d->request_cb) {
143 d->ref++;
144
145 if (d->request_cb(d, 0) > 0) {
146 ret = TRUE;
147 d->gave_up = 1;
148 }
149
150 rd_release(d);
151 }
152
153 if (!(reply = dbus_message_new_method_return(m)))
154 goto oom;
155
156 if (!dbus_message_append_args(
157 reply,
158 DBUS_TYPE_BOOLEAN, &ret,
159 DBUS_TYPE_INVALID))
160 goto oom;
161
162 if (!dbus_connection_send(c, reply, NULL))
163 goto oom;
164
165 dbus_message_unref(reply);
166
167 return DBUS_HANDLER_RESULT_HANDLED;
168
169 } else if (dbus_message_is_method_call(
170 m,
171 "org.freedesktop.DBus.Properties",
172 "Get")) {
173
174 const char *interface, *property;
175
176 if (!dbus_message_get_args(
177 m,
178 &error,
179 DBUS_TYPE_STRING, &interface,
180 DBUS_TYPE_STRING, &property,
181 DBUS_TYPE_INVALID))
182 goto invalid;
183
184 if (strcmp(interface, "org.freedesktop.ReserveDevice1") == 0) {
185 const char *empty = "";
186
187 if (strcmp(property, "ApplicationName") == 0 && d->application_name) {
188 if (!(reply = dbus_message_new_method_return(m)))
189 goto oom;
190
191 if (!add_variant(
192 reply,
193 DBUS_TYPE_STRING,
194 d->application_name ? (const char**) &d->application_name : &empty))
195 goto oom;
196
197 } else if (strcmp(property, "ApplicationDeviceName") == 0) {
198 if (!(reply = dbus_message_new_method_return(m)))
199 goto oom;
200
201 if (!add_variant(
202 reply,
203 DBUS_TYPE_STRING,
204 d->application_device_name ? (const char**) &d->application_device_name : &empty))
205 goto oom;
206
207 } else if (strcmp(property, "Priority") == 0) {
208 if (!(reply = dbus_message_new_method_return(m)))
209 goto oom;
210
211 if (!add_variant(
212 reply,
213 DBUS_TYPE_INT32,
214 &d->priority))
215 goto oom;
216 } else {
217 if (!(reply = dbus_message_new_error_printf(
218 m,
219 DBUS_ERROR_UNKNOWN_METHOD,
220 "Unknown property %s",
221 property)))
222 goto oom;
223 }
224
225 if (!dbus_connection_send(c, reply, NULL))
226 goto oom;
227
228 dbus_message_unref(reply);
229
230 return DBUS_HANDLER_RESULT_HANDLED;
231 }
232
233 } else if (dbus_message_is_method_call(
234 m,
235 "org.freedesktop.DBus.Introspectable",
236 "Introspect")) {
237 const char *i = introspection;
238
239 if (!(reply = dbus_message_new_method_return(m)))
240 goto oom;
241
242 if (!dbus_message_append_args(
243 reply,
244 DBUS_TYPE_STRING,
245 &i,
246 DBUS_TYPE_INVALID))
247 goto oom;
248
249 if (!dbus_connection_send(c, reply, NULL))
250 goto oom;
251
252 dbus_message_unref(reply);
253
254 return DBUS_HANDLER_RESULT_HANDLED;
255 }
256
257 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
258
259 invalid:
260 if (reply)
261 dbus_message_unref(reply);
262
263 if (!(reply = dbus_message_new_error(
264 m,
265 DBUS_ERROR_INVALID_ARGS,
266 "Invalid arguments")))
267 goto oom;
268
269 if (!dbus_connection_send(c, reply, NULL))
270 goto oom;
271
272 dbus_message_unref(reply);
273
274 dbus_error_free(&error);
275
276 return DBUS_HANDLER_RESULT_HANDLED;
277
278 oom:
279 if (reply)
280 dbus_message_unref(reply);
281
282 dbus_error_free(&error);
283
284 return DBUS_HANDLER_RESULT_NEED_MEMORY;
285 }
286
287 static DBusHandlerResult filter_handler(
288 DBusConnection *c,
289 DBusMessage *m,
290 void *userdata) {
291
292 DBusMessage *reply;
293 rd_device *d;
294 DBusError error;
295
296 dbus_error_init(&error);
297
298 d = userdata;
299 assert(d->ref >= 1);
300
301 if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) {
302 const char *name;
303
304 if (!dbus_message_get_args(
305 m,
306 &error,
307 DBUS_TYPE_STRING, &name,
308 DBUS_TYPE_INVALID))
309 goto invalid;
310
311 if (strcmp(name, d->service_name) == 0 && d->owning) {
312 d->owning = 0;
313
314 if (!d->gave_up) {
315 d->ref++;
316
317 if (d->request_cb)
318 d->request_cb(d, 1);
319 d->gave_up = 1;
320
321 rd_release(d);
322 }
323
324 return DBUS_HANDLER_RESULT_HANDLED;
325 }
326 }
327
328 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
329
330 invalid:
331 if (!(reply = dbus_message_new_error(
332 m,
333 DBUS_ERROR_INVALID_ARGS,
334 "Invalid arguments")))
335 goto oom;
336
337 if (!dbus_connection_send(c, reply, NULL))
338 goto oom;
339
340 dbus_message_unref(reply);
341
342 dbus_error_free(&error);
343
344 return DBUS_HANDLER_RESULT_HANDLED;
345
346 oom:
347 if (reply)
348 dbus_message_unref(reply);
349
350 dbus_error_free(&error);
351
352 return DBUS_HANDLER_RESULT_NEED_MEMORY;
353 }
354
355
356 static const struct DBusObjectPathVTable vtable ={
357 .message_function = object_handler
358 };
359
360 int rd_acquire(
361 rd_device **_d,
362 DBusConnection *connection,
363 const char *device_name,
364 const char *application_name,
365 int32_t priority,
366 rd_request_cb_t request_cb,
367 DBusError *error) {
368
369 rd_device *d = NULL;
370 int r, k;
371 DBusError _error;
372 DBusMessage *m = NULL, *reply = NULL;
373 dbus_bool_t good;
374
375 if (!error)
376 error = &_error;
377
378 dbus_error_init(error);
379
380 if (!_d)
381 return -EINVAL;
382
383 if (!connection)
384 return -EINVAL;
385
386 if (!device_name)
387 return -EINVAL;
388
389 if (!request_cb && priority != INT32_MAX)
390 return -EINVAL;
391
392 if (!(d = calloc(sizeof(rd_device), 1)))
393 return -ENOMEM;
394
395 d->ref = 1;
396
397 if (!(d->device_name = strdup(device_name))) {
398 r = -ENOMEM;
399 goto fail;
400 }
401
402 if (!(d->application_name = strdup(application_name))) {
403 r = -ENOMEM;
404 goto fail;
405 }
406
407 d->priority = priority;
408 d->connection = dbus_connection_ref(connection);
409 d->request_cb = request_cb;
410
411 if (!(d->service_name = malloc(sizeof(SERVICE_PREFIX) + strlen(device_name)))) {
412 r = -ENOMEM;
413 goto fail;
414 }
415 sprintf(d->service_name, SERVICE_PREFIX "%s", d->device_name);
416
417 if (!(d->object_path = malloc(sizeof(OBJECT_PREFIX) + strlen(device_name)))) {
418 r = -ENOMEM;
419 goto fail;
420 }
421 sprintf(d->object_path, OBJECT_PREFIX "%s", d->device_name);
422
423 if ((k = dbus_bus_request_name(
424 d->connection,
425 d->service_name,
426 DBUS_NAME_FLAG_DO_NOT_QUEUE|
427 (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0),
428 error)) < 0) {
429 r = -EIO;
430 goto fail;
431 }
432
433 if (k == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
434 goto success;
435
436 if (k != DBUS_REQUEST_NAME_REPLY_EXISTS) {
437 r = -EIO;
438 goto fail;
439 }
440
441 if (priority <= INT32_MIN) {
442 r = -EBUSY;
443 goto fail;
444 }
445
446 if (!(m = dbus_message_new_method_call(
447 d->service_name,
448 d->object_path,
449 "org.freedesktop.ReserveDevice1",
450 "RequestRelease"))) {
451 r = -ENOMEM;
452 goto fail;
453 }
454
455 if (!dbus_message_append_args(
456 m,
457 DBUS_TYPE_INT32, &d->priority,
458 DBUS_TYPE_INVALID)) {
459 r = -ENOMEM;
460 goto fail;
461 }
462
463 if (!(reply = dbus_connection_send_with_reply_and_block(
464 d->connection,
465 m,
466 5000, /* 5s */
467 error))) {
468
469 if (dbus_error_has_name(error, DBUS_ERROR_TIMED_OUT) ||
470 dbus_error_has_name(error, DBUS_ERROR_UNKNOWN_METHOD) ||
471 dbus_error_has_name(error, DBUS_ERROR_NO_REPLY)) {
472 /* This must be treated as denied. */
473 r = -EBUSY;
474 goto fail;
475 }
476
477 r = -EIO;
478 goto fail;
479 }
480
481 if (!dbus_message_get_args(
482 reply,
483 error,
484 DBUS_TYPE_BOOLEAN, &good,
485 DBUS_TYPE_INVALID)) {
486 r = -EIO;
487 goto fail;
488 }
489
490 if (!good) {
491 r = -EBUSY;
492 goto fail;
493 }
494
495 if ((k = dbus_bus_request_name(
496 d->connection,
497 d->service_name,
498 DBUS_NAME_FLAG_DO_NOT_QUEUE|
499 (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0)|
500 DBUS_NAME_FLAG_REPLACE_EXISTING,
501 error)) < 0) {
502 r = -EIO;
503 goto fail;
504 }
505
506 if (k != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
507 r = -EIO;
508 goto fail;
509 }
510
511 success:
512 d->owning = 1;
513
514 if (!(dbus_connection_register_object_path(
515 d->connection,
516 d->object_path,
517 &vtable,
518 d))) {
519 r = -ENOMEM;
520 goto fail;
521 }
522
523 d->registered = 1;
524
525 if (!dbus_connection_add_filter(
526 d->connection,
527 filter_handler,
528 d,
529 NULL)) {
530 r = -ENOMEM;
531 goto fail;
532 }
533
534 d->filtering = 1;
535
536 *_d = d;
537 return 0;
538
539 fail:
540 if (m)
541 dbus_message_unref(m);
542
543 if (reply)
544 dbus_message_unref(reply);
545
546 if (&_error == error)
547 dbus_error_free(&_error);
548
549 if (d)
550 rd_release(d);
551
552 return r;
553 }
554
555 void rd_release(
556 rd_device *d) {
557
558 if (!d)
559 return;
560
561 assert(d->ref > 0);
562
563 if (--d->ref > 0)
564 return;
565
566
567 if (d->filtering)
568 dbus_connection_remove_filter(
569 d->connection,
570 filter_handler,
571 d);
572
573 if (d->registered)
574 dbus_connection_unregister_object_path(
575 d->connection,
576 d->object_path);
577
578 if (d->owning)
579 dbus_bus_release_name(
580 d->connection,
581 d->service_name,
582 NULL);
583
584 free(d->device_name);
585 free(d->application_name);
586 free(d->application_device_name);
587 free(d->service_name);
588 free(d->object_path);
589
590 if (d->connection)
591 dbus_connection_unref(d->connection);
592
593 free(d);
594 }
595
596 int rd_set_application_device_name(rd_device *d, const char *n) {
597 char *t;
598
599 if (!d)
600 return -EINVAL;
601
602 assert(d->ref > 0);
603
604 if (!(t = strdup(n)))
605 return -ENOMEM;
606
607 free(d->application_device_name);
608 d->application_device_name = t;
609 return 0;
610 }
611
612 void rd_set_userdata(rd_device *d, void *userdata) {
613
614 if (!d)
615 return;
616
617 assert(d->ref > 0);
618 d->userdata = userdata;
619 }
620
621 void* rd_get_userdata(rd_device *d) {
622
623 if (!d)
624 return NULL;
625
626 assert(d->ref > 0);
627
628 return d->userdata;
629 }