Consistently include <config.h> in all C source files and never in header files.
[platform/upstream/dbus.git] / bus / signals.c
index d0845b1..6f97b2b 100644 (file)
  *
  * 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
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
+
+#include <config.h>
 #include "signals.h"
 #include "services.h"
 #include "utils.h"
@@ -40,10 +42,13 @@ struct BusMatchRule
   char *destination;
   char *path;
 
+  unsigned int *arg_lens;
   char **args;
   int args_len;
 };
 
+#define BUS_MATCH_ARG_IS_PATH  0x8000000u
+
 BusMatchRule*
 bus_match_rule_new (DBusConnection *matches_go_to)
 {
@@ -86,6 +91,7 @@ bus_match_rule_unref (BusMatchRule *rule)
       dbus_free (rule->sender);
       dbus_free (rule->destination);
       dbus_free (rule->path);
+      dbus_free (rule->arg_lens);
 
       /* can't use dbus_free_string_array() since there
        * are embedded NULL
@@ -205,15 +211,19 @@ match_rule_to_string (BusMatchRule *rule)
         {
           if (rule->args[i] != NULL)
             {
+              dbus_bool_t is_path;
+
               if (_dbus_string_get_length (&str) > 0)
                 {
                   if (!_dbus_string_append (&str, ","))
                     goto nomem;
                 }
+
+              is_path = (rule->arg_lens[i] & BUS_MATCH_ARG_IS_PATH) != 0;
               
               if (!_dbus_string_append_printf (&str,
-                                               "arg%d='%s'",
-                                               i,
+                                               "arg%d%s='%s'",
+                                               i, is_path ? "path" : "",
                                                rule->args[i]))
                 goto nomem;
             }
@@ -346,23 +356,22 @@ bus_match_rule_set_path (BusMatchRule *rule,
 }
 
 dbus_bool_t
-bus_match_rule_set_arg (BusMatchRule *rule,
-                        int           arg,
-                        const char   *value)
+bus_match_rule_set_arg (BusMatchRule     *rule,
+                        int                arg,
+                        const DBusString *value,
+                        dbus_bool_t       is_path)
 {
+  int length;
   char *new;
 
   _dbus_assert (value != NULL);
 
-  new = _dbus_strdup (value);
-  if (new == NULL)
-    return FALSE;
-
   /* args_len is the number of args not including null termination
    * in the char**
    */
   if (arg >= rule->args_len)
     {
+      unsigned int *new_arg_lens;
       char **new_args;
       int new_args_len;
       int i;
@@ -371,12 +380,9 @@ bus_match_rule_set_arg (BusMatchRule *rule,
 
       /* add another + 1 here for null termination */
       new_args = dbus_realloc (rule->args,
-                               sizeof(rule->args[0]) * (new_args_len + 1));
+                               sizeof (char *) * (new_args_len + 1));
       if (new_args == NULL)
-        {
-          dbus_free (new);
-          return FALSE;
-        }
+        return FALSE;
 
       /* NULL the new slots */
       i = rule->args_len;
@@ -387,16 +393,42 @@ bus_match_rule_set_arg (BusMatchRule *rule,
         }
       
       rule->args = new_args;
+
+      /* and now add to the lengths */
+      new_arg_lens = dbus_realloc (rule->arg_lens,
+                                   sizeof (int) * (new_args_len + 1));
+
+      if (new_arg_lens == NULL)
+        return FALSE;
+
+      /* zero the new slots */
+      i = rule->args_len;
+      while (i <= new_args_len) /* <= for null termination */
+        {
+          new_arg_lens[i] = 0;
+          ++i;
+        }
+
+      rule->arg_lens = new_arg_lens;
       rule->args_len = new_args_len;
     }
 
+  length = _dbus_string_get_length (value);
+  if (!_dbus_string_copy_data (value, &new))
+    return FALSE;
+
   rule->flags |= BUS_MATCH_ARGS;
 
   dbus_free (rule->args[arg]);
+  rule->arg_lens[arg] = length;
   rule->args[arg] = new;
 
+  if (is_path)
+    rule->arg_lens[arg] |= BUS_MATCH_ARG_IS_PATH;
+
   /* NULL termination didn't get busted */
   _dbus_assert (rule->args[rule->args_len] == NULL);
+  _dbus_assert (rule->arg_lens[rule->args_len] == 0);
 
   return TRUE;
 }
