forgot to add files...
[platform/upstream/dbus.git] / bus / signals.c
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
27 struct 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
43 BusMatchRule*
44 bus_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
58 void
59 bus_match_rule_ref (BusMatchRule *rule)
60 {
61   _dbus_assert (rule->refcount > 0);
62
63   rule->refcount += 1;
64 }
65
66 void
67 bus_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
84 static char*
85 match_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
182 dbus_bool_t
183 bus_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
193 dbus_bool_t
194 bus_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
212 dbus_bool_t
213 bus_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
231 dbus_bool_t
232 bus_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
250 dbus_bool_t
251 bus_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
269 dbus_bool_t
270 bus_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  */
296 BusMatchRule*
297 bus_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
322 struct BusMatchmaker
323 {
324   int refcount;
325
326   DBusList *all_rules;
327 };
328
329 BusMatchmaker*
330 bus_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
343 void
344 bus_matchmaker_ref (BusMatchmaker *matchmaker)
345 {
346   _dbus_assert (matchmaker->refcount > 0);
347
348   matchmaker->refcount += 1;
349 }
350
351 void
352 bus_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. */
374 dbus_bool_t
375 bus_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
404 static dbus_bool_t
405 match_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
438 static void
439 bus_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
460 void
461 bus_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 */
481 dbus_bool_t
482 bus_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
521 void
522 bus_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
576 static dbus_bool_t
577 connection_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
595 static dbus_bool_t
596 match_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
684 dbus_bool_t
685 bus_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   /* Guessing the important optimization is to skip the signal-related
694    * match lists when processing method call and exception messages.
695    * So separate match rule lists for signals?
696    */
697   
698   DBusList *link;
699
700   _dbus_assert (*recipients_p == NULL);
701
702   /* This avoids sending same message to the same connection twice.
703    * Purpose of the stamp instead of a bool is to avoid iterating over
704    * all connections resetting the bool each time.
705    */
706   bus_connections_increment_stamp (connections);
707
708   /* addressed_recipient is already receiving the message, don't add to list.
709    * NULL addressed_recipient means either bus driver, or this is a signal
710    * and thus lacks a specific addressed_recipient.
711    */
712   if (addressed_recipient != NULL)
713     bus_connection_mark_stamp (addressed_recipient);
714
715   link = _dbus_list_get_first_link (&matchmaker->all_rules);
716   while (link != NULL)
717     {
718       BusMatchRule *rule;
719
720       rule = link->data;
721
722 #ifdef DBUS_ENABLE_VERBOSE_MODE
723       {
724         char *s = match_rule_to_string (rule);
725         
726         _dbus_verbose ("Checking whether message matches rule %s for connection %p\n",
727                        s, rule->matches_go_to);
728         dbus_free (s);
729       }
730 #endif
731       
732       if (match_rule_matches (rule, connections,
733                               sender, addressed_recipient, message))
734         {
735           _dbus_verbose ("Rule matched\n");
736           
737           /* Append to the list if we haven't already */
738           if (bus_connection_mark_stamp (rule->matches_go_to))
739             {
740               if (!_dbus_list_append (recipients_p, rule->matches_go_to))
741                 goto nomem;
742             }
743 #ifdef DBUS_ENABLE_VERBOSE_MODE
744           else
745             {
746               _dbus_verbose ("Connection already receiving this message, so not adding again\n");
747             }
748 #endif /* DBUS_ENABLE_VERBOSE_MODE */
749         }
750
751       link = _dbus_list_get_next_link (&matchmaker->all_rules, link);
752     }
753
754   return TRUE;
755
756  nomem:
757   _dbus_list_clear (recipients_p);
758   return FALSE;
759 }
760
761 #ifdef DBUS_BUILD_TESTS
762 #include "test.h"
763
764 dbus_bool_t
765 bus_signals_test (const DBusString *test_data_dir)
766 {
767   BusMatchmaker *matchmaker;
768
769   matchmaker = bus_matchmaker_new ();
770   bus_matchmaker_ref (matchmaker);
771   bus_matchmaker_unref (matchmaker);
772   bus_matchmaker_unref (matchmaker);
773   
774   return TRUE;
775 }
776
777 #endif /* DBUS_BUILD_TESTS */
778