reserve.c 14.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/***
  Copyright 2009 Lennart Poettering

  Permission is hereby granted, free of charge, to any person
  obtaining a copy of this software and associated documentation files
  (the "Software"), to deal in the Software without restriction,
  including without limitation the rights to use, copy, modify, merge,
  publish, distribute, sublicense, and/or sell copies of the Software,
  and to permit persons to whom the Software is furnished to do so,
  subject to the following conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
***/

#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
31
#include <stdint.h>
32
33

#include "reserve.h"
34
35
36
37
38
#include "jack/control.h"

#define RESERVE_ERROR_NO_MEMORY            "org.freedesktop.ReserveDevice1.Error.NoMemory"
#define RESERVE_ERROR_PROTOCOL_VIOLATION   "org.freedesktop.ReserveDevice1.Error.Protocol"
#define RESERVE_ERROR_RELEASE_DENIED       "org.freedesktop.ReserveDevice1.Error.ReleaseDenied"
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

struct rd_device {
	int ref;

	char *device_name;
	char *application_name;
	char *application_device_name;
	char *service_name;
	char *object_path;
	int32_t priority;

	DBusConnection *connection;

	int owning:1;
	int registered:1;
	int filtering:1;
	int gave_up:1;

	rd_request_cb_t request_cb;
	void *userdata;
};


#define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."
#define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/"

static const char introspection[] =
	DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
	"<node>"
	" <!-- If you are looking for documentation make sure to check out\n"
	"      http://git.0pointer.de/?p=reserve.git;a=blob;f=reserve.txt -->\n"
	" <interface name=\"org.freedesktop.ReserveDevice1\">"
	"  <method name=\"RequestRelease\">"
	"   <arg name=\"priority\" type=\"i\" direction=\"in\"/>"
	"   <arg name=\"result\" type=\"b\" direction=\"out\"/>"
	"  </method>"
	"  <property name=\"Priority\" type=\"i\" access=\"read\"/>"
	"  <property name=\"ApplicationName\" type=\"s\" access=\"read\"/>"
	"  <property name=\"ApplicationDeviceName\" type=\"s\" access=\"read\"/>"
	" </interface>"
	" <interface name=\"org.freedesktop.DBus.Properties\">"
	"  <method name=\"Get\">"
	"   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"
	"   <arg name=\"property\" direction=\"in\" type=\"s\"/>"
	"   <arg name=\"value\" direction=\"out\" type=\"v\"/>"
	"  </method>"
	" </interface>"
	" <interface name=\"org.freedesktop.DBus.Introspectable\">"
	"  <method name=\"Introspect\">"
	"   <arg name=\"data\" type=\"s\" direction=\"out\"/>"
	"  </method>"
	" </interface>"
	"</node>";

static dbus_bool_t add_variant(
	DBusMessage *m,
	int type,
	const void *data) {

	DBusMessageIter iter, sub;
	char t[2];

	t[0] = (char) type;
	t[1] = 0;

	dbus_message_iter_init_append(m, &iter);

	if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, t, &sub))
		return FALSE;

	if (!dbus_message_iter_append_basic(&sub, type, data))
		return FALSE;

	if (!dbus_message_iter_close_container(&iter, &sub))
		return FALSE;

	return TRUE;
}

