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