• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
add_variant(DBusMessage * m,int type,const void * data)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 
object_handler(DBusConnection * c,DBusMessage * m,void * userdata)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 
filter_handler(DBusConnection * c,DBusMessage * m,void * userdata)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 
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)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         dbus_message_unref(m);
478         m = NULL;
479 
480 	if (!dbus_message_get_args(
481 		    reply,
482 		    error,
483 		    DBUS_TYPE_BOOLEAN, &good,
484 		    DBUS_TYPE_INVALID)) {
485 		r = -EIO;
486 		goto fail;
487 	}
488 
489         dbus_message_unref(reply);
490         reply = NULL;
491 
492 	if (!good) {
493 		r = -EBUSY;
494 		goto fail;
495 	}
496 
497 	if ((k = dbus_bus_request_name(
498 		     d->connection,
499 		     d->service_name,
500 		     DBUS_NAME_FLAG_DO_NOT_QUEUE|
501 		     (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0)|
502 		     DBUS_NAME_FLAG_REPLACE_EXISTING,
503 		     error)) < 0) {
504 		r = -EIO;
505 		goto fail;
506 	}
507 
508 	if (k != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
509 		r = -EIO;
510 		goto fail;
511 	}
512 
513 success:
514 	d->owning = 1;
515 
516 	if (!(dbus_connection_register_object_path(
517 		      d->connection,
518 		      d->object_path,
519 		      &vtable,
520 		      d))) {
521 		r = -ENOMEM;
522 		goto fail;
523 	}
524 
525 	d->registered = 1;
526 
527 	if (!dbus_connection_add_filter(
528 		    d->connection,
529 		    filter_handler,
530 		    d,
531 		    NULL)) {
532 		r = -ENOMEM;
533 		goto fail;
534 	}
535 
536 	d->filtering = 1;
537 
538 	*_d = d;
539 	return 0;
540 
541 fail:
542 	if (m)
543 		dbus_message_unref(m);
544 
545 	if (reply)
546 		dbus_message_unref(reply);
547 
548 	if (&_error == error)
549 		dbus_error_free(&_error);
550 
551 	if (d)
552 		rd_release(d);
553 
554 	return r;
555 }
556 
rd_release(rd_device * d)557 void rd_release(
558 	rd_device *d) {
559 
560 	if (!d)
561 		return;
562 
563 	assert(d->ref > 0);
564 
565 	if (--d->ref > 0)
566 		return;
567 
568 
569 	if (d->filtering)
570 		dbus_connection_remove_filter(
571 			d->connection,
572 			filter_handler,
573 			d);
574 
575 	if (d->registered)
576 		dbus_connection_unregister_object_path(
577 			d->connection,
578 			d->object_path);
579 
580 	if (d->owning)
581 		dbus_bus_release_name(
582 			d->connection,
583 			d->service_name,
584 			NULL);
585 
586 	free(d->device_name);
587 	free(d->application_name);
588 	free(d->application_device_name);
589 	free(d->service_name);
590 	free(d->object_path);
591 
592 	if (d->connection)
593 		dbus_connection_unref(d->connection);
594 
595 	free(d);
596 }
597 
rd_set_application_device_name(rd_device * d,const char * n)598 int rd_set_application_device_name(rd_device *d, const char *n) {
599 	char *t;
600 
601 	if (!d)
602 		return -EINVAL;
603 
604 	assert(d->ref > 0);
605 
606 	if (!(t = strdup(n)))
607 		return -ENOMEM;
608 
609 	free(d->application_device_name);
610 	d->application_device_name = t;
611 	return 0;
612 }
613 
rd_set_userdata(rd_device * d,void * userdata)614 void rd_set_userdata(rd_device *d, void *userdata) {
615 
616 	if (!d)
617 		return;
618 
619 	assert(d->ref > 0);
620 	d->userdata = userdata;
621 }
622 
rd_get_userdata(rd_device * d)623 void* rd_get_userdata(rd_device *d) {
624 
625 	if (!d)
626 		return NULL;
627 
628 	assert(d->ref > 0);
629 
630 	return d->userdata;
631 }
632 
rd_dbus_get_name_owner(DBusConnection * connection,const char * name,char ** name_owner,DBusError * error)633 int rd_dbus_get_name_owner(
634 	DBusConnection *connection,
635 	const char *name,
636 	char **name_owner,
637 	DBusError *error) {
638 
639 	DBusMessage *msg, *reply;
640 	int r;
641 
642 	*name_owner = NULL;
643 
644 	if (!(msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner"))) {
645 		r = -ENOMEM;
646 		goto fail;
647 	}
648 
649 	if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)) {
650 		r = -ENOMEM;
651 		goto fail;
652 	}
653 
654 	reply = dbus_connection_send_with_reply_and_block(connection, msg, DBUS_TIMEOUT_USE_DEFAULT, error);
655 	dbus_message_unref(msg);
656 	msg = NULL;
657 
658 	if (reply) {
659 		if (!dbus_message_get_args(reply, error, DBUS_TYPE_STRING, name_owner, DBUS_TYPE_INVALID)) {
660 			dbus_message_unref(reply);
661 			r = -EIO;
662 			goto fail;
663 		}
664 
665 		*name_owner = strdup(*name_owner);
666 		dbus_message_unref(reply);
667 
668 		if (!*name_owner) {
669 			r = -ENOMEM;
670 			goto fail;
671 		}
672 
673 	} else if (dbus_error_has_name(error, "org.freedesktop.DBus.Error.NameHasNoOwner"))
674 		dbus_error_free(error);
675 	else {
676 		r = -EIO;
677 		goto fail;
678 	}
679 
680 	return 0;
681 
682 fail:
683 	if (msg)
684 		dbus_message_unref(msg);
685 
686 	return r;
687 }
688