signals.c revision a683a80c409cc4f2e57ba6a3e60d52f91b8657d0
1/* -*- mode: C; c-file-style: "gnu" -*- */ 2/* signals.c Bus signal connection implementation 3 * 4 * Copyright (C) 2003 Red Hat, Inc. 5 * 6 * Licensed under the Academic Free License version 1.2 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 * 22 */ 23#include "signals.h" 24#include "services.h" 25#include "utils.h" 26 27struct BusMatchRule 28{ 29 int refcount; /**< reference count */ 30 31 DBusConnection *matches_go_to; /**< Owner of the rule */ 32 33 unsigned int flags; /**< BusMatchFlags */ 34 35 int message_type; 36 char *interface; 37 char *member; 38 char *sender; 39 char *destination; 40 char *path; 41}; 42 43BusMatchRule* 44bus_match_rule_new (DBusConnection *matches_go_to) 45{ 46 BusMatchRule *rule; 47 48 rule = dbus_new0 (BusMatchRule, 1); 49 if (rule == NULL) 50 return NULL; 51 52 rule->refcount = 1; 53 rule->matches_go_to = matches_go_to; 54 55 return rule; 56} 57 58void 59bus_match_rule_ref (BusMatchRule *rule) 60{ 61 _dbus_assert (rule->refcount > 0); 62 63 rule->refcount += 1; 64} 65 66void 67bus_match_rule_unref (BusMatchRule *rule) 68{ 69 _dbus_assert (rule->refcount > 0); 70 71 rule->refcount -= 1; 72 if (rule->refcount == 0) 73 { 74 dbus_free (rule->interface); 75 dbus_free (rule->member); 76 dbus_free (rule->sender); 77 dbus_free (rule->destination); 78 dbus_free (rule->path); 79 dbus_free (rule); 80 } 81} 82 83#ifdef DBUS_ENABLE_VERBOSE_MODE 84static char* 85match_rule_to_string (BusMatchRule *rule) 86{ 87 DBusString str; 88 char *ret; 89 90 if (!_dbus_string_init (&str)) 91 { 92 char *s; 93 while ((s = _dbus_strdup ("nomem")) == NULL) 94 ; /* only OK for debug spew... */ 95 return s; 96 } 97 98 if (rule->flags & BUS_MATCH_MESSAGE_TYPE) 99 { 100 /* FIXME make type readable */ 101 if (!_dbus_string_append_printf (&str, "type='%d'", rule->message_type)) 102 goto nomem; 103 } 104 105 if (rule->flags & BUS_MATCH_INTERFACE) 106 { 107 if (_dbus_string_get_length (&str) > 0) 108 { 109 if (!_dbus_string_append (&str, ",")) 110 goto nomem; 111 } 112 113 if (!_dbus_string_append_printf (&str, "interface='%s'", rule->interface)) 114 goto nomem; 115 } 116 117 if (rule->flags & BUS_MATCH_MEMBER) 118 { 119 if (_dbus_string_get_length (&str) > 0) 120 { 121 if (!_dbus_string_append (&str, ",")) 122 goto nomem; 123 } 124 125 if (!_dbus_string_append_printf (&str, "member='%s'", rule->member)) 126 goto nomem; 127 } 128 129 if (rule->flags & BUS_MATCH_PATH) 130 { 131 if (_dbus_string_get_length (&str) > 0) 132 { 133 if (!_dbus_string_append (&str, ",")) 134 goto nomem; 135 } 136 137 if (!_dbus_string_append_printf (&str, "path='%s'", rule->path)) 138 goto nomem; 139 } 140 141 if (rule->flags & BUS_MATCH_SENDER) 142 { 143 if (_dbus_string_get_length (&str) > 0) 144 { 145 if (!_dbus_string_append (&str, ",")) 146 goto nomem; 147 } 148 149 if (!_dbus_string_append_printf (&str, "sender='%s'", rule->sender)) 150 goto nomem; 151 } 152 153 if (rule->flags & BUS_MATCH_DESTINATION) 154 { 155 if (_dbus_string_get_length (&str) > 0) 156 { 157 if (!_dbus_string_append (&str, ",")) 158 goto nomem; 159 } 160 161 if (!_dbus_string_append_printf (&str, "destination='%s'", rule->destination)) 162 goto nomem; 163 } 164 165 if (!_dbus_string_steal_data (&str, &ret)) 166 goto nomem; 167 168 _dbus_string_free (&str); 169 return ret; 170 171 nomem: 172 _dbus_string_free (&str); 173 { 174 char *s; 175 while ((s = _dbus_strdup ("nomem")) == NULL) 176 ; /* only OK for debug spew... */ 177 return s; 178 } 179} 180#endif /* DBUS_ENABLE_VERBOSE_MODE */ 181 182dbus_bool_t 183bus_match_rule_set_message_type (BusMatchRule *rule, 184 int type) 185{ 186 rule->flags |= BUS_MATCH_MESSAGE_TYPE; 187 188 rule->message_type = type; 189 190 return TRUE; 191} 192 193dbus_bool_t 194bus_match_rule_set_interface (BusMatchRule *rule, 195 const char *interface) 196{ 197 char *new; 198 199 _dbus_assert (interface != NULL); 200 201 new = _dbus_strdup (interface); 202 if (new == NULL) 203 return FALSE; 204 205 rule->flags |= BUS_MATCH_INTERFACE; 206 dbus_free (rule->interface); 207 rule->interface = new; 208 209 return TRUE; 210} 211 212dbus_bool_t 213bus_match_rule_set_member (BusMatchRule *rule, 214 const char *member) 215{ 216 char *new; 217 218 _dbus_assert (member != NULL); 219 220 new = _dbus_strdup (member); 221 if (new == NULL) 222 return FALSE; 223 224 rule->flags |= BUS_MATCH_MEMBER; 225 dbus_free (rule->member); 226 rule->member = new; 227 228 return TRUE; 229} 230 231dbus_bool_t 232bus_match_rule_set_sender (BusMatchRule *rule, 233 const char *sender) 234{ 235 char *new; 236 237 _dbus_assert (sender != NULL); 238 239 new = _dbus_strdup (sender); 240 if (new == NULL) 241 return FALSE; 242 243 rule->flags |= BUS_MATCH_SENDER; 244 dbus_free (rule->sender); 245 rule->sender = new; 246 247 return TRUE; 248} 249 250dbus_bool_t 251bus_match_rule_set_destination (BusMatchRule *rule, 252 const char *destination) 253{ 254 char *new; 255 256 _dbus_assert (destination != NULL); 257 258 new = _dbus_strdup (destination); 259 if (new == NULL) 260 return FALSE; 261 262 rule->flags |= BUS_MATCH_DESTINATION; 263 dbus_free (rule->destination); 264 rule->destination = new; 265 266 return TRUE; 267} 268 269dbus_bool_t 270bus_match_rule_set_path (BusMatchRule *rule, 271 const char *path) 272{ 273 char *new; 274 275 _dbus_assert (path != NULL); 276 277 new = _dbus_strdup (path); 278 if (new == NULL) 279 return FALSE; 280 281 rule->flags |= BUS_MATCH_PATH; 282 dbus_free (rule->path); 283 rule->path = new; 284 285 return TRUE; 286} 287 288/* 289 * The format is comma-separated with strings quoted with single quotes 290 * as for the shell (to escape a literal single quote, use '\''). 291 * 292 * type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='Foo', 293 * path='/bar/foo',destination=':452345-34' 294 * 295 */ 296BusMatchRule* 297bus_match_rule_parse (DBusConnection *matches_go_to, 298 const DBusString *rule_text, 299 DBusError *error) 300{ 301 BusMatchRule *rule; 302 303 rule = bus_match_rule_new (matches_go_to); 304 if (rule == NULL) 305 goto oom; 306 307 /* FIXME implement for real */ 308 309 if (!bus_match_rule_set_message_type (rule, 310 DBUS_MESSAGE_TYPE_SIGNAL)) 311 goto oom; 312 313 return rule; 314 315 oom: 316 if (rule) 317 bus_match_rule_unref (rule); 318 BUS_SET_OOM (error); 319 return NULL; 320} 321 322struct BusMatchmaker 323{ 324 int refcount; 325 326 DBusList *all_rules; 327}; 328 329BusMatchmaker* 330bus_matchmaker_new (void) 331{ 332 BusMatchmaker *matchmaker; 333 334 matchmaker = dbus_new0 (BusMatchmaker, 1); 335 if (matchmaker == NULL) 336 return NULL; 337 338 matchmaker->refcount = 1; 339 340 return matchmaker; 341} 342 343void 344bus_matchmaker_ref (BusMatchmaker *matchmaker) 345{ 346 _dbus_assert (matchmaker->refcount > 0); 347 348 matchmaker->refcount += 1; 349} 350 351void 352bus_matchmaker_unref (BusMatchmaker *matchmaker) 353{ 354 _dbus_assert (matchmaker->refcount > 0); 355 356 matchmaker->refcount -= 1; 357 if (matchmaker->refcount == 0) 358 { 359 while (matchmaker->all_rules != NULL) 360 { 361 BusMatchRule *rule; 362 363 rule = matchmaker->all_rules->data; 364 bus_match_rule_unref (rule); 365 _dbus_list_remove_link (&matchmaker->all_rules, 366 matchmaker->all_rules); 367 } 368 369 dbus_free (matchmaker); 370 } 371} 372 373/* The rule can't be modified after it's added. */ 374dbus_bool_t 375bus_matchmaker_add_rule (BusMatchmaker *matchmaker, 376 BusMatchRule *rule) 377{ 378 _dbus_assert (bus_connection_is_active (rule->matches_go_to)); 379 380 if (!_dbus_list_append (&matchmaker->all_rules, rule)) 381 return FALSE; 382 383 if (!bus_connection_add_match_rule (rule->matches_go_to, rule)) 384 { 385 _dbus_list_remove_last (&matchmaker->all_rules, rule); 386 return FALSE; 387 } 388 389 bus_match_rule_ref (rule); 390 391#ifdef DBUS_ENABLE_VERBOSE_MODE 392 { 393 char *s = match_rule_to_string (rule); 394 395 _dbus_verbose ("Added match rule %s to connection %p\n", 396 s, rule->matches_go_to); 397 dbus_free (s); 398 } 399#endif 400 401 return TRUE; 402} 403 404static dbus_bool_t 405match_rule_equal (BusMatchRule *a, 406 BusMatchRule *b) 407{ 408 if (a->flags != b->flags) 409 return FALSE; 410 411 if ((a->flags & BUS_MATCH_MESSAGE_TYPE) && 412 a->message_type != b->message_type) 413 return FALSE; 414 415 if ((a->flags & BUS_MATCH_MEMBER) && 416 strcmp (a->member, b->member) != 0) 417 return FALSE; 418 419 if ((a->flags & BUS_MATCH_PATH) && 420 strcmp (a->path, b->path) != 0) 421 return FALSE; 422 423 if ((a->flags & BUS_MATCH_INTERFACE) && 424 strcmp (a->interface, b->interface) != 0) 425 return FALSE; 426 427 if ((a->flags & BUS_MATCH_SENDER) && 428 strcmp (a->sender, b->sender) != 0) 429 return FALSE; 430 431 if ((a->flags & BUS_MATCH_DESTINATION) && 432 strcmp (a->destination, b->destination) != 0) 433 return FALSE; 434 435 return TRUE; 436} 437 438static void 439bus_matchmaker_remove_rule_link (BusMatchmaker *matchmaker, 440 DBusList *link) 441{ 442 BusMatchRule *rule = link->data; 443 444 bus_connection_remove_match_rule (rule->matches_go_to, rule); 445 _dbus_list_remove_link (&matchmaker->all_rules, link); 446 447#ifdef DBUS_ENABLE_VERBOSE_MODE 448 { 449 char *s = match_rule_to_string (rule); 450 451 _dbus_verbose ("Removed match rule %s for connection %p\n", 452 s, rule->matches_go_to); 453 dbus_free (s); 454 } 455#endif 456 457 bus_match_rule_unref (rule); 458} 459 460void 461bus_matchmaker_remove_rule (BusMatchmaker *matchmaker, 462 BusMatchRule *rule) 463{ 464 bus_connection_remove_match_rule (rule->matches_go_to, rule); 465 _dbus_list_remove (&matchmaker->all_rules, rule); 466 467#ifdef DBUS_ENABLE_VERBOSE_MODE 468 { 469 char *s = match_rule_to_string (rule); 470 471 _dbus_verbose ("Removed match rule %s for connection %p\n", 472 s, rule->matches_go_to); 473 dbus_free (s); 474 } 475#endif 476 477 bus_match_rule_unref (rule); 478} 479 480/* Remove a single rule which is equal to the given rule by value */ 481dbus_bool_t 482bus_matchmaker_remove_rule_by_value (BusMatchmaker *matchmaker, 483 BusMatchRule *value, 484 DBusError *error) 485{ 486 /* FIXME this is an unoptimized linear scan */ 487 488 DBusList *link; 489 490 /* we traverse backward because bus_connection_remove_match_rule() 491 * removes the most-recently-added rule 492 */ 493 link = _dbus_list_get_last_link (&matchmaker->all_rules); 494 while (link != NULL) 495 { 496 BusMatchRule *rule; 497 DBusList *prev; 498 499 rule = link->data; 500 prev = _dbus_list_get_prev_link (&matchmaker->all_rules, link); 501 502 if (match_rule_equal (rule, value)) 503 { 504 bus_matchmaker_remove_rule_link (matchmaker, link); 505 break; 506 } 507 508 link = prev; 509 } 510 511 if (link == NULL) 512 { 513 dbus_set_error (error, DBUS_ERROR_MATCH_RULE_NOT_FOUND, 514 "The given match rule wasn't found and can't be removed"); 515 return FALSE; 516 } 517 518 return TRUE; 519} 520 521void 522bus_matchmaker_disconnected (BusMatchmaker *matchmaker, 523 DBusConnection *disconnected) 524{ 525 DBusList *link; 526 527 /* FIXME 528 * 529 * This scans all match rules on the bus. We could avoid that 530 * for the rules belonging to the connection, since we keep 531 * a list of those; but for the rules that just refer to 532 * the connection we'd need to do something more elaborate. 533 * 534 */ 535 536 _dbus_assert (bus_connection_is_active (disconnected)); 537 538 link = _dbus_list_get_first_link (&matchmaker->all_rules); 539 while (link != NULL) 540 { 541 BusMatchRule *rule; 542 DBusList *next; 543 544 rule = link->data; 545 next = _dbus_list_get_next_link (&matchmaker->all_rules, link); 546 547 if (rule->matches_go_to == disconnected) 548 { 549 bus_matchmaker_remove_rule_link (matchmaker, link); 550 } 551 else if (((rule->flags & BUS_MATCH_SENDER) && *rule->sender == ':') || 552 ((rule->flags & BUS_MATCH_DESTINATION) && *rule->destination == ':')) 553 { 554 /* The rule matches to/from a base service, see if it's the 555 * one being disconnected, since we know this service name 556 * will never be recycled. 557 */ 558 const char *name; 559 560 name = bus_connection_get_name (disconnected); 561 _dbus_assert (name != NULL); /* because we're an active connection */ 562 563 if (((rule->flags & BUS_MATCH_SENDER) && 564 strcmp (rule->sender, name) == 0) || 565 ((rule->flags & BUS_MATCH_DESTINATION) && 566 strcmp (rule->destination, name) == 0)) 567 { 568 bus_matchmaker_remove_rule_link (matchmaker, link); 569 } 570 } 571 572 link = next; 573 } 574} 575 576static dbus_bool_t 577connection_is_primary_owner (DBusConnection *connection, 578 const char *service_name) 579{ 580 BusService *service; 581 DBusString str; 582 BusRegistry *registry; 583 584 registry = bus_connection_get_registry (connection); 585 586 _dbus_string_init_const (&str, service_name); 587 service = bus_registry_lookup (registry, &str); 588 589 if (service == NULL) 590 return FALSE; /* Service doesn't exist so connection can't own it. */ 591 592 return bus_service_get_primary_owner (service) == connection; 593} 594 595static dbus_bool_t 596match_rule_matches (BusMatchRule *rule, 597 BusConnections *connections, 598 DBusConnection *sender, 599 DBusConnection *addressed_recipient, 600 DBusMessage *message) 601{ 602 /* All features of the match rule are AND'd together, 603 * so FALSE if any of them don't match. 604 */ 605 606 if (rule->flags & BUS_MATCH_MESSAGE_TYPE) 607 { 608 _dbus_assert (rule->message_type != DBUS_MESSAGE_TYPE_INVALID); 609 610 if (rule->message_type != dbus_message_get_type (message)) 611 return FALSE; 612 } 613 614 if (rule->flags & BUS_MATCH_INTERFACE) 615 { 616 const char *iface; 617 618 _dbus_assert (rule->interface != NULL); 619 620 iface = dbus_message_get_interface (message); 621 if (iface == NULL) 622 return FALSE; 623 624 if (strcmp (iface, rule->interface) != 0) 625 return FALSE; 626 } 627 628 if (rule->flags & BUS_MATCH_MEMBER) 629 { 630 const char *member; 631 632 _dbus_assert (rule->member != NULL); 633 634 member = dbus_message_get_member (message); 635 if (member == NULL) 636 return FALSE; 637 638 if (strcmp (member, rule->member) != 0) 639 return FALSE; 640 } 641 642 if (rule->flags & BUS_MATCH_SENDER) 643 { 644 _dbus_assert (rule->sender != NULL); 645 646 if (!connection_is_primary_owner (sender, rule->sender)) 647 return FALSE; 648 } 649 650 if (rule->flags & BUS_MATCH_DESTINATION) 651 { 652 const char *destination; 653 654 _dbus_assert (rule->destination != NULL); 655 656 if (addressed_recipient == NULL) 657 return FALSE; 658 659 destination = dbus_message_get_destination (message); 660 if (destination == NULL) 661 return FALSE; 662 663 if (!connection_is_primary_owner (addressed_recipient, rule->destination)) 664 return FALSE; 665 } 666 667 if (rule->flags & BUS_MATCH_PATH) 668 { 669 const char *path; 670 671 _dbus_assert (rule->path != NULL); 672 673 path = dbus_message_get_path (message); 674 if (path == NULL) 675 return FALSE; 676 677 if (strcmp (path, rule->path) != 0) 678 return FALSE; 679 } 680 681 return TRUE; 682} 683 684dbus_bool_t 685bus_matchmaker_get_recipients (BusMatchmaker *matchmaker, 686 BusConnections *connections, 687 DBusConnection *sender, 688 DBusConnection *addressed_recipient, 689 DBusMessage *message, 690 DBusList **recipients_p) 691{ 692 /* FIXME for now this is a wholly unoptimized linear search */ 693 694 DBusList *link; 695 696 _dbus_assert (*recipients_p == NULL); 697 698 /* This avoids sending same message to the same connection twice. 699 * Purpose of the stamp instead of a bool is to avoid iterating over 700 * all connections resetting the bool each time. 701 */ 702 bus_connections_increment_stamp (connections); 703 704 /* addressed_recipient is already receiving the message, don't add to list. 705 * NULL addressed_recipient means either bus driver, or this is a signal 706 * and thus lacks a specific addressed_recipient. 707 */ 708 if (addressed_recipient != NULL) 709 bus_connection_mark_stamp (addressed_recipient); 710 711 link = _dbus_list_get_first_link (&matchmaker->all_rules); 712 while (link != NULL) 713 { 714 BusMatchRule *rule; 715 716 rule = link->data; 717 718#ifdef DBUS_ENABLE_VERBOSE_MODE 719 { 720 char *s = match_rule_to_string (rule); 721 722 _dbus_verbose ("Checking whether message matches rule %s for connection %p\n", 723 s, rule->matches_go_to); 724 dbus_free (s); 725 } 726#endif 727 728 if (match_rule_matches (rule, connections, 729 sender, addressed_recipient, message)) 730 { 731 _dbus_verbose ("Rule matched\n"); 732 733 /* Append to the list if we haven't already */ 734 if (bus_connection_mark_stamp (rule->matches_go_to)) 735 { 736 if (!_dbus_list_append (recipients_p, rule->matches_go_to)) 737 goto nomem; 738 } 739#ifdef DBUS_ENABLE_VERBOSE_MODE 740 else 741 { 742 _dbus_verbose ("Connection already receiving this message, so not adding again\n"); 743 } 744#endif /* DBUS_ENABLE_VERBOSE_MODE */ 745 } 746 747 link = _dbus_list_get_next_link (&matchmaker->all_rules, link); 748 } 749 750 return TRUE; 751 752 nomem: 753 _dbus_list_clear (recipients_p); 754 return FALSE; 755} 756 757#ifdef DBUS_BUILD_TESTS 758#include "test.h" 759 760dbus_bool_t 761bus_signals_test (const DBusString *test_data_dir) 762{ 763 BusMatchmaker *matchmaker; 764 765 matchmaker = bus_matchmaker_new (); 766 bus_matchmaker_ref (matchmaker); 767 bus_matchmaker_unref (matchmaker); 768 bus_matchmaker_unref (matchmaker); 769 770 return TRUE; 771} 772 773#endif /* DBUS_BUILD_TESTS */ 774 775