static DBusHandlerResult object_handler(
	DBusConnection *c,
	DBusMessage *m,
	void *userdata) {

	rd_device *d;
	DBusError error;
	DBusMessage *reply = NULL;

	dbus_error_init(&error);

129
	d = (rd_device*)userdata;
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
	assert(d->ref >= 1);

	if (dbus_message_is_method_call(
		    m,
		    "org.freedesktop.ReserveDevice1",
		    "RequestRelease")) {

		int32_t priority;
		dbus_bool_t ret;

		if (!dbus_message_get_args(
			    m,
			    &error,
			    DBUS_TYPE_INT32, &priority,
			    DBUS_TYPE_INVALID))
			goto invalid;

		ret = FALSE;

		if (priority > d->priority && d->request_cb) {
			d->ref++;

			if (d->request_cb(d, 0) > 0) {
				ret = TRUE;
				d->gave_up = 1;
			}

			rd_release(d);
		}

		if (!(reply = dbus_message_new_method_return(m)))
			goto oom;

		if (!dbus_message_append_args(
			    reply,
			    DBUS_TYPE_BOOLEAN, &ret,
			    DBUS_TYPE_INVALID))
			goto oom;

		if (!dbus_connection_send(c, reply, NULL))
			goto oom;

		dbus_message_unref(reply);

		return DBUS_HANDLER_RESULT_HANDLED;

	} else if (dbus_message_is_method_call(
			   m,
			   "org.freedesktop.DBus.Properties",
			   "Get")) {

		const char *interface, *property;

		if (!dbus_message_get_args(
			    m,
			    &error,
			    DBUS_TYPE_STRING, &interface,
			    DBUS_TYPE_STRING, &property,
			    DBUS_TYPE_INVALID))
			goto invalid;

		if (strcmp(interface, "org.freedesktop.ReserveDevice1") == 0) {
			const char *empty = "";

			if (strcmp(property, "ApplicationName") == 0 && d->application_name) {
				if (!(reply = dbus_message_new_method_return(m)))
					goto oom;

				if (!add_variant(
					    reply,
					    DBUS_TYPE_STRING,
					    d->application_name ? (const char**) &d->application_name : &empty))
					goto oom;

			} else if (strcmp(property, "ApplicationDeviceName") == 0) {
				if (!(reply = dbus_message_new_method_return(m)))
					goto oom;

				if (!add_variant(
					    reply,
					    DBUS_TYPE_STRING,
					    d->application_device_name ? (const char**) &d->application_device_name : &empty))
					goto oom;

			} else if (strcmp(property, "Priority") == 0) {
				if (!(reply = dbus_message_new_method_return(m)))
					goto oom;

				if (!add_variant(
					    reply,
					    DBUS_TYPE_INT32,
					    &d->priority))
					goto oom;
			} else {
				if (!(reply = dbus_message_new_error_printf(
					      m,
					      DBUS_ERROR_UNKNOWN_METHOD,
					      "Unknown property %s",
					      property)))
					goto oom;
			}

			if (!dbus_connection_send(c, reply, NULL))
				goto oom;

			dbus_message_unref(reply);

			return DBUS_HANDLER_RESULT_HANDLED;
		}

	} else if (dbus_message_is_method_call(
			   m,
			   "org.freedesktop.DBus.Introspectable",
			   "Introspect")) {
			    const char *i = introspection;

		if (!(reply = dbus_message_new_method_return(m)))
			goto oom;

		if (!dbus_message_append_args(
			    reply,
			    DBUS_TYPE_STRING,
			    &i,
			    DBUS_TYPE_INVALID))
			goto oom;

		if (!dbus_connection_send(c, reply, NULL))
			goto oom;

		dbus_message_unref(reply);

		return DBUS_HANDLER_RESULT_HANDLED;
	}

	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

invalid:
	if (reply)
		dbus_message_unref(reply);

	if (!(reply = dbus_message_new_error(
		      m,
		      DBUS_ERROR_INVALID_ARGS,
		      "Invalid arguments")))
		goto oom;

	if (!dbus_connection_send(c, reply, NULL))
		goto oom;

	dbus_message_unref(reply);

	dbus_error_free(&error);

	return DBUS_HANDLER_RESULT_HANDLED;

oom:
	if (reply)
		dbus_message_unref(reply);

	dbus_error_free(&error);

	return DBUS_HANDLER_RESULT_NEED_MEMORY;
}

static DBusHandlerResult filter_handler(
	DBusConnection *c,
	DBusMessage *m,
	void *userdata) {

	DBusMessage *reply;
	rd_device *d;
	DBusError error;

	dbus_error_init(&error);

305
	d = (rd_device*)userdata;
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360

	if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) {
		const char *name;

		if (!dbus_message_get_args(
			    m,
			    &error,
			    DBUS_TYPE_STRING, &name,
			    DBUS_TYPE_INVALID))
			goto invalid;

		if (strcmp(name, d->service_name) == 0 && d->owning) {
			d->owning = 0;

			if (!d->gave_up)  {
				d->ref++;

				if (d->request_cb)
					d->request_cb(d, 1);
				d->gave_up = 1;

				rd_release(d);
			}

			return DBUS_HANDLER_RESULT_HANDLED;
		}
	}

	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

invalid:
	if (!(reply = dbus_message_new_error(
		      m,
		      DBUS_ERROR_INVALID_ARGS,
		      "Invalid arguments")))
		goto oom;

	if (!dbus_connection_send(c, reply, NULL))
		goto oom;

	dbus_message_unref(reply);

	dbus_error_free(&error);

	return DBUS_HANDLER_RESULT_HANDLED;

oom:
	if (reply)
		dbus_message_unref(reply);

	dbus_error_free(&error);

	return DBUS_HANDLER_RESULT_NEED_MEMORY;
}