@@ -688,8 +720,10 @@ bus_match_rule_parse_arg_match (BusMatchRule     *rule,
                                 const DBusString *value,
                                 DBusError        *error)
 {
+  dbus_bool_t is_path;
   DBusString key_str;
   unsigned long arg;
+  int length;
   int end;
 
   /* For now, arg0='foo' always implies that 'foo' is a
@@ -701,6 +735,7 @@ bus_match_rule_parse_arg_match (BusMatchRule     *rule,
   /* First we need to parse arg0 = 0, arg27 = 27 */
 
   _dbus_string_init_const (&key_str, key);
+  length = _dbus_string_get_length (&key_str);
 
   if (_dbus_string_get_length (&key_str) < 4)
     {
@@ -709,14 +744,24 @@ bus_match_rule_parse_arg_match (BusMatchRule     *rule,
       goto failed;
     }
 
-  if (!_dbus_string_parse_uint (&key_str, 3, &arg, &end) ||
-      end != _dbus_string_get_length (&key_str))
+  if (!_dbus_string_parse_uint (&key_str, 3, &arg, &end))
     {
       dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
                       "Key '%s' in match rule starts with 'arg' but could not parse arg number. Should be 'arg0' or 'arg7' for example.\n", key);
       goto failed;
     }
 
+  if (end != length &&
+      ((end + 4) != length ||
+       !_dbus_string_ends_with_c_str (&key_str, "path")))
+    {
+      dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                      "Key '%s' in match rule contains junk after argument number. Only 'path' is optionally valid ('arg0path' for example).\n", key);
+      goto failed;
+    }
+
+  is_path = end != length;
+
   /* If we didn't check this we could allocate a huge amount of RAM */
   if (arg > DBUS_MAXIMUM_MATCH_RULE_ARG_NUMBER)
     {
@@ -730,12 +775,11 @@ bus_match_rule_parse_arg_match (BusMatchRule     *rule,
       rule->args[arg] != NULL)
     {
       dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
-                      "Key '%s' specified twice in match rule\n", key);
+                      "Argument %d matched more than once in match rule\n", key);
       goto failed;
     }
   
