Merge commit 'origin/master-tx'
[profile/ivi/pulseaudio-panda.git] / 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 }