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
36struct 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
60static 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=\"" DBUS_INTERFACE_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=\"" DBUS_INTERFACE_INTROSPECTABLE "\">"
82	"  <method name=\"Introspect\">"
83	"   <arg name=\"data\" type=\"s\" direction=\"out\"/>"
84	"  </method>"
85	" </interface>"
86	"</node>";
87
88static 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
113static 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			   DBUS_INTERFACE_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			   DBUS_INTERFACE_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
261invalid:
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
280oom:
281	if (reply)
282		dbus_message_unref(reply);
283
284	dbus_error_free(&error);
285
286	return DBUS_HANDLER_RESULT_NEED_MEMORY;
287}
288
289static 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, DBUS_INTERFACE_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
344invalid:
345	free(name_owner);
346	dbus_error_free(&error);
347
348	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
349}
350
351
352static const struct DBusObjectPathVTable vtable ={
353	.message_function = object_handler
354};
355
356int 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
513success:
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
541fail:
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
557void 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
598int 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
614void 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
623void* 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
633int 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, DBUS_ERROR_NAME_HAS_NO_OWNER))
674		dbus_error_free(error);
675	else {
676		r = -EIO;
677		goto fail;
678	}
679
680	return 0;
681
682fail:
683	if (msg)
684		dbus_message_unref(msg);
685
686	return r;
687}
688