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 #include <stdint.h>
32 
33 #include "reserve.h"
34 #include "jack/control.h"
35 
36 #define RESERVE_ERROR_NO_MEMORY            "org.freedesktop.ReserveDevice1.Error.NoMemory"
37 #define RESERVE_ERROR_PROTOCOL_VIOLATION   "org.freedesktop.ReserveDevice1.Error.Protocol"
38 #define RESERVE_ERROR_RELEASE_DENIED       "org.freedesktop.ReserveDevice1.Error.ReleaseDenied"
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 	int owning:1;
53 	int registered:1;
54 	int filtering:1;
55 	int gave_up:1;
56 
57 	rd_request_cb_t request_cb;
58 	void *userdata;
59 };
60 
61 
62 #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."
63 #define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/"
64 
65 static const char introspection[] =
66 	DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
67 	"<node>"
68 	" <!-- If you are looking for documentation make sure to check out\n"
69 	"      http://git.0pointer.de/?p=reserve.git;a=blob;f=reserve.txt -->\n"
70 	" <interface name=\"org.freedesktop.ReserveDevice1\">"
71 	"  <method name=\"RequestRelease\">"
72 	"   <arg name=\"priority\" type=\"i\" direction=\"in\"/>"
73 	"   <arg name=\"result\" type=\"b\" direction=\"out\"/>"
74 	"  </method>"
75 	"  <property name=\"Priority\" type=\"i\" access=\"read\"/>"
76 	"  <property name=\"ApplicationName\" type=\"s\" access=\"read\"/>"
77 	"  <property name=\"ApplicationDeviceName\" type=\"s\" access=\"read\"/>"
78 	" </interface>"
79 	" <interface name=\"org.freedesktop.DBus.Properties\">"
80 	"  <method name=\"Get\">"
81 	"   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"
82 	"   <arg name=\"property\" direction=\"in\" type=\"s\"/>"
83 	"   <arg name=\"value\" direction=\"out\" type=\"v\"/>"
84 	"  </method>"
85 	" </interface>"
86 	" <interface name=\"org.freedesktop.DBus.Introspectable\">"
87 	"  <method name=\"Introspect\">"
88 	"   <arg name=\"data\" type=\"s\" direction=\"out\"/>"
89 	"  </method>"
90 	" </interface>"
91 	"</node>";
92 
add_variant(DBusMessage * m,int type,const void * data)93 static dbus_bool_t add_variant(
94 	DBusMessage *m,
95 	int type,
96 	const void *data) {
97 
98 	DBusMessageIter iter, sub;
99 	char t[2];
100 
101 	t[0] = (char) type;
102 	t[1] = 0;
103 
104 	dbus_message_iter_init_append(m, &iter);
105 
106 	if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, t, &sub))
107 		return FALSE;
108 
109 	if (!dbus_message_iter_append_basic(&sub, type, data))
110 		return FALSE;
111 
112 	if (!dbus_message_iter_close_container(&iter, &sub))
113 		return FALSE;
114 
115 	return TRUE;
116 }
117 
object_handler(DBusConnection * c,DBusMessage * m,void * userdata)118 static DBusHandlerResult object_handler(
119 	DBusConnection *c,
120 	DBusMessage *m,
121 	void *userdata) {
122 
123 	rd_device *d;
124 	DBusError error;
125 	DBusMessage *reply = NULL;
126 
127 	dbus_error_init(&error);
128 
129 	d = (rd_device*)userdata;
130 	assert(d->ref >= 1);
131 
132 	if (dbus_message_is_method_call(
133 		    m,
134 		    "org.freedesktop.ReserveDevice1",
135 		    "RequestRelease")) {
136 
137 		int32_t priority;
138 		dbus_bool_t ret;
139 
140 		if (!dbus_message_get_args(
141 			    m,
142 			    &error,
143 			    DBUS_TYPE_INT32, &priority,
144 			    DBUS_TYPE_INVALID))
145 			goto invalid;
146 
147 		ret = FALSE;
148 
149 		if (priority > d->priority && d->request_cb) {
150 			d->ref++;
151 
152 			if (d->request_cb(d, 0) > 0) {
153 				ret = TRUE;
154 				d->gave_up = 1;
155 			}
156 
157 			rd_release(d);
158 		}
159 
160 		if (!(reply = dbus_message_new_method_return(m)))
161 			goto oom;
162 
163 		if (!dbus_message_append_args(
164 			    reply,
165 			    DBUS_TYPE_BOOLEAN, &ret,
166 			    DBUS_TYPE_INVALID))
167 			goto oom;
168 
169 		if (!dbus_connection_send(c, reply, NULL))
170 			goto oom;
171 
172 		dbus_message_unref(reply);
173 
174 		return DBUS_HANDLER_RESULT_HANDLED;
175 
176 	} else if (dbus_message_is_method_call(
177 			   m,
178 			   "org.freedesktop.DBus.Properties",
179 			   "Get")) {
180 
181 		const char *interface, *property;
182 
183 		if (!dbus_message_get_args(
184 			    m,
185 			    &error,
186 			    DBUS_TYPE_STRING, &interface,
187 			    DBUS_TYPE_STRING, &property,
188 			    DBUS_TYPE_INVALID))
189 			goto invalid;
190 
191 		if (strcmp(interface, "org.freedesktop.ReserveDevice1") == 0) {
192 			const char *empty = "";
193 
194 			if (strcmp(property, "ApplicationName") == 0 && d->application_name) {
195 				if (!(reply = dbus_message_new_method_return(m)))
196 					goto oom;
197 
198 				if (!add_variant(
199 					    reply,
200 					    DBUS_TYPE_STRING,
201 					    d->application_name ? (const char**) &d->application_name : &empty))
202 					goto oom;
203 
204 			} else if (strcmp(property, "ApplicationDeviceName") == 0) {
205 				if (!(reply = dbus_message_new_method_return(m)))
206 					goto oom;
207 
208 				if (!add_variant(
209 					    reply,
210 					    DBUS_TYPE_STRING,
211 					    d->application_device_name ? (const char**) &d->application_device_name : &empty))
212 					goto oom;
213 
214 			} else if (strcmp(property, "Priority") == 0) {
215 				if (!(reply = dbus_message_new_method_return(m)))
216 					goto oom;
217 
218 				if (!add_variant(
219 					    reply,
220 					    DBUS_TYPE_INT32,
221 					    &d->priority))
222 					goto oom;
223 			} else {
224 				if (!(reply = dbus_message_new_error_printf(
225 					      m,
226 					      DBUS_ERROR_UNKNOWN_METHOD,
227 					      "Unknown property %s",
228 					      property)))
229 					goto oom;
230 			}
231 
232 			if (!dbus_connection_send(c, reply, NULL))
233 				goto oom;
234 
235 			dbus_message_unref(reply);
236 
237 			return DBUS_HANDLER_RESULT_HANDLED;
238 		}
239 
240 	} else if (dbus_message_is_method_call(
241 			   m,
242 			   "org.freedesktop.DBus.Introspectable",
243 			   "Introspect")) {
244 			    const char *i = introspection;
245 
246 		if (!(reply = dbus_message_new_method_return(m)))
247 			goto oom;
248 
249 		if (!dbus_message_append_args(
250 			    reply,
251 			    DBUS_TYPE_STRING,
252 			    &i,
253 			    DBUS_TYPE_INVALID))
254 			goto oom;
255 
256 		if (!dbus_connection_send(c, reply, NULL))
257 			goto oom;
258 
259 		dbus_message_unref(reply);
260 
261 		return DBUS_HANDLER_RESULT_HANDLED;
262 	}
263 
264 	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
265 
266 invalid:
267 	if (reply)
268 		dbus_message_unref(reply);
269 
270 	if (!(reply = dbus_message_new_error(
271 		      m,
272 		      DBUS_ERROR_INVALID_ARGS,
273 		      "Invalid arguments")))
274 		goto oom;
275 
276 	if (!dbus_connection_send(c, reply, NULL))
277 		goto oom;
278 
279 	dbus_message_unref(reply);
280 
281 	dbus_error_free(&error);
282 
283 	return DBUS_HANDLER_RESULT_HANDLED;
284 
285 oom:
286 	if (reply)
287 		dbus_message_unref(reply);
288 
289 	dbus_error_free(&error);
290 
291 	return DBUS_HANDLER_RESULT_NEED_MEMORY;
292 }
293 
filter_handler(DBusConnection * c,DBusMessage * m,void * userdata)294 static DBusHandlerResult filter_handler(
295 	DBusConnection *c,
296 	DBusMessage *m,
297 	void *userdata) {
298 
299 	DBusMessage *reply;
300 	rd_device *d;
301 	DBusError error;
302 
303 	dbus_error_init(&error);
304 
305 	d = (rd_device*)userdata;
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 			d->owning = 0;
319 
320 			if (!d->gave_up)  {
321 				d->ref++;
322 
323 				if (d->request_cb)
324 					d->request_cb(d, 1);
325 				d->gave_up = 1;
326 
327 				rd_release(d);
328 			}
329 
330 			return DBUS_HANDLER_RESULT_HANDLED;
331 		}
332 	}
333 
334 	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
335 
336 invalid:
337 	if (!(reply = dbus_message_new_error(
338 		      m,
339 		      DBUS_ERROR_INVALID_ARGS,
340 		      "Invalid arguments")))
341 		goto oom;
342 
343 	if (!dbus_connection_send(c, reply, NULL))
344 		goto oom;
345 
346 	dbus_message_unref(reply);
347 
348 	dbus_error_free(&error);
349 
350 	return DBUS_HANDLER_RESULT_HANDLED;
351 
352 oom:
353 	if (reply)
354 		dbus_message_unref(reply);
355 
356 	dbus_error_free(&error);
357 
358 	return DBUS_HANDLER_RESULT_NEED_MEMORY;
359 }
360 
361 static DBusObjectPathVTable vtable;
362 
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)363 int rd_acquire(
364 	rd_device **_d,
365 	DBusConnection *connection,
366 	const char *device_name,
367 	const char *application_name,
368 	int32_t priority,
369 	rd_request_cb_t request_cb,
370 	DBusError *error) {
371 
372 	rd_device *d = NULL;
373 	int r, k;
374 	DBusError _error;
375 	DBusMessage *m = NULL, *reply = NULL;
376 	dbus_bool_t good;
377     vtable.message_function = object_handler;
378 
379 	if (!error) {
380 		error = &_error;
381 		dbus_error_init(error);
382 	}
383 
384 	if (!_d) {
385 		assert(0);
386 		r = -EINVAL;
387 		goto fail;
388 	}
389 
390 	if (!connection) {
391 		assert(0);
392 		r = -EINVAL;
393 		goto fail;
394 	}
395 
396 	if (!device_name) {
397 		assert(0);
398 		r = -EINVAL;
399 		goto fail;
400 	}
401 
402 	if (!request_cb && priority != INT32_MAX) {
403 		assert(0);
404 		r = -EINVAL;
405 		goto fail;
406 	}
407 
408 	if (!(d = (rd_device *)calloc(sizeof(rd_device), 1))) {
409 		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for rd_device struct");
410 		r = -ENOMEM;
411 		goto fail;
412 	}
413 
414 	d->ref = 1;
415 
416 	if (!(d->device_name = strdup(device_name))) {
417 		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot duplicate device name string");
418 		r = -ENOMEM;
419 		goto fail;
420 	}
421 
422 	if (!(d->application_name = strdup(application_name))) {
423 		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot duplicate application name string");
424 		r = -ENOMEM;
425 		goto fail;
426 	}
427 
428 	d->priority = priority;
429 	d->connection = dbus_connection_ref(connection);
430 	d->request_cb = request_cb;
431 
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 		r = -ENOMEM;
435 		goto fail;
436 	}
437 	sprintf(d->service_name, SERVICE_PREFIX "%s", d->device_name);
438 
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 		r = -ENOMEM;
442 		goto fail;
443 	}
444 	sprintf(d->object_path, OBJECT_PREFIX "%s", d->device_name);
445 
446 	if ((k = dbus_bus_request_name(
447 		     d->connection,
448 		     d->service_name,
449 		     DBUS_NAME_FLAG_DO_NOT_QUEUE|
450 		     (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0),
451 		     error)) < 0) {
452 		jack_error("dbus_bus_request_name() failed. (1)");
453 		r = -EIO;
454 		goto fail;
455 	}
456 
457 	switch (k) {
458 	case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
459 	case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
460 		goto success;
461 	case DBUS_REQUEST_NAME_REPLY_EXISTS:
462 		break;
463 	case DBUS_REQUEST_NAME_REPLY_IN_QUEUE : /* DBUS_NAME_FLAG_DO_NOT_QUEUE was specified */
464 	default:								/* unknown reply returned */
465 		jack_error("request name reply with unexpected value %d.", k);
466 		assert(0);
467 		r = -EIO;
468 		goto fail;
469 	}
470 
471 	if (priority <= INT32_MIN) {
472 		r = -EBUSY;
473 		dbus_set_error(error, RESERVE_ERROR_RELEASE_DENIED, "Device reservation request with priority %"PRIi32" denied for \"%s\"", priority, device_name);
474 		goto fail;
475 	}
476 
477 	if (!(m = dbus_message_new_method_call(
478 		      d->service_name,
479 		      d->object_path,
480 		      "org.freedesktop.ReserveDevice1",
481 		      "RequestRelease"))) {
482 		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for RequestRelease method call");
483 		r = -ENOMEM;
484 		goto fail;
485 	}
486 
487 	if (!dbus_message_append_args(
488 		    m,
489 		    DBUS_TYPE_INT32, &d->priority,
490 		    DBUS_TYPE_INVALID)) {
491 		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot append args for RequestRelease method call");
492 		r = -ENOMEM;
493 		goto fail;
494 	}
495 
496 	if (!(reply = dbus_connection_send_with_reply_and_block(
497 		      d->connection,
498 		      m,
499 		      5000, /* 5s */
500 		      error))) {
501 
502 		if (dbus_error_has_name(error, DBUS_ERROR_TIMED_OUT) ||
503 		    dbus_error_has_name(error, DBUS_ERROR_UNKNOWN_METHOD) ||
504 		    dbus_error_has_name(error, DBUS_ERROR_NO_REPLY)) {
505 			/* 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 			r = -EBUSY;
508 			goto fail;
509 		}
510 
511 		jack_error("dbus_connection_send_with_reply_and_block(RequestRelease) failed.");
512 		r = -EIO;
513 		goto fail;
514 	}
515 
516 	if (!dbus_message_get_args(
517 		    reply,
518 		    error,
519 		    DBUS_TYPE_BOOLEAN, &good,
520 		    DBUS_TYPE_INVALID)) {
521 		jack_error("RequestRelease() reply is invalid.");
522 		r = -EIO;
523 		goto fail;
524 	}
525 
526 	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 		r = -EBUSY;
529 		goto fail;
530 	}
531 
532 	if ((k = dbus_bus_request_name(
533 		     d->connection,
534 		     d->service_name,
535 		     DBUS_NAME_FLAG_DO_NOT_QUEUE|
536 		     (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0)|
537 		     DBUS_NAME_FLAG_REPLACE_EXISTING,
538 		     error)) < 0) {
539 		jack_error("dbus_bus_request_name() failed. (2)");
540 		r = -EIO;
541 		goto fail;
542 	}
543 
544 	if (k != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
545         /* this is racy, another contender may have acquired the device */
546 		dbus_set_error(error, RESERVE_ERROR_PROTOCOL_VIOLATION, "request name reply is not DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER but %d.", k);
547 		r = -EIO;
548 		goto fail;
549 	}
550 
551 success:
552 	d->owning = 1;
553 
554 	if (!(dbus_connection_try_register_object_path(
555 		      d->connection,
556 		      d->object_path,
557 		      &vtable,
558 		      d,
559 		      error))) {
560 		jack_error("cannot register object path \"%s\": %s", d->object_path, error->message);
561 		r = -ENOMEM;
562 		goto fail;
563 	}
564 
565 	d->registered = 1;
566 
567 	if (!dbus_connection_add_filter(
568 		    d->connection,
569 		    filter_handler,
570 		    d,
571 		    NULL)) {
572 		dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot add filter");
573 		r = -ENOMEM;
574 		goto fail;
575 	}
576 
577 	d->filtering = 1;
578 
579 	*_d = d;
580 	return 0;
581 
582 fail:
583 	if (m)
584 		dbus_message_unref(m);
585 
586 	if (reply)
587 		dbus_message_unref(reply);
588 
589 	if (&_error == error)
590 		dbus_error_free(&_error);
591 
592 	if (d)
593 		rd_release(d);
594 
595 	return r;
596 }
597 
rd_release(rd_device * d)598 void rd_release(
599 	rd_device *d) {
600 
601 	if (!d)
602 		return;
603 
604 	assert(d->ref > 0);
605 
606 	if (--d->ref)
607 		return;
608 
609 
610 	if (d->filtering)
611 		dbus_connection_remove_filter(
612 			d->connection,
613 			filter_handler,
614 			d);
615 
616 	if (d->registered)
617 		dbus_connection_unregister_object_path(
618 			d->connection,
619 			d->object_path);
620 
621 	if (d->owning) {
622 		DBusError error;
623 		dbus_error_init(&error);
624 
625 		dbus_bus_release_name(
626 			d->connection,
627 			d->service_name,
628 			&error);
629 
630 		dbus_error_free(&error);
631 	}
632 
633 	free(d->device_name);
634 	free(d->application_name);
635 	free(d->application_device_name);
636 	free(d->service_name);
637 	free(d->object_path);
638 
639 	if (d->connection)
640 		dbus_connection_unref(d->connection);
641 
642 	free(d);
643 }
644 
rd_set_application_device_name(rd_device * d,const char * n)645 int rd_set_application_device_name(rd_device *d, const char *n) {
646 	char *t;
647 
648 	if (!d)
649 		return -EINVAL;
650 
651 	assert(d->ref > 0);
652 
653 	if (!(t = strdup(n)))
654 		return -ENOMEM;
655 
656 	free(d->application_device_name);
657 	d->application_device_name = t;
658 	return 0;
659 }
660 
rd_set_userdata(rd_device * d,void * userdata)661 void rd_set_userdata(rd_device *d, void *userdata) {
662 
663 	if (!d)
664 		return;
665 
666 	assert(d->ref > 0);
667 	d->userdata = userdata;
668 }
669 
rd_get_userdata(rd_device * d)670 void* rd_get_userdata(rd_device *d) {
671 
672 	if (!d)
673 		return NULL;
674 
675 	assert(d->ref > 0);
676 
677 	return d->userdata;
678 }
679