-  if (!bus_match_rule_set_arg (rule, arg,
-                               _dbus_string_get_const_data (value)))
+  if (!bus_match_rule_set_arg (rule, arg, value, is_path))
     {
       BUS_SET_OOM (error);
       goto failed;
@@ -976,25 +1020,173 @@ bus_match_rule_parse (DBusConnection   *matches_go_to,
   return rule;
 }
 
+typedef struct RulePool RulePool;
+struct RulePool
+{
+  /* Maps non-NULL interface names to non-NULL (DBusList **)s */
+  DBusHashTable *rules_by_iface;
+
+  /* List of BusMatchRules which don't specify an interface */
+  DBusList *rules_without_iface;
+};
+
 struct BusMatchmaker
 {
   int refcount;
 
-  DBusList *all_rules;
+  /* Pools of rules, grouped by the type of message they match. 0
+   * (DBUS_MESSAGE_TYPE_INVALID) represents rules that do not specify a message
+   * type.
+   */
+  RulePool rules_by_type[DBUS_NUM_MESSAGE_TYPES];
 };
 
+static void
+rule_list_free (DBusList **rules)
+{
+  while (*rules != NULL)
+    {
+      BusMatchRule *rule;
+
+      rule = (*rules)->data;
+      bus_match_rule_unref (rule);
+      _dbus_list_remove_link (rules, *rules);
+    }
+}
+
+static void
+rule_list_ptr_free (DBusList **list)
+{
+  /* We have to cope with NULL because the hash table frees the "existing"
+   * value (which is NULL) when creating a new table entry...
+   */
+  if (list != NULL)
+    {
+      rule_list_free (list);
+      dbus_free (list);
+    }
+}
+
 BusMatchmaker*
 bus_matchmaker_new (void)
 {
   BusMatchmaker *matchmaker;
+  int i;
 
   matchmaker = dbus_new0 (BusMatchmaker, 1);
   if (matchmaker == NULL)
     return NULL;
 
   matchmaker->refcount = 1;
-  
+
+  for (i = DBUS_MESSAGE_TYPE_INVALID; i < DBUS_NUM_MESSAGE_TYPES; i++)
+    {
+      RulePool *p = matchmaker->rules_by_type + i;
+
+      p->rules_by_iface = _dbus_hash_table_new (DBUS_HASH_STRING,
+          dbus_free, (DBusFreeFunction) rule_list_ptr_free);
+
+      if (p->rules_by_iface == NULL)
+        goto nomem;
+    }
+
   return matchmaker;
+
+ nomem:
+  for (i = DBUS_MESSAGE_TYPE_INVALID; i < DBUS_NUM_MESSAGE_TYPES; i++)
+    {
+      RulePool *p = matchmaker->rules_by_type + i;
+
+      if (p->rules_by_iface == NULL)
+        break;
+      else
+        _dbus_hash_table_unref (p->rules_by_iface);
+    }
+
+  return NULL;
+}
+
+static DBusList **
+bus_matchmaker_get_rules (BusMatchmaker *matchmaker,
+                          int            message_type,
+                          const char    *interface,
+                          dbus_bool_t    create)
+{
+  RulePool *p;
+
+  _dbus_assert (message_type >= 0);
+  _dbus_assert (message_type < DBUS_NUM_MESSAGE_TYPES);
+
+  _dbus_verbose ("Looking up rules for message_type %d, interface %s\n",
+                 message_type,
+                 interface != NULL ? interface : "<null>");
+
+  p = matchmaker->rules_by_type + message_type;
+
+  if (interface == NULL)
+    {
+      return &p->rules_without_iface;
+    }
+  else
+    {
+      DBusList **list;
+
+      list = _dbus_hash_table_lookup_string (p->rules_by_iface, interface);
+
+      if (list == NULL && create)
+        {
+          char *dupped_interface;
+
+          list = dbus_new0 (DBusList *, 1);
+          if (list == NULL)
+            return NULL;
+
+          dupped_interface = _dbus_strdup (interface);
+          if (dupped_interface == NULL)
+            {
+              dbus_free (list);
+              return NULL;
+            }
+
+          _dbus_verbose ("Adding list for type %d, iface %s\n", message_type,
+                         interface);
+
+          if (!_dbus_hash_table_insert_string (p->rules_by_iface,
+                                               dupped_interface, list))
+            {
+              dbus_free (list);
+              dbus_free (dupped_interface);
+              return NULL;
+            }
+        }
+
+      return list;
+    }
+}
+
+static void
+bus_matchmaker_gc_rules (BusMatchmaker *matchmaker,
+                         int            message_type,
+                         const char    *interface,
+                         DBusList     **rules)
+{
+  RulePool *p;
+
+  if (interface == NULL)
+    return;
+
+  if (*rules != NULL)
+    return;
+
+  _dbus_verbose ("GCing HT entry for message_type %u, interface %s\n",
+                 message_type, interface);
+
+  p = matchmaker->rules_by_type + message_type;
+
+  _dbus_assert (_dbus_hash_table_lookup_string (p->rules_by_iface, interface)
+      == rules);
+
+  _dbus_hash_table_remove_string (p->rules_by_iface, interface);
 }
 
 BusMatchmaker *
@@ -1015,14 +1207,14 @@ bus_matchmaker_unref (BusMatchmaker *matchmaker)
   matchmaker->refcount -= 1;
   if (matchmaker->refcount == 0)
     {
-      while (matchmaker->all_rules != NULL)
+      int i;
+
+      for (i = DBUS_MESSAGE_TYPE_INVALID; i < DBUS_NUM_MESSAGE_TYPES; i++)
         {
-          BusMatchRule *rule;
+          RulePool *p = matchmaker->rules_by_type + i;
 
-          rule = matchmaker->all_rules->data;
-          bus_match_rule_unref (rule);
-          _dbus_list_remove_link (&matchmaker->all_rules,
-                                  matchmaker->all_rules);
+          _dbus_hash_table_unref (p->rules_by_iface);
+          rule_list_free (&p->rules_without_iface);
         }
 
       dbus_free (matchmaker);
@@ -1034,17 +1226,31 @@ dbus_bool_t
 bus_matchmaker_add_rule (BusMatchmaker   *matchmaker,
                          BusMatchRule    *rule)
 {
+  DBusList **rules;
+
   _dbus_assert (bus_connection_is_active (rule->matches_go_to));
 
-  if (!_dbus_list_append (&matchmaker->all_rules, rule))
+  _dbus_verbose ("Adding rule with message_type %d, interface %s\n",
+                 rule->message_type,
+                 rule->interface != NULL ? rule->interface : "<null>");
+
+  rules = bus_matchmaker_get_rules (matchmaker, rule->message_type,
+                                    rule->interface, TRUE);
+
+  if (rules == NULL)
+    return FALSE;
+
+  if (!_dbus_list_append (rules, rule))
     return FALSE;
 
   if (!bus_connection_add_match_rule (rule->matches_go_to, rule))
     {
-      _dbus_list_remove_last (&matchmaker->all_rules, rule);
+      _dbus_list_remove_last (rules, rule);
+      bus_matchmaker_gc_rules (matchmaker, rule->message_type,
+                               rule->interface, rules);
       return FALSE;
     }
-  
+
   bus_match_rule_ref (rule);
 
 #ifdef DBUS_ENABLE_VERBOSE_MODE
@@ -1104,13 +1310,20 @@ match_rule_equal (BusMatchRule *a,
       i = 0;
       while (i < a->args_len)
         {
+          int length;
+
           if ((a->args[i] != NULL) != (b->args[i] != NULL))
             return FALSE;
 
+          if (a->arg_lens[i] != b->arg_lens[i])
+            return FALSE;
+
+          length = a->arg_lens[i] & ~BUS_MATCH_ARG_IS_PATH;
+
           if (a->args[i] != NULL)
             {
               _dbus_assert (b->args[i] != NULL);
-              if (strcmp (a->args[i], b->args[i]) != 0)
+              if (memcmp (a->args[i], b->args[i], length) != 0)
                 return FALSE;
             }
           
@@ -1122,13 +1335,13 @@ match_rule_equal (BusMatchRule *a,
 }
 
 static void
-bus_matchmaker_remove_rule_link (BusMatchmaker   *matchmaker,
+bus_matchmaker_remove_rule_link (DBusList       **rules,
                                  DBusList        *link)
 {
   BusMatchRule *rule = link->data;
   
   bus_connection_remove_match_rule (rule->matches_go_to, rule);
-  _dbus_list_remove_link (&matchmaker->all_rules, link);
+  _dbus_list_remove_link (rules, link);
 
 #ifdef DBUS_ENABLE_VERBOSE_MODE
   {
@@ -1147,8 +1360,25 @@ void
 bus_matchmaker_remove_rule (BusMatchmaker   *matchmaker,
                             BusMatchRule    *rule)
 {
+  DBusList **rules;
+
+  _dbus_verbose ("Removing rule with message_type %d, interface %s\n",
+                 rule->message_type,
+                 rule->interface != NULL ? rule->interface : "<null>");
+
   bus_connection_remove_match_rule (rule->matches_go_to, rule);
-  _dbus_list_remove (&matchmaker->all_rules, rule);
+
+  rules = bus_matchmaker_get_rules (matchmaker, rule->message_type,
+                                    rule->interface, FALSE);
+
+  /* We should only be asked to remove a rule by identity right after it was
+   * added, so there should be a list for it.
+   */
+  _dbus_assert (rules != NULL);
+
+  _dbus_list_remove (rules, rule);
+  bus_matchmaker_gc_rules (matchmaker, rule->message_type, rule->interface,
+      rules);
 
 #ifdef DBUS_ENABLE_VERBOSE_MODE
   {
@@ -1169,29 +1399,38 @@ bus_matchmaker_remove_rule_by_value (BusMatchmaker   *matchmaker,
                                      BusMatchRule    *value,
                                      DBusError       *error)
 {
-  /* FIXME this is an unoptimized linear scan */
+  DBusList **rules;
+  DBusList *link = NULL;
 
-  DBusList *link;
+  _dbus_verbose ("Removing rule by value with message_type %d, interface %s\n",
+                 value->message_type,
+                 value->interface != NULL ? value->interface : "<null>");
 
-  /* 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)
+  rules = bus_matchmaker_get_rules (matchmaker, value->message_type,
+      value->interface, FALSE);
+
+  if (rules != NULL)
     {
-      BusMatchRule *rule;
-      DBusList *prev;
+      /* we traverse backward because bus_connection_remove_match_rule()
+       * removes the most-recently-added rule
+       */
+      link = _dbus_list_get_last_link (rules);
+      while (link != NULL)
+        {
+          BusMatchRule *rule;
+          DBusList *prev;
 
-      rule = link->data;
-      prev = _dbus_list_get_prev_link (&matchmaker->all_rules, link);
+          rule = link->data;
+          prev = _dbus_list_get_prev_link (rules, link);
 
-      if (match_rule_equal (rule, value))
-        {
-          bus_matchmaker_remove_rule_link (matchmaker, link);
-          break;
-        }
+          if (match_rule_equal (rule, value))
+            {
+              bus_matchmaker_remove_rule_link (rules, link);
+              break;
+            }
 
-      link = prev;
+          link = prev;
+        }
     }
 
   if (link == NULL)
@@ -1201,38 +1440,30 @@ bus_matchmaker_remove_rule_by_value (BusMatchmaker   *matchmaker,
       return FALSE;
     }
 
+  bus_matchmaker_gc_rules (matchmaker, value->message_type, value->interface,
+      rules);
+
   return TRUE;
 }
 
-void
-bus_matchmaker_disconnected (BusMatchmaker   *matchmaker,
-                             DBusConnection  *disconnected)
+static void
+rule_list_remove_by_connection (DBusList       **rules,
+                                DBusConnection  *connection)
 {
   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);
+  link = _dbus_list_get_first_link (rules);
   while (link != NULL)
     {
       BusMatchRule *rule;
       DBusList *next;
 
       rule = link->data;
-      next = _dbus_list_get_next_link (&matchmaker->all_rules, link);
+      next = _dbus_list_get_next_link (rules, link);
 
-      if (rule->matches_go_to == disconnected)
+      if (rule->matches_go_to == connection)
         {
-          bus_matchmaker_remove_rule_link (matchmaker, link);
+          bus_matchmaker_remove_rule_link (rules, link);
         }
       else if (((rule->flags & BUS_MATCH_SENDER) && *rule->sender == ':') ||
                ((rule->flags & BUS_MATCH_DESTINATION) && *rule->destination == ':'))
@@ -1243,7 +1474,7 @@ bus_matchmaker_disconnected (BusMatchmaker   *matchmaker,
            */
           const char *name;
 
-          name = bus_connection_get_name (disconnected);
+          name = bus_connection_get_name (connection);
           _dbus_assert (name != NULL); /* because we're an active connection */
 
           if (((rule->flags & BUS_MATCH_SENDER) &&
@@ -1251,7 +1482,7 @@ bus_matchmaker_disconnected (BusMatchmaker   *matchmaker,
               ((rule->flags & BUS_MATCH_DESTINATION) &&
                strcmp (rule->destination, name) == 0))
             {
-              bus_matchmaker_remove_rule_link (matchmaker, link);
+              bus_matchmaker_remove_rule_link (rules, link);
             }
         }
 
@@ -1259,6 +1490,44 @@ bus_matchmaker_disconnected (BusMatchmaker   *matchmaker,
     }
 }
 
+void
+bus_matchmaker_disconnected (BusMatchmaker   *matchmaker,
+                             DBusConnection  *connection)
+{
+  int i;
+
+  /* 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 (connection));
+
+  _dbus_verbose ("Removing all rules for connection %p\n", connection);
+
+  for (i = DBUS_MESSAGE_TYPE_INVALID; i < DBUS_NUM_MESSAGE_TYPES; i++)
+    {
+      RulePool *p = matchmaker->rules_by_type + i;
+      DBusHashIter iter;
+
+      rule_list_remove_by_connection (&p->rules_without_iface, connection);
+
+      _dbus_hash_iter_init (p->rules_by_iface, &iter);
+      while (_dbus_hash_iter_next (&iter))
+        {
+          DBusList **items = _dbus_hash_iter_get_value (&iter);
+
+          rule_list_remove_by_connection (items, connection);
+
+          if (*items == NULL)
+            _dbus_hash_iter_remove_entry (&iter);
+        }
+    }
+}
+
 static dbus_bool_t
 connection_is_primary_owner (DBusConnection *connection,
                              const char     *service_name)
@@ -1284,8 +1553,11 @@ static dbus_bool_t
 match_rule_matches (BusMatchRule    *rule,
                     DBusConnection  *sender,
                     DBusConnection  *addressed_recipient,
-                    DBusMessage     *message)
+                    DBusMessage     *message,
+                    BusMatchFlags    already_matched)
 {
+  int flags;
+
   /* All features of the match rule are AND'd together,
    * so FALSE if any of them don't match.
    */
@@ -1294,8 +1566,11 @@ match_rule_matches (BusMatchRule    *rule,
    * or for addressed_recipient may mean a message with no
    * specific recipient (i.e. a signal)
    */
-  
-  if (rule->flags & BUS_MATCH_MESSAGE_TYPE)
+
+  /* Don't bother re-matching features we've already checked implicitly. */
+  flags = rule->flags & (~already_matched);
+
+  if (flags & BUS_MATCH_MESSAGE_TYPE)
     {
       _dbus_assert (rule->message_type != DBUS_MESSAGE_TYPE_INVALID);
 
@@ -1303,7 +1578,7 @@ match_rule_matches (BusMatchRule    *rule,
         return FALSE;
     }
 
-  if (rule->flags & BUS_MATCH_INTERFACE)
+  if (flags & BUS_MATCH_INTERFACE)
     {
       const char *iface;
 
@@ -1317,7 +1592,7 @@ match_rule_matches (BusMatchRule    *rule,
         return FALSE;
     }
 
-  if (rule->flags & BUS_MATCH_MEMBER)
+  if (flags & BUS_MATCH_MEMBER)
     {
       const char *member;
 
@@ -1331,7 +1606,7 @@ match_rule_matches (BusMatchRule    *rule,
         return FALSE;
     }
 
-  if (rule->flags & BUS_MATCH_SENDER)
+  if (flags & BUS_MATCH_SENDER)
     {
       _dbus_assert (rule->sender != NULL);
 
@@ -1348,7 +1623,7 @@ match_rule_matches (BusMatchRule    *rule,
         }
     }
 
-  if (rule->flags & BUS_MATCH_DESTINATION)
+  if (flags & BUS_MATCH_DESTINATION)
     {
       const char *destination;
 
@@ -1371,7 +1646,7 @@ match_rule_matches (BusMatchRule    *rule,
         }
     }
 
-  if (rule->flags & BUS_MATCH_PATH)
+  if (flags & BUS_MATCH_PATH)
     {
       const char *path;
 
@@ -1385,7 +1660,7 @@ match_rule_matches (BusMatchRule    *rule,
         return FALSE;
     }
 
-  if (rule->flags & BUS_MATCH_ARGS)
+  if (flags & BUS_MATCH_ARGS)
     {
       int i;
       DBusMessageIter iter;
@@ -1399,14 +1674,19 @@ match_rule_matches (BusMatchRule    *rule,
         {
           int current_type;
           const char *expected_arg;
+          int expected_length;
+          dbus_bool_t is_path;
 
           expected_arg = rule->args[i];
+          expected_length = rule->arg_lens[i] & ~BUS_MATCH_ARG_IS_PATH;
+          is_path = (rule->arg_lens[i] & BUS_MATCH_ARG_IS_PATH) != 0;
           
           current_type = dbus_message_iter_get_arg_type (&iter);
 
           if (expected_arg != NULL)
             {
               const char *actual_arg;
+              int actual_length;
               
               if (current_type != DBUS_TYPE_STRING)
                 return FALSE;
@@ -1415,8 +1695,29 @@ match_rule_matches (BusMatchRule    *rule,
               dbus_message_iter_get_basic (&iter, &actual_arg);
               _dbus_assert (actual_arg != NULL);
 
-              if (strcmp (expected_arg, actual_arg) != 0)
-                return FALSE;
+              actual_length = strlen (actual_arg);
+
+              if (is_path)
+                {
+                  if (actual_length < expected_length &&
+                      actual_arg[actual_length - 1] != '/')
+                    return FALSE;
+
+                  if (expected_length < actual_length &&
+                      expected_arg[expected_length - 1] != '/')
+                    return FALSE;
+
+                  if (memcmp (actual_arg, expected_arg,
+                              MIN (actual_length, expected_length)) != 0)
+                    return FALSE;
+                }
+              else
+                {
+                  if (expected_length != actual_length ||
+                      memcmp (expected_arg, actual_arg, expected_length) != 0)
+                    return FALSE;
+                }
+
             }
           
           if (current_type != DBUS_TYPE_INVALID)
@@ -1429,38 +1730,19 @@ match_rule_matches (BusMatchRule    *rule,
   return TRUE;
 }
 
-dbus_bool_t
-bus_matchmaker_get_recipients (BusMatchmaker   *matchmaker,
-                               BusConnections  *connections,
-                               DBusConnection  *sender,
-                               DBusConnection  *addressed_recipient,
-                               DBusMessage     *message,
-                               DBusList       **recipients_p)
+static dbus_bool_t
+get_recipients_from_list (DBusList       **rules,
+                          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);
+  if (rules == NULL)
+    return TRUE;
 
-  /* 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);
+  link = _dbus_list_get_first_link (rules);
   while (link != NULL)
     {
       BusMatchRule *rule;
@@ -1470,23 +1752,24 @@ bus_matchmaker_get_recipients (BusMatchmaker   *matchmaker,
 #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,
-                              sender, addressed_recipient, message))
+                              sender, addressed_recipient, message,
+                              BUS_MATCH_MESSAGE_TYPE | BUS_MATCH_INTERFACE))
         {
           _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;
+                return FALSE;
             }
 #ifdef DBUS_ENABLE_VERBOSE_MODE
           else
@@ -1496,14 +1779,72 @@ bus_matchmaker_get_recipients (BusMatchmaker   *matchmaker,
 #endif /* DBUS_ENABLE_VERBOSE_MODE */
         }
 
-      link = _dbus_list_get_next_link (&matchmaker->all_rules, link);
+      link = _dbus_list_get_next_link (rules, link);
     }
 
   return TRUE;
+}
 
- nomem:
-  _dbus_list_clear (recipients_p);
-  return FALSE;
+dbus_bool_t
+bus_matchmaker_get_recipients (BusMatchmaker   *matchmaker,
+                               BusConnections  *connections,
+                               DBusConnection  *sender,
+                               DBusConnection  *addressed_recipient,
+                               DBusMessage     *message,
+                               DBusList       **recipients_p)
+{
+  int type;
+  const char *interface;
+  DBusList **neither, **just_type, **just_iface, **both;
+
+  _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);
+
+  type = dbus_message_get_type (message);
+  interface = dbus_message_get_interface (message);
+
+  neither = bus_matchmaker_get_rules (matchmaker, DBUS_MESSAGE_TYPE_INVALID,
+      NULL, FALSE);
+  just_type = just_iface = both = NULL;
+
+  if (interface != NULL)
+    just_iface = bus_matchmaker_get_rules (matchmaker,
+        DBUS_MESSAGE_TYPE_INVALID, interface, FALSE);
+
+  if (type > DBUS_MESSAGE_TYPE_INVALID && type < DBUS_NUM_MESSAGE_TYPES)
+    {
+      just_type = bus_matchmaker_get_rules (matchmaker, type, NULL, FALSE);
+
+      if (interface != NULL)
+        both = bus_matchmaker_get_rules (matchmaker, type, interface, FALSE);
+    }
+
+  if (!(get_recipients_from_list (neither, sender, addressed_recipient,
+                                  message, recipients_p) &&
+        get_recipients_from_list (just_iface, sender, addressed_recipient,
+                                  message, recipients_p) &&
+        get_recipients_from_list (just_type, sender, addressed_recipient,
+                                  message, recipients_p) &&
+        get_recipients_from_list (both, sender, addressed_recipient,
+                                  message, recipients_p)))
+    {
+      _dbus_list_clear (recipients_p);
+      return FALSE;
+    }
+
+  return TRUE;
 }
 
 #ifdef DBUS_BUILD_TESTS
@@ -1868,7 +2209,7 @@ check_matches (dbus_bool_t  expected_to_match,
   _dbus_assert (rule != NULL);
 
   /* We can't test sender/destination rules since we pass NULL here */
-  matched = match_rule_matches (rule, NULL, NULL, message);
+  matched = match_rule_matches (rule, NULL, NULL, message, 0);
 
   if (matched != expected_to_match)
     {