diff options
Diffstat (limited to 'bus/signals.c')
-rw-r--r-- | bus/signals.c | 778 |
1 files changed, 778 insertions, 0 deletions
diff --git a/bus/signals.c b/bus/signals.c new file mode 100644 index 00000000..30d977c3 --- /dev/null +++ b/bus/signals.c @@ -0,0 +1,778 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* signals.c Bus signal connection implementation + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include "signals.h" +#include "services.h" +#include "utils.h" + +struct BusMatchRule +{ + int refcount; /**< reference count */ + + DBusConnection *matches_go_to; /**< Owner of the rule */ + + unsigned int flags; /**< BusMatchFlags */ + + int message_type; + char *interface; + char *member; + char *sender; + char *destination; + char *path; +}; + +BusMatchRule* +bus_match_rule_new (DBusConnection *matches_go_to) +{ + BusMatchRule *rule; + + rule = dbus_new0 (BusMatchRule, 1); + if (rule == NULL) + return NULL; + + rule->refcount = 1; + rule->matches_go_to = matches_go_to; + + return rule; +} + +void +bus_match_rule_ref (BusMatchRule *rule) +{ + _dbus_assert (rule->refcount > 0); + + rule->refcount += 1; +} + +void +bus_match_rule_unref (BusMatchRule *rule) +{ + _dbus_assert (rule->refcount > 0); + + rule->refcount -= 1; + if (rule->refcount == 0) + { + dbus_free (rule->interface); + dbus_free (rule->member); + dbus_free (rule->sender); + dbus_free (rule->destination); + dbus_free (rule->path); + dbus_free (rule); + } +} + +#ifdef DBUS_ENABLE_VERBOSE_MODE +static char* +match_rule_to_string (BusMatchRule *rule) +{ + DBusString str; + char *ret; + + if (!_dbus_string_init (&str)) + { + char *s; + while ((s = _dbus_strdup ("nomem")) == NULL) + ; /* only OK for debug spew... */ + return s; + } + + if (rule->flags & BUS_MATCH_MESSAGE_TYPE) + { + /* FIXME make type readable */ + if (!_dbus_string_append_printf (&str, "type='%d'", rule->message_type)) + goto nomem; + } + + if (rule->flags & BUS_MATCH_INTERFACE) + { + if (_dbus_string_get_length (&str) > 0) + { + if (!_dbus_string_append (&str, ",")) + goto nomem; + } + + if (!_dbus_string_append_printf (&str, "interface='%s'", rule->interface)) + goto nomem; + } + + if (rule->flags & BUS_MATCH_MEMBER) + { + if (_dbus_string_get_length (&str) > 0) + { + if (!_dbus_string_append (&str, ",")) + goto nomem; + } + + if (!_dbus_string_append_printf (&str, "member='%s'", rule->member)) + goto nomem; + } + + if (rule->flags & BUS_MATCH_PATH) + { + if (_dbus_string_get_length (&str) > 0) + { + if (!_dbus_string_append (&str, ",")) + goto nomem; + } + + if (!_dbus_string_append_printf (&str, "path='%s'", rule->path)) + goto nomem; + } + + if (rule->flags & BUS_MATCH_SENDER) + { + if (_dbus_string_get_length (&str) > 0) + { + if (!_dbus_string_append (&str, ",")) + goto nomem; + } + + if (!_dbus_string_append_printf (&str, "sender='%s'", rule->sender)) + goto nomem; + } + + if (rule->flags & BUS_MATCH_DESTINATION) + { + if (_dbus_string_get_length (&str) > 0) + { + if (!_dbus_string_append (&str, ",")) + goto nomem; + } + + if (!_dbus_string_append_printf (&str, "destination='%s'", rule->destination)) + goto nomem; + } + + if (!_dbus_string_steal_data (&str, &ret)) + goto nomem; + + _dbus_string_free (&str); + return ret; + + nomem: + _dbus_string_free (&str); + { + char *s; + while ((s = _dbus_strdup ("nomem")) == NULL) + ; /* only OK for debug spew... */ + return s; + } +} +#endif /* DBUS_ENABLE_VERBOSE_MODE */ + +dbus_bool_t +bus_match_rule_set_message_type (BusMatchRule *rule, + int type) +{ + rule->flags |= BUS_MATCH_MESSAGE_TYPE; + + rule->message_type = type; + + return TRUE; +} + +dbus_bool_t +bus_match_rule_set_interface (BusMatchRule *rule, + const char *interface) +{ + char *new; + + _dbus_assert (interface != NULL); + + new = _dbus_strdup (interface); + if (new == NULL) + return FALSE; + + rule->flags |= BUS_MATCH_INTERFACE; + dbus_free (rule->interface); + rule->interface = new; + + return TRUE; +} + +dbus_bool_t +bus_match_rule_set_member (BusMatchRule *rule, + const char *member) +{ + char *new; + + _dbus_assert (member != NULL); + + new = _dbus_strdup (member); + if (new == NULL) + return FALSE; + + rule->flags |= BUS_MATCH_MEMBER; + dbus_free (rule->member); + rule->member = new; + + return TRUE; +} + +dbus_bool_t +bus_match_rule_set_sender (BusMatchRule *rule, + const char *sender) +{ + char *new; + + _dbus_assert (sender != NULL); + + new = _dbus_strdup (sender); + if (new == NULL) + return FALSE; + + rule->flags |= BUS_MATCH_SENDER; + dbus_free (rule->sender); + rule->sender = new; + + return TRUE; +} + +dbus_bool_t +bus_match_rule_set_destination (BusMatchRule *rule, + const char *destination) +{ + char *new; + + _dbus_assert (destination != NULL); + + new = _dbus_strdup (destination); + if (new == NULL) + return FALSE; + + rule->flags |= BUS_MATCH_DESTINATION; + dbus_free (rule->destination); + rule->destination = new; + + return TRUE; +} + +dbus_bool_t +bus_match_rule_set_path (BusMatchRule *rule, + const char *path) +{ + char *new; + + _dbus_assert (path != NULL); + + new = _dbus_strdup (path); + if (new == NULL) + return FALSE; + + rule->flags |= BUS_MATCH_PATH; + dbus_free (rule->path); + rule->path = new; + + return TRUE; +} + +/* + * The format is comma-separated with strings quoted with single quotes + * as for the shell (to escape a literal single quote, use '\''). + * + * type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='Foo', + * path='/bar/foo',destination=':452345-34' + * + */ +BusMatchRule* +bus_match_rule_parse (DBusConnection *matches_go_to, + const DBusString *rule_text, + DBusError *error) +{ + BusMatchRule *rule; + + rule = bus_match_rule_new (matches_go_to); + if (rule == NULL) + goto oom; + + /* FIXME implement for real */ + + if (!bus_match_rule_set_message_type (rule, + DBUS_MESSAGE_TYPE_SIGNAL)) + goto oom; + + return rule; + + oom: + if (rule) + bus_match_rule_unref (rule); + BUS_SET_OOM (error); + return NULL; +} + +struct BusMatchmaker +{ + int refcount; + + DBusList *all_rules; +}; + +BusMatchmaker* +bus_matchmaker_new (void) +{ + BusMatchmaker *matchmaker; + + matchmaker = dbus_new0 (BusMatchmaker, 1); + if (matchmaker == NULL) + return NULL; + + matchmaker->refcount = 1; + + return matchmaker; +} + +void +bus_matchmaker_ref (BusMatchmaker *matchmaker) +{ + _dbus_assert (matchmaker->refcount > 0); + + matchmaker->refcount += 1; +} + +void +bus_matchmaker_unref (BusMatchmaker *matchmaker) +{ + _dbus_assert (matchmaker->refcount > 0); + + matchmaker->refcount -= 1; + if (matchmaker->refcount == 0) + { + while (matchmaker->all_rules != NULL) + { + BusMatchRule *rule; + + rule = matchmaker->all_rules->data; + bus_match_rule_unref (rule); + _dbus_list_remove_link (&matchmaker->all_rules, + matchmaker->all_rules); + } + + dbus_free (matchmaker); + } +} + +/* The rule can't be modified after it's added. */ +dbus_bool_t +bus_matchmaker_add_rule (BusMatchmaker *matchmaker, + BusMatchRule *rule) +{ + _dbus_assert (bus_connection_is_active (rule->matches_go_to)); + + if (!_dbus_list_append (&matchmaker->all_rules, rule)) + return FALSE; + + if (!bus_connection_add_match_rule (rule->matches_go_to, rule)) + { + _dbus_list_remove_last (&matchmaker->all_rules, rule); + return FALSE; + } + + bus_match_rule_ref (rule); + +#ifdef DBUS_ENABLE_VERBOSE_MODE + { + char *s = match_rule_to_string (rule); + + _dbus_verbose ("Added match rule %s to connection %p\n", + s, rule->matches_go_to); + dbus_free (s); + } +#endif + + return TRUE; +} + +static dbus_bool_t +match_rule_equal (BusMatchRule *a, + BusMatchRule *b) +{ + if (a->flags != b->flags) + return FALSE; + + if ((a->flags & BUS_MATCH_MESSAGE_TYPE) && + a->message_type != b->message_type) + return FALSE; + + if ((a->flags & BUS_MATCH_MEMBER) && + strcmp (a->member, b->member) != 0) + return FALSE; + + if ((a->flags & BUS_MATCH_PATH) && + strcmp (a->path, b->path) != 0) + return FALSE; + + if ((a->flags & BUS_MATCH_INTERFACE) && + strcmp (a->interface, b->interface) != 0) + return FALSE; + + if ((a->flags & BUS_MATCH_SENDER) && + strcmp (a->sender, b->sender) != 0) + return FALSE; + + if ((a->flags & BUS_MATCH_DESTINATION) && + strcmp (a->destination, b->destination) != 0) + return FALSE; + + return TRUE; +} + +static void +bus_matchmaker_remove_rule_link (BusMatchmaker *matchmaker, + DBusList *link) +{ + BusMatchRule *rule = link->data; + + bus_connection_remove_match_rule (rule->matches_go_to, rule); + _dbus_list_remove_link (&matchmaker->all_rules, link); + +#ifdef DBUS_ENABLE_VERBOSE_MODE + { + char *s = match_rule_to_string (rule); + + _dbus_verbose ("Removed match rule %s for connection %p\n", + s, rule->matches_go_to); + dbus_free (s); + } +#endif + + bus_match_rule_unref (rule); +} + +void +bus_matchmaker_remove_rule (BusMatchmaker *matchmaker, + BusMatchRule *rule) +{ + bus_connection_remove_match_rule (rule->matches_go_to, rule); + _dbus_list_remove (&matchmaker->all_rules, rule); + +#ifdef DBUS_ENABLE_VERBOSE_MODE + { + char *s = match_rule_to_string (rule); + + _dbus_verbose ("Removed match rule %s for connection %p\n", + s, rule->matches_go_to); + dbus_free (s); + } +#endif + + bus_match_rule_unref (rule); +} + +/* Remove a single rule which is equal to the given rule by value */ +dbus_bool_t +bus_matchmaker_remove_rule_by_value (BusMatchmaker *matchmaker, + BusMatchRule *value, + DBusError *error) +{ + /* FIXME this is an unoptimized linear scan */ + + DBusList *link; + + /* we traverse backward because bus_connection_remove_match_rule() + * removes the most-recently-added rule + */ + link = _dbus_list_get_last_link (&matchmaker->all_rules); + while (link != NULL) + { + BusMatchRule *rule; + DBusList *prev; + + rule = link->data; + prev = _dbus_list_get_prev_link (&matchmaker->all_rules, link); + + if (match_rule_equal (rule, value)) + { + bus_matchmaker_remove_rule_link (matchmaker, link); + break; + } + + link = prev; + } + + if (link == NULL) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_NOT_FOUND, + "The given match rule wasn't found and can't be removed"); + return FALSE; + } + + return TRUE; +} + +void +bus_matchmaker_disconnected (BusMatchmaker *matchmaker, + DBusConnection *disconnected) +{ + DBusList *link; + + /* FIXME + * + * This scans all match rules on the bus. We could avoid that + * for the rules belonging to the connection, since we keep + * a list of those; but for the rules that just refer to + * the connection we'd need to do something more elaborate. + * + */ + + _dbus_assert (bus_connection_is_active (disconnected)); + + link = _dbus_list_get_first_link (&matchmaker->all_rules); + while (link != NULL) + { + BusMatchRule *rule; + DBusList *next; + + rule = link->data; + next = _dbus_list_get_next_link (&matchmaker->all_rules, link); + + if (rule->matches_go_to == disconnected) + { + bus_matchmaker_remove_rule_link (matchmaker, link); + } + else if (((rule->flags & BUS_MATCH_SENDER) && *rule->sender == ':') || + ((rule->flags & BUS_MATCH_DESTINATION) && *rule->destination == ':')) + { + /* The rule matches to/from a base service, see if it's the + * one being disconnected, since we know this service name + * will never be recycled. + */ + const char *name; + + name = bus_connection_get_name (disconnected); + _dbus_assert (name != NULL); /* because we're an active connection */ + + if (((rule->flags & BUS_MATCH_SENDER) && + strcmp (rule->sender, name) == 0) || + ((rule->flags & BUS_MATCH_DESTINATION) && + strcmp (rule->destination, name) == 0)) + { + bus_matchmaker_remove_rule_link (matchmaker, link); + } + } + + link = next; + } +} + +static dbus_bool_t +connection_is_primary_owner (DBusConnection *connection, + const char *service_name) +{ + BusService *service; + DBusString str; + BusRegistry *registry; + + registry = bus_connection_get_registry (connection); + + _dbus_string_init_const (&str, service_name); + service = bus_registry_lookup (registry, &str); + + if (service == NULL) + return FALSE; /* Service doesn't exist so connection can't own it. */ + + return bus_service_get_primary_owner (service) == connection; +} + +static dbus_bool_t +match_rule_matches (BusMatchRule *rule, + BusConnections *connections, + DBusConnection *sender, + DBusConnection *addressed_recipient, + DBusMessage *message) +{ + /* All features of the match rule are AND'd together, + * so FALSE if any of them don't match. + */ + + if (rule->flags & BUS_MATCH_MESSAGE_TYPE) + { + _dbus_assert (rule->message_type != DBUS_MESSAGE_TYPE_INVALID); + + if (rule->message_type != dbus_message_get_type (message)) + return FALSE; + } + + if (rule->flags & BUS_MATCH_INTERFACE) + { + const char *iface; + + _dbus_assert (rule->interface != NULL); + + iface = dbus_message_get_interface (message); + if (iface == NULL) + return FALSE; + + if (strcmp (iface, rule->interface) != 0) + return FALSE; + } + + if (rule->flags & BUS_MATCH_MEMBER) + { + const char *member; + + _dbus_assert (rule->member != NULL); + + member = dbus_message_get_member (message); + if (member == NULL) + return FALSE; + + if (strcmp (member, rule->member) != 0) + return FALSE; + } + + if (rule->flags & BUS_MATCH_SENDER) + { + _dbus_assert (rule->sender != NULL); + + if (!connection_is_primary_owner (sender, rule->sender)) + return FALSE; + } + + if (rule->flags & BUS_MATCH_DESTINATION) + { + const char *destination; + + _dbus_assert (rule->destination != NULL); + + if (addressed_recipient == NULL) + return FALSE; + + destination = dbus_message_get_destination (message); + if (destination == NULL) + return FALSE; + + if (!connection_is_primary_owner (addressed_recipient, rule->destination)) + return FALSE; + } + + if (rule->flags & BUS_MATCH_PATH) + { + const char *path; + + _dbus_assert (rule->path != NULL); + + path = dbus_message_get_path (message); + if (path == NULL) + return FALSE; + + if (strcmp (path, rule->path) != 0) + return FALSE; + } + + return TRUE; +} + +dbus_bool_t +bus_matchmaker_get_recipients (BusMatchmaker *matchmaker, + BusConnections *connections, + DBusConnection *sender, + DBusConnection *addressed_recipient, + DBusMessage *message, + DBusList **recipients_p) +{ + /* FIXME for now this is a wholly unoptimized linear search */ + /* Guessing the important optimization is to skip the signal-related + * match lists when processing method call and exception messages. + * So separate match rule lists for signals? + */ + + DBusList *link; + + _dbus_assert (*recipients_p == NULL); + + /* This avoids sending same message to the same connection twice. + * Purpose of the stamp instead of a bool is to avoid iterating over + * all connections resetting the bool each time. + */ + bus_connections_increment_stamp (connections); + + /* addressed_recipient is already receiving the message, don't add to list. + * NULL addressed_recipient means either bus driver, or this is a signal + * and thus lacks a specific addressed_recipient. + */ + if (addressed_recipient != NULL) + bus_connection_mark_stamp (addressed_recipient); + + link = _dbus_list_get_first_link (&matchmaker->all_rules); + while (link != NULL) + { + BusMatchRule *rule; + + rule = link->data; + +#ifdef DBUS_ENABLE_VERBOSE_MODE + { + char *s = match_rule_to_string (rule); + + _dbus_verbose ("Checking whether message matches rule %s for connection %p\n", + s, rule->matches_go_to); + dbus_free (s); + } +#endif + + if (match_rule_matches (rule, connections, + sender, addressed_recipient, message)) + { + _dbus_verbose ("Rule matched\n"); + + /* Append to the list if we haven't already */ + if (bus_connection_mark_stamp (rule->matches_go_to)) + { + if (!_dbus_list_append (recipients_p, rule->matches_go_to)) + goto nomem; + } +#ifdef DBUS_ENABLE_VERBOSE_MODE + else + { + _dbus_verbose ("Connection already receiving this message, so not adding again\n"); + } +#endif /* DBUS_ENABLE_VERBOSE_MODE */ + } + + link = _dbus_list_get_next_link (&matchmaker->all_rules, link); + } + + return TRUE; + + nomem: + _dbus_list_clear (recipients_p); + return FALSE; +} + +#ifdef DBUS_BUILD_TESTS +#include "test.h" + +dbus_bool_t +bus_signals_test (const DBusString *test_data_dir) +{ + BusMatchmaker *matchmaker; + + matchmaker = bus_matchmaker_new (); + bus_matchmaker_ref (matchmaker); + bus_matchmaker_unref (matchmaker); + bus_matchmaker_unref (matchmaker); + + return TRUE; +} + +#endif /* DBUS_BUILD_TESTS */ + |