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