361
static DBusObjectPathVTable vtable;
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376

int rd_acquire(
	rd_device **_d,
	DBusConnection *connection,
	const char *device_name,
	const char *application_name,
	int32_t priority,
	rd_request_cb_t request_cb,
	DBusError *error) {

	rd_device *d = NULL;
	int r, k;
	DBusError _error;
	DBusMessage *m = NULL, *reply = NULL;
	dbus_bool_t good;
sletz's avatar
sletz committed
377
    vtable.message_function = object_handler;
378

379
	if (!error) {
380
		error = &_error;
381
382
		dbus_error_init(error);
	}
383

384
385
386
387
388
	if (!_d) {
		assert(0);
		r = -EINVAL;
		goto fail;
	}
389

390
391
392
393
394
	if (!connection) {
		assert(0);
		r = -EINVAL;
		goto fail;
	}
395

396
397
398
399
400
	if (!device_name) {
		assert(0);
		r = -EINVAL;
		goto fail;
	}
401

402
403
404
405
406
	if (!request_cb && priority != INT32_MAX) {
		assert(0);
		r = -EINVAL;
		goto fail;
	}
407

408
409
410
411
412
	if (!(d = (rd_device *)calloc(sizeof(rd_device), 1))) {
		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for rd_device struct");
		r = -ENOMEM;
		goto fail;
	}
413
414
415
416

	d->ref = 1;

	if (!(d->device_name = strdup(device_name))) {
417
		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot duplicate device name string");
418
419
420
421
422
		r = -ENOMEM;
		goto fail;
	}

	if (!(d->application_name = strdup(application_name))) {
423
		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot duplicate application name string");
424
425
426
427
428
429
430
431
		r = -ENOMEM;
		goto fail;
	}

	d->priority = priority;
	d->connection = dbus_connection_ref(connection);
	d->request_cb = request_cb;

432
	if (!(d->service_name = (char*)malloc(sizeof(SERVICE_PREFIX) + strlen(device_name)))) {
433
		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for service name string");
434
435
436
437
438
		r = -ENOMEM;
		goto fail;
	}
	sprintf(d->service_name, SERVICE_PREFIX "%s", d->device_name);

439
	if (!(d->object_path = (char*)malloc(sizeof(OBJECT_PREFIX) + strlen(device_name)))) {
440
		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for object path string");
441
442
443
444
445
446
447
448
449
450
451
		r = -ENOMEM;
		goto fail;
	}
	sprintf(d->object_path, OBJECT_PREFIX "%s", d->device_name);

	if ((k = dbus_bus_request_name(
		     d->connection,
		     d->service_name,
		     DBUS_NAME_FLAG_DO_NOT_QUEUE|
		     (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0),
		     error)) < 0) {
452
		jack_error("dbus_bus_request_name() failed. (1)");
453
454
455
456
		r = -EIO;
		goto fail;
	}

457
458
459
	switch (k) {
	case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
	case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
460
		goto success;
461
462
463
464
465
466
	case DBUS_REQUEST_NAME_REPLY_EXISTS:
		break;
	case DBUS_REQUEST_NAME_REPLY_IN_QUEUE : /* DBUS_NAME_FLAG_DO_NOT_QUEUE was specified */
	default:								/* unknown reply returned */
		jack_error("request name reply with unexpected value %d.", k);
		assert(0);
467
468
469
470
471
472
		r = -EIO;
		goto fail;
	}

	if (priority <= INT32_MIN) {
		r = -EBUSY;
473
		dbus_set_error(error, RESERVE_ERROR_RELEASE_DENIED, "Device reservation request with priority %"PRIi32" denied for \"%s\"", priority, device_name);
474
475
476
477
478
479
480
481
		goto fail;
	}

	if (!(m = dbus_message_new_method_call(
		      d->service_name,
		      d->object_path,
		      "org.freedesktop.ReserveDevice1",
		      "RequestRelease"))) {
482
		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for RequestRelease method call");
483
484
485
486
487
488
489
490
		r = -ENOMEM;
		goto fail;
	}

	if (!dbus_message_append_args(
		    m,
		    DBUS_TYPE_INT32, &d->priority,
		    DBUS_TYPE_INVALID)) {
491
		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot append args for RequestRelease method call");
492
493
494
495
496
497
498
499
500
501
502
503
504
505
		r = -ENOMEM;
		goto fail;
	}

	if (!(reply = dbus_connection_send_with_reply_and_block(
		      d->connection,
		      m,
		      5000, /* 5s */
		      error))) {

		if (dbus_error_has_name(error, DBUS_ERROR_TIMED_OUT) ||
		    dbus_error_has_name(error, DBUS_ERROR_UNKNOWN_METHOD) ||
		    dbus_error_has_name(error, DBUS_ERROR_NO_REPLY)) {
			/* This must be treated as denied. */
506
			jack_info("Device reservation request with priority %"PRIi32" denied for \"%s\": %s (%s)", priority, device_name, error->name, error->message);
507
508
509
510
			r = -EBUSY;
			goto fail;
		}

511
		jack_error("dbus_connection_send_with_reply_and_block(RequestRelease) failed.");
512
513
514
515
516
517
518
519
520
		r = -EIO;
		goto fail;
	}

	if (!dbus_message_get_args(
		    reply,
		    error,
		    DBUS_TYPE_BOOLEAN, &good,
		    DBUS_TYPE_INVALID)) {
521
		jack_error("RequestRelease() reply is invalid.");
522
523
524
525
526
		r = -EIO;
		goto fail;
	}

	if (!good) {
527
        dbus_set_error(error, RESERVE_ERROR_RELEASE_DENIED, "Device reservation request with priority %"PRIi32" denied for \"%s\" via RequestRelease()", priority, device_name);
528
529
530
531
532
533
534
535
536
537
538
		r = -EBUSY;
		goto fail;
	}

	if ((k = dbus_bus_request_name(
		     d->connection,
		     d->service_name,
		     DBUS_NAME_FLAG_DO_NOT_QUEUE|
		     (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0)|
		     DBUS_NAME_FLAG_REPLACE_EXISTING,
		     error)) < 0) {
539
		jack_error("dbus_bus_request_name() failed. (2)");
540
541
542
543
544
		r = -EIO;
		goto fail;
	}

	if (k != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
545
546
        /* this is racy, another contender may have acquired the device */
		dbus_set_error(error, RESERVE_ERROR_PROTOCOL_VIOLATION, "request name reply is not DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER but %d.", k);
547
548
549
550
551
552
553
		r = -EIO;
		goto fail;
	}

success:
	d->owning = 1;

554
	if (!(dbus_connection_try_register_object_path(
555
556
557
		      d->connection,
		      d->object_path,
		      &vtable,
558
559
560
		      d,
		      error))) {
		jack_error("cannot register object path \"%s\": %s", d->object_path, error->message);
561
562
563
564
565
566
567
568
569
570
571
		r = -ENOMEM;
		goto fail;
	}

	d->registered = 1;

	if (!dbus_connection_add_filter(
		    d->connection,
		    filter_handler,
		    d,
		    NULL)) {
572
		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot add filter");
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
		r = -ENOMEM;
		goto fail;
	}

	d->filtering = 1;

	*_d = d;
	return 0;

fail:
	if (m)
		dbus_message_unref(m);

	if (reply)
		dbus_message_unref(reply);

	if (&_error == error)
		dbus_error_free(&_error);

	if (d)
		rd_release(d);

	return r;
}

