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