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