void rd_release(
	rd_device *d) {

	if (!d)
		return;

	assert(d->ref > 0);

	if (--d->ref)
		return;


	if (d->filtering)
		dbus_connection_remove_filter(
			d->connection,
			filter_handler,
			d);

	if (d->registered)
		dbus_connection_unregister_object_path(
			d->connection,
			d->object_path);

	if (d->owning) {
		DBusError error;
		dbus_error_init(&error);

		dbus_bus_release_name(
			d->connection,
			d->service_name,
			&error);

		dbus_error_free(&error);
	}

	free(d->device_name);
	free(d->application_name);
	free(d->application_device_name);
	free(d->service_name);
	free(d->object_path);

	if (d->connection)
		dbus_connection_unref(d->connection);

	free(d);
}

int rd_set_application_device_name(rd_device *d, const char *n) {
	char *t;

	if (!d)
		return -EINVAL;

	assert(d->ref > 0);

	if (!(t = strdup(n)))
		return -ENOMEM;

	free(d->application_device_name);
	d->application_device_name = t;
	return 0;
}

void rd_set_userdata(rd_device *d, void *userdata) {

	if (!d)
		return;

	assert(d->ref > 0);
	d->userdata = userdata;
}

void* rd_get_userdata(rd_device *d) {

	if (!d)
		return NULL;

	assert(d->ref > 0);

	return d->userdata;
}