1 /* DBus device reservation API
2  *
3  * Copyright © 2019 Wim Taymans
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice (including the next
13  * paragraph) shall be included in all copies or substantial portions of the
14  * Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22  * DEALINGS IN THE SOFTWARE.
23  */
24 
25 #ifndef NAME
26 #define NAME "reserve"
27 #endif
28 
29 #include "reserve.h"
30 
31 #include <spa/utils/string.h>
32 #include <pipewire/log.h>
33 
34 #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."
35 #define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/"
36 
37 static const char introspection[] =
38 	DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
39 	"<node>"
40 	" <!-- If you are looking for documentation make sure to check out\n"
41 	"      http://git.0pointer.de/?p=reserve.git;a=blob;f=reserve.txt -->\n"
42 	" <interface name=\"org.freedesktop.ReserveDevice1\">"
43 	"  <method name=\"RequestRelease\">"
44 	"   <arg name=\"priority\" type=\"i\" direction=\"in\"/>"
45 	"   <arg name=\"result\" type=\"b\" direction=\"out\"/>"
46 	"  </method>"
47 	"  <property name=\"Priority\" type=\"i\" access=\"read\"/>"
48 	"  <property name=\"ApplicationName\" type=\"s\" access=\"read\"/>"
49 	"  <property name=\"ApplicationDeviceName\" type=\"s\" access=\"read\"/>"
50 	" </interface>"
51 	" <interface name=\"org.freedesktop.DBus.Properties\">"
52 	"  <method name=\"Get\">"
53 	"   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"
54 	"   <arg name=\"property\" direction=\"in\" type=\"s\"/>"
55 	"   <arg name=\"value\" direction=\"out\" type=\"v\"/>"
56 	"  </method>"
57 	" </interface>"
58 	" <interface name=\"org.freedesktop.DBus.Introspectable\">"
59 	"  <method name=\"Introspect\">"
60 	"   <arg name=\"data\" type=\"s\" direction=\"out\"/>"
61 	"  </method>"
62 	" </interface>"
63 	"</node>";
64 
65 struct rd_device {
66 	DBusConnection *connection;
67 
68 	int32_t priority;
69 	char *service_name;
70 	char *object_path;
71 	char *application_name;
72 	char *application_device_name;
73 
74 	const struct rd_device_callbacks *callbacks;
75 	void *data;
76 
77 	DBusMessage *reply;
78 
79 	unsigned int filtering:1;
80 	unsigned int registered:1;
81 	unsigned int acquiring:1;
82 	unsigned int owning:1;
83 };
84 
add_variant(DBusMessage * m,int type,const void * data)85 static dbus_bool_t add_variant(DBusMessage *m, int type, const void *data)
86 {
87 	DBusMessageIter iter, sub;
88 	char t[2];
89 
90 	t[0] = (char) type;
91 	t[1] = 0;
92 
93 	dbus_message_iter_init_append(m, &iter);
94 
95 	if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, t, &sub))
96 		return false;
97 
98 	if (!dbus_message_iter_append_basic(&sub, type, data))
99 		return false;
100 
101 	if (!dbus_message_iter_close_container(&iter, &sub))
102 		return false;
103 
104 	return true;
105 }
106 
object_handler(DBusConnection * c,DBusMessage * m,void * userdata)107 static DBusHandlerResult object_handler(DBusConnection *c, DBusMessage *m, void *userdata)
108 {
109 	struct rd_device *d = userdata;
110 	DBusError error;
111 	DBusMessage *reply = NULL;
112 
113 	dbus_error_init(&error);
114 
115 	if (dbus_message_is_method_call(m, "org.freedesktop.ReserveDevice1",
116 				"RequestRelease")) {
117 		int32_t priority;
118 
119 		if (!dbus_message_get_args(m, &error,
120 					DBUS_TYPE_INT32, &priority,
121 					DBUS_TYPE_INVALID))
122 			goto invalid;
123 
124 		pw_log_debug("%p: request release priority:%d", d, priority);
125 
126 		if (!(reply = dbus_message_new_method_return(m)))
127 			goto oom;
128 
129 		if (d->reply)
130 			rd_device_complete_release(d, false);
131 		d->reply = reply;
132 
133 		if (priority > d->priority && d->callbacks->release)
134 			d->callbacks->release(d->data, d, 0);
135 		else
136 			rd_device_complete_release(d, false);
137 
138 		return DBUS_HANDLER_RESULT_HANDLED;
139 
140 	} else if (dbus_message_is_method_call(
141 			   m,
142 			   "org.freedesktop.DBus.Properties",
143 			   "Get")) {
144 
145 		const char *interface, *property;
146 
147 		if (!dbus_message_get_args( m, &error,
148 					DBUS_TYPE_STRING, &interface,
149 					DBUS_TYPE_STRING, &property,
150 					DBUS_TYPE_INVALID))
151 			goto invalid;
152 
153 		if (spa_streq(interface, "org.freedesktop.ReserveDevice1")) {
154 			const char *empty = "";
155 
156 			if (spa_streq(property, "ApplicationName") && d->application_name) {
157 				if (!(reply = dbus_message_new_method_return(m)))
158 					goto oom;
159 
160 				if (!add_variant(reply,
161 					    DBUS_TYPE_STRING,
162 					    d->application_name ? (const char**) &d->application_name : &empty))
163 					goto oom;
164 
165 			} else if (spa_streq(property, "ApplicationDeviceName")) {
166 				if (!(reply = dbus_message_new_method_return(m)))
167 					goto oom;
168 
169 				if (!add_variant(reply,
170 					    DBUS_TYPE_STRING,
171 					    d->application_device_name ? (const char**) &d->application_device_name : &empty))
172 					goto oom;
173 
174 			} else if (spa_streq(property, "Priority")) {
175 				if (!(reply = dbus_message_new_method_return(m)))
176 					goto oom;
177 
178 				if (!add_variant(reply,
179 					    DBUS_TYPE_INT32, &d->priority))
180 					goto oom;
181 			} else {
182 				if (!(reply = dbus_message_new_error_printf(m,
183 								DBUS_ERROR_UNKNOWN_METHOD,
184 								"Unknown property %s", property)))
185 					goto oom;
186 			}
187 
188 			if (!dbus_connection_send(c, reply, NULL))
189 				goto oom;
190 
191 			dbus_message_unref(reply);
192 
193 			return DBUS_HANDLER_RESULT_HANDLED;
194 		}
195 	} else if (dbus_message_is_method_call(
196 			   m,
197 			   "org.freedesktop.DBus.Introspectable",
198 			   "Introspect")) {
199 			    const char *i = introspection;
200 
201 		if (!(reply = dbus_message_new_method_return(m)))
202 			goto oom;
203 
204 		if (!dbus_message_append_args(reply,
205 					DBUS_TYPE_STRING, &i,
206 					DBUS_TYPE_INVALID))
207 			goto oom;
208 
209 		if (!dbus_connection_send(c, reply, NULL))
210 			goto oom;
211 
212 		dbus_message_unref(reply);
213 
214 		return DBUS_HANDLER_RESULT_HANDLED;
215 	}
216 
217 	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
218 
219 invalid:
220 	if (!(reply = dbus_message_new_error(m,
221 					DBUS_ERROR_INVALID_ARGS,
222 					"Invalid arguments")))
223 		goto oom;
224 
225 	if (!dbus_connection_send(c, reply, NULL))
226 		goto oom;
227 
228 	dbus_message_unref(reply);
229 
230 	dbus_error_free(&error);
231 
232 	return DBUS_HANDLER_RESULT_HANDLED;
233 
234 oom:
235 	if (reply)
236 		dbus_message_unref(reply);
237 
238 	dbus_error_free(&error);
239 
240 	return DBUS_HANDLER_RESULT_NEED_MEMORY;
241 }
242 
243 static const struct DBusObjectPathVTable vtable ={
244 	.message_function = object_handler
245 };
246 
filter_handler(DBusConnection * c,DBusMessage * m,void * userdata)247 static DBusHandlerResult filter_handler(DBusConnection *c, DBusMessage *m, void *userdata)
248 {
249 	struct rd_device *d = userdata;
250 	DBusError error;
251 	const char *name;
252 
253 	dbus_error_init(&error);
254 
255 	if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameAcquired")) {
256 		if (!dbus_message_get_args( m, &error,
257 			    DBUS_TYPE_STRING, &name,
258 			    DBUS_TYPE_INVALID))
259 			goto invalid;
260 
261 		if (!spa_streq(name, d->service_name))
262 			goto invalid;
263 
264 		pw_log_debug("%p: acquired %s, %s", d, name, d->service_name);
265 
266 		d->owning = true;
267 
268 		if (!d->registered) {
269 			if (!(dbus_connection_register_object_path(d->connection,
270 							d->object_path,
271 							&vtable,
272 							d)))
273 				goto invalid;
274 
275 			if (!spa_streq(name, d->service_name))
276 				goto invalid;
277 
278 			d->registered = true;
279 
280 			if (d->callbacks->acquired)
281 				d->callbacks->acquired(d->data, d);
282 		}
283 	} else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) {
284 		if (!dbus_message_get_args( m, &error,
285 			    DBUS_TYPE_STRING, &name,
286 			    DBUS_TYPE_INVALID))
287 			goto invalid;
288 
289 		if (!spa_streq(name, d->service_name))
290 			goto invalid;
291 
292 		pw_log_debug("%p: lost %s", d, name);
293 
294 		d->owning = false;
295 
296 		if (d->registered) {
297 			dbus_connection_unregister_object_path(d->connection,
298 					d->object_path);
299 			d->registered = false;
300 		}
301 	}
302 	if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
303 		const char *old, *new;
304 		if (!dbus_message_get_args( m, &error,
305 			    DBUS_TYPE_STRING, &name,
306 			    DBUS_TYPE_STRING, &old,
307 			    DBUS_TYPE_STRING, &new,
308 			    DBUS_TYPE_INVALID))
309 			goto invalid;
310 
311 		if (!spa_streq(name, d->service_name) || d->owning)
312 			goto invalid;
313 
314 		pw_log_debug("%p: changed %s: %s -> %s", d, name, old, new);
315 
316 		if (old == NULL || *old == 0) {
317 			if (d->callbacks->busy && !d->acquiring)
318 				d->callbacks->busy(d->data, d, name, 0);
319 		} else {
320 			if (d->callbacks->available)
321 				d->callbacks->available(d->data, d, name);
322 		}
323 	}
324 
325 invalid:
326 	dbus_error_free(&error);
327 	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
328 }
329 
330 struct rd_device *
rd_device_new(DBusConnection * connection,const char * device_name,const char * application_name,int32_t priority,const struct rd_device_callbacks * callbacks,void * data)331 rd_device_new(DBusConnection *connection, const char *device_name, const char *application_name,
332 		int32_t priority, const struct rd_device_callbacks *callbacks, void *data)
333 {
334 	struct rd_device *d;
335 	int res;
336 
337 	d = calloc(1, sizeof(struct rd_device));
338 	if (d == NULL)
339 		return NULL;
340 
341 	d->connection = connection;
342 	d->priority = priority;
343 	d->callbacks = callbacks;
344 	d->data = data;
345 
346 	d->application_name = strdup(application_name);
347 
348 	d->object_path = spa_aprintf(OBJECT_PREFIX "%s", device_name);
349 	if (d->object_path == NULL) {
350 		res = -errno;
351 		goto error_free;
352 	}
353 	d->service_name = spa_aprintf(SERVICE_PREFIX "%s", device_name);
354 	if (d->service_name == NULL) {
355 		res = -errno;
356 		goto error_free;
357 	}
358 
359 	if (!dbus_connection_add_filter(d->connection,
360 				filter_handler,
361 				d,
362 				NULL)) {
363 		res = -ENOMEM;
364 		goto error_free;
365 	}
366 	dbus_bus_add_match(d->connection,
367                         "type='signal',sender='org.freedesktop.DBus',"
368                         "interface='org.freedesktop.DBus',member='NameLost'", NULL);
369 	dbus_bus_add_match(d->connection,
370                         "type='signal',sender='org.freedesktop.DBus',"
371                         "interface='org.freedesktop.DBus',member='NameAcquired'", NULL);
372 	dbus_bus_add_match(d->connection,
373                         "type='signal',sender='org.freedesktop.DBus',"
374                         "interface='org.freedesktop.DBus',member='NameOwnerChanged'", NULL);
375 
376 	dbus_connection_ref(d->connection);
377 
378 	pw_log_debug("%p: new device %s", d, device_name);
379 
380 	return d;
381 
382 error_free:
383 	free(d->service_name);
384 	free(d->object_path);
385 	free(d);
386 	errno = -res;
387 	return NULL;
388 }
389 
rd_device_acquire(struct rd_device * d)390 int rd_device_acquire(struct rd_device *d)
391 {
392 	int res;
393 	DBusError error;
394 
395 	dbus_error_init(&error);
396 
397 	pw_log_debug("%p: reserve %s", d, d->service_name);
398 
399 	d->acquiring = true;
400 
401 	if ((res = dbus_bus_request_name(d->connection,
402 					d->service_name,
403 					(d->priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0),
404 					&error)) < 0) {
405 			pw_log_warn("%p: reserve failed: %s", d, error.message);
406 			dbus_error_free(&error);
407 			return -EIO;
408 	}
409 
410 	pw_log_debug("%p: reserve result: %d", d, res);
411 
412 	if (res == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER ||
413 	    res == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER)
414 		return 0;
415 
416 	if (res == DBUS_REQUEST_NAME_REPLY_EXISTS ||
417 	    res == DBUS_REQUEST_NAME_REPLY_IN_QUEUE)
418 		return -EBUSY;
419 
420 	return -EIO;
421 }
422 
rd_device_request_release(struct rd_device * d)423 int rd_device_request_release(struct rd_device *d)
424 {
425 	DBusMessage *m = NULL;
426 
427 	if (d->priority <= INT32_MIN)
428 		return -EBUSY;
429 
430 	if ((m = dbus_message_new_method_call(d->service_name,
431 					d->object_path,
432 					"org.freedesktop.ReserveDevice1",
433 					"RequestRelease")) == NULL) {
434 		return -ENOMEM;
435 	}
436         if (!dbus_message_append_args(m,
437 				DBUS_TYPE_INT32, &d->priority,
438 				DBUS_TYPE_INVALID)) {
439 		dbus_message_unref(m);
440 		return -ENOMEM;
441         }
442 	if (!dbus_connection_send(d->connection, m, NULL)) {
443 		return -EIO;
444 	}
445 	return 0;
446 }
447 
rd_device_complete_release(struct rd_device * d,int res)448 int rd_device_complete_release(struct rd_device *d, int res)
449 {
450 	dbus_bool_t ret = res != 0;
451 
452 	if (d->reply == NULL)
453 		return -EINVAL;
454 
455 	pw_log_debug("%p: complete release %d", d, res);
456 
457 	if (!dbus_message_append_args(d->reply,
458 				DBUS_TYPE_BOOLEAN, &ret,
459 				DBUS_TYPE_INVALID)) {
460 		res = -ENOMEM;
461 		goto exit;
462 	}
463 
464 	if (!dbus_connection_send(d->connection, d->reply, NULL)) {
465 		res = -EIO;
466 		goto exit;
467 	}
468 	res = 0;
469 exit:
470 	dbus_message_unref(d->reply);
471 	d->reply = NULL;
472 	return res;
473 }
474 
rd_device_release(struct rd_device * d)475 void rd_device_release(struct rd_device *d)
476 {
477 	pw_log_debug("%p: release %d", d, d->owning);
478 
479 	if (d->owning) {
480 		DBusError error;
481 		dbus_error_init(&error);
482 
483 		dbus_bus_release_name(d->connection,
484 				d->service_name, &error);
485 		dbus_error_free(&error);
486 	}
487 	d->acquiring = false;
488 }
489 
rd_device_destroy(struct rd_device * d)490 void rd_device_destroy(struct rd_device *d)
491 {
492 	dbus_connection_remove_filter(d->connection,
493 			filter_handler, d);
494 
495 	if (d->registered)
496 		dbus_connection_unregister_object_path(d->connection,
497 				d->object_path);
498 
499 	rd_device_release(d);
500 
501 	free(d->service_name);
502 	free(d->object_path);
503 	free(d->application_name);
504 	free(d->application_device_name);
505 	if (d->reply)
506 		dbus_message_unref(d->reply);
507 
508 	dbus_connection_unref(d->connection);
509 
510 	free(d);
511 }
512 
rd_device_set_application_device_name(struct rd_device * d,const char * name)513 int rd_device_set_application_device_name(struct rd_device *d, const char *name)
514 {
515 	char *t;
516 
517 	if (!d)
518 		return -EINVAL;
519 
520 	if (!(t = strdup(name)))
521 		return -ENOMEM;
522 
523 	free(d->application_device_name);
524 	d->application_device_name = t;
525 
526 	return 0;
527 }
528