when calculating average slave/master delta, use absolute value.
[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 #ifndef DBUS_TIMEOUT_USE_DEFAULT
37 #define DBUS_TIMEOUT_USE_DEFAULT (-1)
38 #endif
39
40 struct rd_device {
41         int ref;
42
43         char *device_name;
44         char *application_name;
45         char *application_device_name;
46         char *service_name;
47         char *object_path;
48         int32_t priority;
49
50         DBusConnection *connection;
51
52         unsigned owning:1;
53         unsigned registered:1;
54         unsigned filtering:1;
55         unsigned gave_up:1;
56
57         rd_request_cb_t request_cb;
58         void *userdata;
59 };
60
61 #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."
62 #define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/"
63
64 static const char introspection[] =
65         DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
66         "<node>"
67         " <!-- If you are looking for documentation make sure to check out\n"
68         "      http://git.0pointer.de/?p=reserve.git;a=blob;f=reserve.txt -->\n"
69         " <interface name=\"org.freedesktop.ReserveDevice1\">"
70         "  <method name=\"RequestRelease\">"
71         "   <arg name=\"priority\" type=\"i\" direction=\"in\"/>"
72         "   <arg name=\"result\" type=\"b\" direction=\"out\"/>"
73         "  </method>"
74         "  <property name=\"Priority\" type=\"i\" access=\"read\"/>"
75         "  <property name=\"ApplicationName\" type=\"s\" access=\"read\"/>"
76         "  <property name=\"ApplicationDeviceName\" type=\"s\" access=\"read\"/>"
77         " </interface>"
78         " <interface name=\"org.freedesktop.DBus.Properties\">"
79         "  <method name=\"Get\">"
80         "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"
81         "   <arg name=\"property\" direction=\"in\" type=\"s\"/>"
82         "   <arg name=\"value\" direction=\"out\" type=\"v\"/>"
83         "  </method>"
84         " </interface>"
85         " <interface name=\"org.freedesktop.DBus.Introspectable\">"
86         "  <method name=\"Introspect\">"
87         "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"
88         "  </method>"
89         " </interface>"
90         "</node>";
91
92 static dbus_bool_t add_variant(
93         DBusMessage *m,
94         int type,
95         const void *data) {
96
97         DBusMessageIter iter, sub;
98         char t[2];
99
100         t[0] = (char) type;
101         t[1] = 0;
102
103         dbus_message_iter_init_append(m, &iter);
104
105         if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, t, &sub))
106                 return FALSE;
107
108         if (!dbus_message_iter_append_basic(&sub, type, data))
109                 return FALSE;
110
111         if (!dbus_message_iter_close_container(&iter, &sub))
112                 return FALSE;
113
114         return TRUE;
115 }
116
117 static DBusHandlerResult object_handler(
118         DBusConnection *c,
119         DBusMessage *m,
120         void *userdata) {
121
122         rd_device *d;
123         DBusError error;
124         DBusMessage *reply = NULL;
125
126         dbus_error_init(&error);
127
128         d = userdata;
129         assert(d->ref >= 1);
130
131         if (dbus_message_is_method_call(
132                     m,
133                     "org.freedesktop.ReserveDevice1",
134                     "RequestRelease")) {
135
136                 int32_t priority;
137                 dbus_bool_t ret;
138
139                 if (!dbus_message_get_args(
140                             m,
141                             &error,
142                             DBUS_TYPE_INT32, &priority,
143                             DBUS_TYPE_INVALID))
144                         goto invalid;
145
146                 ret = FALSE;
147
148                 if (priority > d->priority && d->request_cb) {
149                         d->ref++;
150
151                         if (d->request_cb(d, 0) > 0) {
152                                 ret = TRUE;
153                                 d->gave_up = 1;
154                         }
155
156                         rd_release(d);
157                 }
158
159                 if (!(reply = dbus_message_new_method_return(m)))
160                         goto oom;
161
162                 if (!dbus_message_append_args(
163                             reply,
164                             DBUS_TYPE_BOOLEAN, &ret,
165                             DBUS_TYPE_INVALID))
166                         goto oom;
167
168                 if (!dbus_connection_send(c, reply, NULL))
169                         goto oom;
170
171                 dbus_message_unref(reply);
172
173                 return DBUS_HANDLER_RESULT_HANDLED;
174
175         } else if (dbus_message_is_method_call(
176                            m,
177                            "org.freedesktop.DBus.Properties",
178                            "Get")) {
179
180                 const char *interface, *property;
181
182                 if (!dbus_message_get_args(
183                             m,
184                             &error,
185                             DBUS_TYPE_STRING, &interface,
186                             DBUS_TYPE_STRING, &property,
187                             DBUS_TYPE_INVALID))
188                         goto invalid;
189
190                 if (strcmp(interface, "org.freedesktop.ReserveDevice1") == 0) {
191                         const char *empty = "";
192
193                         if (strcmp(property, "ApplicationName") == 0 && d->application_name) {
194                                 if (!(reply = dbus_message_new_method_return(m)))
195                                         goto oom;
196
197                                 if (!add_variant(
198                                             reply,
199                                             DBUS_TYPE_STRING,
200                                             d->application_name ? (const char * const *) &d->application_name : &empty))
201                                         goto oom;
202
203                         } else if (strcmp(property, "ApplicationDeviceName") == 0) {
204                                 if (!(reply = dbus_message_new_method_return(m)))
205                                         goto oom;
206
207                                 if (!add_variant(
208                                             reply,
209                                             DBUS_TYPE_STRING,
210                                             d->application_device_name ? (const char * const *) &d->application_device_name : &empty))
211                                         goto oom;
212
213                         } else if (strcmp(property, "Priority") == 0) {
214                                 if (!(reply = dbus_message_new_method_return(m)))
215                                         goto oom;
216
217                                 if (!add_variant(
218                                             reply,
219                                             DBUS_TYPE_INT32,
220                                             &d->priority))
221                                         goto oom;
222                         } else {
223                                 if (!(reply = dbus_message_new_error_printf(
224                                               m,
225                                               DBUS_ERROR_UNKNOWN_METHOD,
226                                               "Unknown property %s",
227                                               property)))
228                                         goto oom;
229                         }
230
231                         if (!dbus_connection_send(c, reply, NULL))
232                                 goto oom;
233
234                         dbus_message_unref(reply);
235
236                         return DBUS_HANDLER_RESULT_HANDLED;
237                 }
238
239         } else if (dbus_message_is_method_call(
240                            m,
241                            "org.freedesktop.DBus.Introspectable",
242                            "Introspect")) {
243                             const char *i = introspection;
244
245                 if (!(reply = dbus_message_new_method_return(m)))
246                         goto oom;
247
248                 if (!dbus_message_append_args(
249                             reply,
250                             DBUS_TYPE_STRING,
251                             &i,
252                             DBUS_TYPE_INVALID))
253                         goto oom;
254
255                 if (!dbus_connection_send(c, reply, NULL))
256                         goto oom;
257
258                 dbus_message_unref(reply);
259
260                 return DBUS_HANDLER_RESULT_HANDLED;
261         }
262
263         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
264
265 invalid:
266         if (reply)
267                 dbus_message_unref(reply);
268
269         if (!(reply = dbus_message_new_error(
270                       m,
271                       DBUS_ERROR_INVALID_ARGS,
272                       "Invalid arguments")))
273                 goto oom;
274
275         if (!dbus_connection_send(c, reply, NULL))
276                 goto oom;
277
278         dbus_message_unref(reply);
279
280         dbus_error_free(&error);
281
282         return DBUS_HANDLER_RESULT_HANDLED;
283
284 oom:
285         if (reply)
286                 dbus_message_unref(reply);
287
288         dbus_error_free(&error);
289
290         return DBUS_HANDLER_RESULT_NEED_MEMORY;
291 }
292
293 static DBusHandlerResult filter_handler(
294         DBusConnection *c,
295         DBusMessage *m,
296         void *userdata) {
297
298         rd_device *d;
299         DBusError error;
300         char *name_owner = NULL;
301
302         dbus_error_init(&error);
303
304         d = userdata;
305         assert(d->ref >= 1);
306
307         if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) {
308                 const char *name;
309
310                 if (!dbus_message_get_args(
311                             m,
312                             &error,
313                             DBUS_TYPE_STRING, &name,
314                             DBUS_TYPE_INVALID))
315                         goto invalid;
316
317                 if (strcmp(name, d->service_name) == 0 && d->owning) {
318                         /* Verify the actual owner of the name to avoid leaked NameLost
319                          * signals from previous reservations. The D-Bus daemon will send
320                          * all messages asynchronously in the correct order, but we could
321                          * potentially process them too late due to the pseudo-blocking
322                          * call mechanism used during both acquisition and release. This
323                          * can happen if we release the device and immediately after
324                          * reacquire it before NameLost is processed. */
325                         if (!d->gave_up) {
326                                 const char *un;
327
328                                 if ((un = dbus_bus_get_unique_name(c)) && rd_dbus_get_name_owner(c, d->service_name, &name_owner, &error) == 0)
329                                         if (name_owner && strcmp(name_owner, un) == 0)
330                                                 goto invalid; /* Name still owned by us */
331                         }
332
333                         d->owning = 0;
334
335                         if (!d->gave_up)  {
336                                 d->ref++;
337
338                                 if (d->request_cb)
339                                         d->request_cb(d, 1);
340                                 d->gave_up = 1;
341
342                                 rd_release(d);
343                         }
344
345                 }
346         }
347
348 invalid:
349         free(name_owner);
350         dbus_error_free(&error);
351
352         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
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 }
630
631 int rd_dbus_get_name_owner(
632         DBusConnection *connection,
633         const char *name,
634         char **name_owner,
635         DBusError *error) {
636
637         DBusMessage *msg, *reply;
638         int r;
639
640         *name_owner = NULL;
641
642         if (!(msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner"))) {
643                 r = -ENOMEM;
644                 goto fail;
645         }
646
647         if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)) {
648                 r = -ENOMEM;
649                 goto fail;
650         }
651
652         reply = dbus_connection_send_with_reply_and_block(connection, msg, DBUS_TIMEOUT_USE_DEFAULT, error);
653         dbus_message_unref(msg);
654         msg = NULL;
655
656         if (reply) {
657                 if (!dbus_message_get_args(reply, error, DBUS_TYPE_STRING, name_owner, DBUS_TYPE_INVALID)) {
658                         dbus_message_unref(reply);
659                         r = -EIO;
660                         goto fail;
661                 }
662
663                 *name_owner = strdup(*name_owner);
664                 dbus_message_unref(reply);
665
666                 if (!*name_owner) {
667                         r = -ENOMEM;
668                         goto fail;
669                 }
670
671         } else if (dbus_error_has_name(error, "org.freedesktop.DBus.Error.NameHasNoOwner"))
672                 dbus_error_free(error);
673         else {
674                 r = -EIO;
675                 goto fail;
676         }
677
678         return 0;
679
680 fail:
681         if (msg)
682                 dbus_message_unref(msg);
683
684         return r;
685 }