Merge branch 'master' of git://0pointer.de/pulseaudio into dbus-work
[platform/upstream/pulseaudio.git] / 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 }