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