2003-04-10 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / bus / services.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* services.c  Service management
3  *
4  * Copyright (C) 2003  Red Hat, Inc.
5  * Copyright (C) 2003  CodeFactory AB
6  *
7  * Licensed under the Academic Free License version 1.2
8  * 
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  * 
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  *
23  */
24 #include <dbus/dbus-hash.h>
25 #include <dbus/dbus-list.h>
26 #include <dbus/dbus-mempool.h>
27
28 #include "driver.h"
29 #include "services.h"
30 #include "connection.h"
31 #include "utils.h"
32 #include "activation.h"
33
34 struct BusService
35 {
36   int refcount;
37
38   BusRegistry *registry;
39   char *name;
40   DBusList *owners;
41   
42   unsigned int prohibit_replacement : 1;
43 };
44
45 struct BusRegistry
46 {
47   int refcount;
48
49   BusContext *context;
50   
51   DBusHashTable *service_hash;
52   DBusMemPool   *service_pool;
53 };
54
55 BusRegistry*
56 bus_registry_new (BusContext *context)
57 {
58   BusRegistry *registry;
59
60   registry = dbus_new0 (BusRegistry, 1);
61   if (registry == NULL)
62     return NULL;
63
64   registry->refcount = 1;
65   registry->context = context;
66   
67   registry->service_hash = _dbus_hash_table_new (DBUS_HASH_STRING,
68                                                  NULL, NULL);
69   if (registry->service_hash == NULL)
70     goto failed;
71   
72   registry->service_pool = _dbus_mem_pool_new (sizeof (BusService),
73                                                TRUE);
74   if (registry->service_pool == NULL)
75     goto failed;
76
77   return registry;
78
79  failed:
80   bus_registry_unref (registry);
81   return NULL;
82 }
83
84 void
85 bus_registry_ref (BusRegistry *registry)
86 {
87   _dbus_assert (registry->refcount > 0);
88   registry->refcount += 1;
89 }
90
91 void
92 bus_registry_unref  (BusRegistry *registry)
93 {
94   _dbus_assert (registry->refcount > 0);
95   registry->refcount -= 1;
96
97   if (registry->refcount == 0)
98     {
99       if (registry->service_hash)
100         _dbus_hash_table_unref (registry->service_hash);
101       if (registry->service_pool)
102         _dbus_mem_pool_free (registry->service_pool);
103
104       dbus_free (registry);
105     }
106 }
107
108 BusService*
109 bus_registry_lookup (BusRegistry      *registry,
110                      const DBusString *service_name)
111 {
112   BusService *service;
113
114   service = _dbus_hash_table_lookup_string (registry->service_hash,
115                                             _dbus_string_get_const_data (service_name));
116
117   return service;
118 }
119
120 BusService*
121 bus_registry_ensure (BusRegistry               *registry,
122                      const DBusString          *service_name,
123                      DBusConnection            *owner_if_created,
124                      BusTransaction            *transaction,
125                      DBusError                 *error)
126 {
127   BusService *service;
128
129   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
130   
131   _dbus_assert (owner_if_created != NULL);
132   _dbus_assert (transaction != NULL);
133
134   service = _dbus_hash_table_lookup_string (registry->service_hash,
135                                             _dbus_string_get_const_data (service_name));
136   if (service != NULL)
137     return service;
138   
139   service = _dbus_mem_pool_alloc (registry->service_pool);
140   if (service == NULL)
141     {
142       BUS_SET_OOM (error);
143       return NULL;
144     }
145
146   service->registry = registry;  
147   service->refcount = 1;
148   
149   if (!_dbus_string_copy_data (service_name, &service->name))
150     {
151       _dbus_mem_pool_dealloc (registry->service_pool, service);
152       BUS_SET_OOM (error);
153       return NULL;
154     }
155
156   if (!bus_driver_send_service_created (service->name, transaction, error))
157     {
158       bus_service_unref (service);
159       return NULL;
160     }
161
162   if (!bus_activation_service_created (bus_context_get_activation (registry->context),
163                                        service->name, transaction, error))
164     {
165       bus_service_unref (service);
166       return NULL;
167     }
168   
169   if (!bus_service_add_owner (service, owner_if_created,
170                               transaction, error))
171     {
172       bus_service_unref (service);
173       return NULL;
174     }
175   
176   if (!_dbus_hash_table_insert_string (registry->service_hash,
177                                        service->name,
178                                        service))
179     {
180       /* The add_owner gets reverted on transaction cancel */
181       BUS_SET_OOM (error);
182       return NULL;
183     }
184   
185   return service;
186 }
187
188 void
189 bus_registry_foreach (BusRegistry               *registry,
190                       BusServiceForeachFunction  function,
191                       void                      *data)
192 {
193   DBusHashIter iter;
194   
195   _dbus_hash_iter_init (registry->service_hash, &iter);
196   while (_dbus_hash_iter_next (&iter))
197     {
198       BusService *service = _dbus_hash_iter_get_value (&iter);
199
200       (* function) (service, data);
201     }
202 }
203
204 dbus_bool_t
205 bus_registry_list_services (BusRegistry *registry,
206                             char      ***listp,
207                             int         *array_len)
208 {
209   int i, j, len;
210   char **retval;
211   DBusHashIter iter;
212    
213   len = _dbus_hash_table_get_n_entries (registry->service_hash);
214   retval = dbus_new (char *, len + 1);
215
216   if (retval == NULL)
217     return FALSE;
218
219   _dbus_hash_iter_init (registry->service_hash, &iter);
220   i = 0;
221   while (_dbus_hash_iter_next (&iter))
222     {
223       BusService *service = _dbus_hash_iter_get_value (&iter);
224
225       retval[i] = _dbus_strdup (service->name);
226       if (retval[i] == NULL)
227         goto error;
228
229       i++;
230     }
231
232   retval[i] = NULL;
233   
234   if (array_len)
235     *array_len = len;
236   
237   *listp = retval;
238   return TRUE;
239   
240  error:
241   for (j = 0; j < i; j++)
242     dbus_free (retval[i]);
243   dbus_free (retval);
244
245   return FALSE;
246 }
247
248 dbus_bool_t
249 bus_registry_acquire_service (BusRegistry      *registry,
250                               DBusConnection   *connection,
251                               const DBusString *service_name,
252                               dbus_uint32_t     flags,
253                               dbus_uint32_t    *result,
254                               BusTransaction   *transaction,
255                               DBusError        *error)
256 {
257   dbus_bool_t retval;
258   DBusConnection *old_owner;
259   DBusConnection *current_owner;
260   BusService *service;
261   
262   retval = FALSE;
263
264   if (_dbus_string_get_length (service_name) == 0)
265     {
266       dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
267                       "Zero-length service name is not allowed");
268       
269       _dbus_verbose ("Attempt to acquire zero-length service name\n");
270       
271       goto out;
272     }
273   
274   if (_dbus_string_get_byte (service_name, 0) == ':')
275     {
276       /* Not allowed; only base services can start with ':' */
277       dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
278                       "Cannot acquire a service starting with ':' such as \"%s\"",
279                       _dbus_string_get_const_data (service_name));
280       
281       _dbus_verbose ("Attempt to acquire invalid base service name \"%s\"",
282                      _dbus_string_get_const_data (service_name));
283       
284       goto out;
285     }
286   
287   service = bus_registry_lookup (registry, service_name);
288
289   if (service != NULL)
290     old_owner = bus_service_get_primary_owner (service);
291   else
292     old_owner = NULL;
293       
294   if (service == NULL)
295     {
296       service = bus_registry_ensure (registry,
297                                      service_name, connection, transaction, error);
298       if (service == NULL)
299         goto out;
300     }
301
302   current_owner = bus_service_get_primary_owner (service);
303
304   if (old_owner == NULL)
305     {
306       _dbus_assert (current_owner == connection);
307
308       bus_service_set_prohibit_replacement (service,
309                                             (flags & DBUS_SERVICE_FLAG_PROHIBIT_REPLACEMENT));      
310                         
311       *result = DBUS_SERVICE_REPLY_PRIMARY_OWNER;      
312     }
313   else if (old_owner == connection)
314     *result = DBUS_SERVICE_REPLY_ALREADY_OWNER;
315   else if (!((flags & DBUS_SERVICE_FLAG_REPLACE_EXISTING)))
316     *result = DBUS_SERVICE_REPLY_SERVICE_EXISTS;
317   else if (bus_service_get_prohibit_replacement (service))
318     {
319       /* Queue the connection */
320       if (!bus_service_add_owner (service, connection,
321                                   transaction, error))
322         goto out;
323       
324       *result = DBUS_SERVICE_REPLY_IN_QUEUE;
325     }
326   else
327     {
328       /* Replace the current owner */
329
330       /* We enqueue the new owner and remove the first one because
331        * that will cause ServiceAcquired and ServiceLost messages to
332        * be sent.
333        */
334       
335       if (!bus_service_add_owner (service, connection,
336                                   transaction, error))
337         goto out;
338
339       if (!bus_service_remove_owner (service, old_owner,
340                                      transaction, error))
341         goto out;
342       
343       _dbus_assert (connection == bus_service_get_primary_owner (service));
344       *result = DBUS_SERVICE_REPLY_PRIMARY_OWNER;
345     }
346
347   retval = TRUE;
348   
349  out:
350   return retval;
351 }
352
353 static void
354 bus_service_unlink_owner (BusService      *service,
355                           DBusConnection  *owner)
356 {
357   _dbus_list_remove_last (&service->owners, owner);
358   bus_connection_remove_owned_service (owner, service);
359 }
360
361 static void
362 bus_service_unlink (BusService *service)
363 {
364   _dbus_assert (service->owners == NULL);
365
366   /* the service may not be in the hash, if
367    * the failure causing transaction cancel
368    * was in the right place, but that's OK
369    */
370   _dbus_hash_table_remove_string (service->registry->service_hash,
371                                   service->name);
372   
373   bus_service_unref (service);
374 }
375
376 static void
377 bus_service_relink (BusService           *service,
378                     DBusPreallocatedHash *preallocated)
379 {
380   _dbus_assert (service->owners == NULL);
381   _dbus_assert (preallocated != NULL);
382
383   _dbus_hash_table_insert_string_preallocated (service->registry->service_hash,
384                                                preallocated,
385                                                service->name,
386                                                service);
387   
388   bus_service_ref (service);
389 }
390
391 typedef struct
392 {
393   DBusConnection *connection;
394   BusService *service;
395 } OwnershipCancelData;
396
397 static void
398 cancel_ownership (void *data)
399 {
400   OwnershipCancelData *d = data;
401
402   /* We don't need to send messages notifying of these
403    * changes, since we're reverting something that was
404    * cancelled (effectively never really happened)
405    */
406   bus_service_unlink_owner (d->service, d->connection);
407   
408   if (d->service->owners == NULL)
409     bus_service_unlink (d->service);
410 }
411
412 static void
413 free_ownership_cancel_data (void *data)
414 {
415   OwnershipCancelData *d = data;
416
417   dbus_connection_unref (d->connection);
418   bus_service_unref (d->service);
419   
420   dbus_free (d);
421 }
422
423 static dbus_bool_t
424 add_cancel_ownership_to_transaction (BusTransaction *transaction,
425                                      BusService     *service,
426                                      DBusConnection *connection)
427 {
428   OwnershipCancelData *d;
429
430   d = dbus_new (OwnershipCancelData, 1);
431   if (d == NULL)
432     return FALSE;
433   
434   d->service = service;
435   d->connection = connection;
436
437   if (!bus_transaction_add_cancel_hook (transaction, cancel_ownership, d,
438                                         free_ownership_cancel_data))
439     {
440       dbus_free (d);
441       return FALSE;
442     }
443
444   bus_service_ref (d->service);
445   dbus_connection_ref (d->connection);
446   
447   return TRUE;
448 }
449
450 /* this function is self-cancelling if you cancel the transaction */
451 dbus_bool_t
452 bus_service_add_owner (BusService     *service,
453                        DBusConnection *owner,
454                        BusTransaction *transaction,
455                        DBusError      *error)
456 {
457   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
458   
459  /* Send service acquired message first, OOM will result
460   * in cancelling the transaction
461   */
462   if (service->owners == NULL)
463     {
464       if (!bus_driver_send_service_acquired (owner, service->name, transaction, error))
465         return FALSE;
466     }
467   
468   if (!_dbus_list_append (&service->owners,
469                           owner))
470     {
471       BUS_SET_OOM (error);
472       return FALSE;
473     }
474
475   if (!bus_connection_add_owned_service (owner, service))
476     {
477       _dbus_list_remove_last (&service->owners, owner);
478       BUS_SET_OOM (error);
479       return FALSE;
480     }
481
482   if (!add_cancel_ownership_to_transaction (transaction,
483                                             service,
484                                             owner))
485     {
486       bus_service_unlink_owner (service, owner);
487       BUS_SET_OOM (error);
488       return FALSE;
489     }
490   
491   return TRUE;
492 }
493
494 typedef struct
495 {
496   DBusConnection *connection;
497   BusService     *service;
498   DBusConnection *before_connection; /* restore to position before this connection in owners list */
499   DBusList       *connection_link;
500   DBusList       *service_link;
501   DBusPreallocatedHash *hash_entry;
502 } OwnershipRestoreData;
503
504 static void
505 restore_ownership (void *data)
506 {
507   OwnershipRestoreData *d = data;
508   DBusList *link;
509
510   _dbus_assert (d->service_link != NULL);
511   _dbus_assert (d->connection_link != NULL);
512   
513   if (d->service->owners == NULL)
514     {
515       _dbus_assert (d->hash_entry != NULL);
516       bus_service_relink (d->service, d->hash_entry);
517     }
518   else
519     {
520       _dbus_assert (d->hash_entry == NULL);
521     }
522   
523   /* We don't need to send messages notifying of these
524    * changes, since we're reverting something that was
525    * cancelled (effectively never really happened)
526    */
527   link = _dbus_list_get_first_link (&d->service->owners);
528   while (link != NULL)
529     {
530       if (link->data == d->before_connection)
531         break;
532
533       link = _dbus_list_get_next_link (&d->service->owners, link);
534     }
535   
536   _dbus_list_insert_before_link (&d->service->owners, link, d->connection_link);
537
538   /* Note that removing then restoring this changes the order in which
539    * ServiceDeleted messages are sent on destruction of the
540    * connection.  This should be OK as the only guarantee there is
541    * that the base service is destroyed last, and we never even
542    * tentatively remove the base service.
543    */
544   bus_connection_add_owned_service_link (d->connection, d->service_link);
545   
546   d->hash_entry = NULL;
547   d->service_link = NULL;
548   d->connection_link = NULL;
549 }
550
551 static void
552 free_ownership_restore_data (void *data)
553 {
554   OwnershipRestoreData *d = data;
555
556   if (d->service_link)
557     _dbus_list_free_link (d->service_link);
558   if (d->connection_link)
559     _dbus_list_free_link (d->connection_link);
560   if (d->hash_entry)
561     _dbus_hash_table_free_preallocated_entry (d->service->registry->service_hash,
562                                               d->hash_entry);
563
564   dbus_connection_unref (d->connection);
565   bus_service_unref (d->service);
566   
567   dbus_free (d);
568 }
569
570 static dbus_bool_t
571 add_restore_ownership_to_transaction (BusTransaction *transaction,
572                                       BusService     *service,
573                                       DBusConnection *connection)
574 {
575   OwnershipRestoreData *d;
576   DBusList *link;
577
578   d = dbus_new (OwnershipRestoreData, 1);
579   if (d == NULL)
580     return FALSE;
581   
582   d->service = service;
583   d->connection = connection;
584   d->service_link = _dbus_list_alloc_link (service);
585   d->connection_link = _dbus_list_alloc_link (connection);
586   d->hash_entry = _dbus_hash_table_preallocate_entry (service->registry->service_hash);
587   
588   bus_service_ref (d->service);
589   dbus_connection_ref (d->connection);
590
591   d->before_connection = NULL;
592   link = _dbus_list_get_first_link (&service->owners);
593   while (link != NULL)
594     {
595       if (link->data == connection)
596         {
597           link = _dbus_list_get_next_link (&service->owners, link);
598
599           if (link)
600             d->before_connection = link->data;
601
602           break;
603         }
604       
605       link = _dbus_list_get_next_link (&service->owners, link);
606     }
607   
608   if (d->service_link == NULL ||
609       d->connection_link == NULL ||
610       d->hash_entry == NULL ||
611       !bus_transaction_add_cancel_hook (transaction, restore_ownership, d,
612                                         free_ownership_restore_data))
613     {
614       free_ownership_restore_data (d);
615       return FALSE;
616     }
617   
618   return TRUE;
619 }
620
621 /* this function is self-cancelling if you cancel the transaction */
622 dbus_bool_t
623 bus_service_remove_owner (BusService     *service,
624                           DBusConnection *owner,
625                           BusTransaction *transaction,
626                           DBusError      *error)
627 {
628   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
629   
630   /* We send out notifications before we do any work we
631    * might have to undo if the notification-sending failed
632    */
633   
634   /* Send service lost message */
635   if (bus_service_get_primary_owner (service) == owner)
636     {
637       if (!bus_driver_send_service_lost (owner, service->name,
638                                          transaction, error))
639         return FALSE;
640     }
641
642   if (service->owners == NULL)
643     {
644       _dbus_assert_not_reached ("Tried to remove owner of a service that has no owners");
645     }
646   else if (_dbus_list_length_is_one (&service->owners))
647     {
648       if (!bus_driver_send_service_deleted (service->name,
649                                             transaction, error))
650         return FALSE;
651     }
652   else
653     {
654       DBusList *link;
655       link = _dbus_list_get_first (&service->owners);
656       _dbus_assert (link != NULL);
657       link = _dbus_list_get_next_link (&service->owners, link);
658
659       _dbus_assert (link != NULL);
660
661       /* This will be our new owner */
662       if (!bus_driver_send_service_acquired (link->data,
663                                              service->name,
664                                              transaction,
665                                              error))
666         return FALSE;
667     }
668
669   if (!add_restore_ownership_to_transaction (transaction, service, owner))
670     {
671       BUS_SET_OOM (error);
672       return FALSE;
673     }
674   
675   bus_service_unlink_owner (service, owner);
676
677   if (service->owners == NULL)
678     bus_service_unlink (service);
679
680   return TRUE;
681 }
682
683 void
684 bus_service_ref (BusService *service)
685 {
686   _dbus_assert (service->refcount > 0);
687   
688   service->refcount += 1;
689 }
690
691 void
692 bus_service_unref (BusService *service)
693 {
694   _dbus_assert (service->refcount > 0);
695   
696   service->refcount -= 1;
697
698   if (service->refcount == 0)
699     {
700       _dbus_assert (service->owners == NULL);
701       
702       dbus_free (service->name);
703       _dbus_mem_pool_dealloc (service->registry->service_pool, service);
704     }
705 }
706
707 DBusConnection*
708 bus_service_get_primary_owner (BusService *service)
709 {
710   return _dbus_list_get_first (&service->owners);
711 }
712
713 const char*
714 bus_service_get_name (BusService *service)
715 {
716   return service->name;
717 }
718
719 void
720 bus_service_set_prohibit_replacement (BusService  *service,
721                                       dbus_bool_t  prohibit_replacement)
722 {
723   service->prohibit_replacement = prohibit_replacement != FALSE;
724 }
725
726 dbus_bool_t
727 bus_service_get_prohibit_replacement (BusService *service)
728 {
729   return service->prohibit_replacement;
730 }
731
732 dbus_bool_t
733 bus_service_has_owner (BusService     *service,
734                        DBusConnection *owner)
735 {
736   DBusList *link;
737
738   link = _dbus_list_get_first_link (&service->owners);
739   
740   while (link != NULL)
741     {
742       if (link->data == owner)
743         return TRUE;
744       
745       link = _dbus_list_get_next_link (&service->owners, link);
746     }
747
748   return FALSE;
749 }