2003-04-04 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / bus / activation.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* activation.c  Activation of services
3  *
4  * Copyright (C) 2003  CodeFactory AB
5  * Copyright (C) 2003  Red Hat, Inc.
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 "activation.h"
25 #include "desktop-file.h"
26 #include "services.h"
27 #include "utils.h"
28 #include <dbus/dbus-internals.h>
29 #include <dbus/dbus-hash.h>
30 #include <dbus/dbus-list.h>
31 #include <sys/types.h>
32 #include <dirent.h>
33 #include <errno.h>
34
35 #define DBUS_SERVICE_SECTION "D-BUS Service"
36 #define DBUS_SERVICE_NAME "Name"
37 #define DBUS_SERVICE_EXEC "Exec"
38
39 struct BusActivation
40 {
41   int refcount;
42   DBusHashTable *entries;
43   DBusHashTable *pending_activations;
44   char *server_address;
45   BusContext *context;
46 };
47
48 typedef struct
49 {
50   char *name;
51   char *exec;
52 } BusActivationEntry;
53
54 typedef struct BusPendingActivationEntry BusPendingActivationEntry;
55
56 struct BusPendingActivationEntry
57 {
58   DBusMessage *activation_message;
59   DBusConnection *connection;
60 };
61
62 typedef struct
63 {
64   char *service_name;
65   DBusList *entries;
66 } BusPendingActivation;
67
68 static void
69 bus_pending_activation_entry_free (BusPendingActivationEntry *entry)
70 {
71   if (entry->activation_message)
72     dbus_message_unref (entry->activation_message);
73   
74   if (entry->connection)
75     dbus_connection_unref (entry->connection);
76
77   dbus_free (entry);
78 }
79
80 static void
81 bus_pending_activation_free (BusPendingActivation *activation)
82 {
83   DBusList *link;
84   
85   if (!activation)
86     return;
87
88   dbus_free (activation->service_name);
89
90   link = _dbus_list_get_first_link (&activation->entries);
91
92   while (link != NULL)
93     {
94       BusPendingActivationEntry *entry = link->data;
95
96       bus_pending_activation_entry_free (entry);
97
98       link = _dbus_list_get_next_link (&activation->entries, link);
99     }
100   _dbus_list_clear (&activation->entries);
101   
102   dbus_free (activation);
103 }
104
105 static void
106 bus_activation_entry_free (BusActivationEntry *entry)
107 {
108   if (!entry)
109     return;
110   
111   dbus_free (entry->name);
112   dbus_free (entry->exec);
113
114   dbus_free (entry);
115 }
116
117 static dbus_bool_t
118 add_desktop_file_entry (BusActivation  *activation,
119                         BusDesktopFile *desktop_file,
120                         DBusError      *error)
121 {
122   char *name, *exec;
123   BusActivationEntry *entry;
124
125   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
126   
127   name = NULL;
128   exec = NULL;
129   entry = NULL;
130   
131   if (!bus_desktop_file_get_string (desktop_file,
132                                     DBUS_SERVICE_SECTION,
133                                     DBUS_SERVICE_NAME,
134                                     &name))
135     {
136       dbus_set_error (error, DBUS_ERROR_FAILED,
137                       "No \""DBUS_SERVICE_NAME"\" key in .service file\n");
138       goto failed;
139     }
140
141   if (!bus_desktop_file_get_string (desktop_file,
142                                     DBUS_SERVICE_SECTION,
143                                     DBUS_SERVICE_EXEC,
144                                     &exec))
145     {
146       dbus_set_error (error, DBUS_ERROR_FAILED,
147                       "No \""DBUS_SERVICE_EXEC"\" key in .service file\n");
148       goto failed;
149     }
150
151   /* FIXME we need a better-defined algorithm for which service file to
152    * pick than "whichever one is first in the directory listing"
153    */
154   if (_dbus_hash_table_lookup_string (activation->entries, name))
155     {
156       dbus_set_error (error, DBUS_ERROR_FAILED,
157                       "Service %s already exists in activation entry list\n", name);
158       goto failed;
159     }
160   
161   entry = dbus_new0 (BusActivationEntry, 1);
162   if (entry == NULL)
163     {
164       BUS_SET_OOM (error);
165       goto failed;
166     }
167   
168   entry->name = name;
169   entry->exec = exec;
170
171   if (!_dbus_hash_table_insert_string (activation->entries, entry->name, entry))
172     {
173       BUS_SET_OOM (error);
174       goto failed;
175     }
176
177   _dbus_verbose ("Added \"%s\" to list of services\n", entry->name);
178   
179   return TRUE;
180
181  failed:
182   dbus_free (name);
183   dbus_free (exec);
184   dbus_free (entry);
185   
186   return FALSE;
187 }
188
189 /* warning: this doesn't fully "undo" itself on failure, i.e. doesn't strip
190  * hash entries it already added.
191  */
192 static dbus_bool_t
193 load_directory (BusActivation *activation,
194                 const char    *directory,
195                 DBusError     *error)
196 {
197   DBusDirIter *iter;
198   DBusString dir, filename;
199   DBusString full_path;
200   BusDesktopFile *desktop_file;
201   DBusError tmp_error;
202   dbus_bool_t retval;
203   
204   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
205   
206   _dbus_string_init_const (&dir, directory);
207
208   iter = NULL;
209   desktop_file = NULL;
210   
211   if (!_dbus_string_init (&filename))
212     {
213       BUS_SET_OOM (error);
214       return FALSE;
215     }
216
217   if (!_dbus_string_init (&full_path))
218     {
219       BUS_SET_OOM (error);
220       _dbus_string_free (&filename);
221       return FALSE;
222     }
223
224   retval = FALSE;
225   
226   /* from this point it's safe to "goto out" */
227   
228   iter = _dbus_directory_open (&dir, error);
229   if (iter == NULL)
230     {
231       _dbus_verbose ("Failed to open directory %s: %s\n",
232                      directory, error ? error->message : "unknown");
233       goto out;
234     }
235   
236   /* Now read the files */
237   dbus_error_init (&tmp_error);
238   while (_dbus_directory_get_next_file (iter, &filename, &tmp_error))
239     {
240       _dbus_assert (!dbus_error_is_set (&tmp_error));
241       
242       _dbus_string_set_length (&full_path, 0);
243       
244       if (!_dbus_string_append (&full_path, directory) ||
245           !_dbus_concat_dir_and_file (&full_path, &filename))
246         {
247           BUS_SET_OOM (error);
248           goto out;
249         }
250       
251       if (!_dbus_string_ends_with_c_str (&filename, ".service"))
252         {
253           _dbus_verbose ("Skipping non-.service file %s\n",
254                          _dbus_string_get_const_data (&filename));
255           continue;
256         }
257       
258       desktop_file = bus_desktop_file_load (&full_path, &tmp_error);
259
260       if (desktop_file == NULL)
261         {
262           _dbus_verbose ("Could not load %s: %s\n",
263                          _dbus_string_get_const_data (&full_path),
264                          tmp_error.message);
265
266           if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY))
267             {
268               dbus_move_error (&tmp_error, error);
269               goto out;
270             }
271           
272           dbus_error_free (&tmp_error);
273           continue;
274         }
275
276       if (!add_desktop_file_entry (activation, desktop_file, &tmp_error))
277         {
278           bus_desktop_file_free (desktop_file);
279           desktop_file = NULL;
280           
281           _dbus_verbose ("Could not add %s to activation entry list: %s\n",
282                          _dbus_string_get_const_data (&full_path), tmp_error.message);
283
284           if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY))
285             {
286               dbus_move_error (&tmp_error, error);
287               goto out;
288             }
289
290           dbus_error_free (&tmp_error);
291           continue;
292         }
293       else
294         {
295           bus_desktop_file_free (desktop_file);
296           desktop_file = NULL;
297           continue;
298         }
299     }
300
301   if (dbus_error_is_set (&tmp_error))
302     {
303       dbus_move_error (&tmp_error, error);
304       goto out;
305     }
306   
307   retval = TRUE;
308   
309  out:
310   if (!retval)
311     _DBUS_ASSERT_ERROR_IS_SET (error);
312   else
313     _DBUS_ASSERT_ERROR_IS_CLEAR (error);
314   
315   if (iter != NULL)
316     _dbus_directory_close (iter);
317   if (desktop_file)
318     bus_desktop_file_free (desktop_file);
319   _dbus_string_free (&filename);
320   _dbus_string_free (&full_path);
321   
322   return retval;
323 }
324
325 BusActivation*
326 bus_activation_new (BusContext        *context,
327                     const DBusString  *address,
328                     DBusList         **directories,
329                     DBusError         *error)
330 {
331   BusActivation *activation;
332   DBusList *link;
333   
334   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
335   
336   activation = dbus_new0 (BusActivation, 1);
337   if (activation == NULL)
338     {
339       BUS_SET_OOM (error);
340       return NULL;
341     }
342   
343   activation->refcount = 1;
344   activation->context = context;
345   
346   if (!_dbus_string_copy_data (address, &activation->server_address))
347     {
348       BUS_SET_OOM (error);
349       goto failed;
350     }
351   
352   activation->entries = _dbus_hash_table_new (DBUS_HASH_STRING, NULL,
353                                              (DBusFreeFunction)bus_activation_entry_free);
354   if (activation->entries == NULL)
355     {      
356       BUS_SET_OOM (error);
357       goto failed;
358     }
359
360   activation->pending_activations = _dbus_hash_table_new (DBUS_HASH_STRING, NULL,
361                                                           (DBusFreeFunction)bus_pending_activation_free);
362
363   if (activation->pending_activations == NULL)
364     {
365       BUS_SET_OOM (error);
366       goto failed;
367     }
368   
369   /* Load service files */
370   link = _dbus_list_get_first_link (directories);
371   while (link != NULL)
372     {
373       if (!load_directory (activation, link->data, error))
374         goto failed;
375       link = _dbus_list_get_next_link (directories, link);
376     }
377
378   return activation;
379   
380  failed:
381   bus_activation_unref (activation);  
382   return NULL;
383 }
384
385 void
386 bus_activation_ref (BusActivation *activation)
387 {
388   _dbus_assert (activation->refcount > 0);
389   
390   activation->refcount += 1;
391 }
392
393 void
394 bus_activation_unref (BusActivation *activation)
395 {
396   _dbus_assert (activation->refcount > 0);
397
398   activation->refcount -= 1;
399
400   if (activation->refcount == 0)
401     {
402       dbus_free (activation->server_address);
403       if (activation->entries)
404         _dbus_hash_table_unref (activation->entries);
405       if (activation->pending_activations)
406         _dbus_hash_table_unref (activation->pending_activations);
407       dbus_free (activation);
408     }
409 }
410
411 static void
412 child_setup (void *data)
413 {
414   BusActivation *activation = data;
415   const char *type;
416   
417   /* If no memory, we simply have the child exit, so it won't try
418    * to connect to the wrong thing.
419    */
420   if (!_dbus_setenv ("DBUS_ACTIVATION_ADDRESS", activation->server_address))
421     _dbus_exit (1);
422
423   type = bus_context_get_type (activation->context);
424   if (type != NULL)
425     {
426       if (!_dbus_setenv ("DBUS_BUS_TYPE", type))
427         _dbus_exit (1);
428     }
429 }
430
431 dbus_bool_t
432 bus_activation_service_created (BusActivation  *activation,
433                                 const char     *service_name,
434                                 BusTransaction *transaction,
435                                 DBusError      *error)
436 {
437   BusPendingActivation *pending_activation;
438   DBusMessage *message;
439   DBusList *link;
440
441   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
442   
443   /* Check if it's a pending activation */
444   pending_activation = _dbus_hash_table_lookup_string (activation->pending_activations, service_name);
445
446   if (!pending_activation)
447     return TRUE;
448
449   link = _dbus_list_get_first_link (&pending_activation->entries);
450   while (link != NULL)
451     {
452       BusPendingActivationEntry *entry = link->data;
453       DBusList *next = _dbus_list_get_next_link (&pending_activation->entries, link);
454       
455       if (dbus_connection_get_is_connected (entry->connection))
456         {
457           message = dbus_message_new_reply (entry->activation_message);
458           if (!message)
459             {
460               BUS_SET_OOM (error);
461               goto error;
462             }
463
464           if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS) ||
465               !dbus_message_append_args (message,
466                                          DBUS_TYPE_UINT32, DBUS_ACTIVATION_REPLY_ACTIVATED,
467                                          0))
468             {
469               dbus_message_unref (message);
470               BUS_SET_OOM (error);
471               goto error;
472             }
473           
474           if (!bus_transaction_send_message (transaction, entry->connection, message))
475             {
476               dbus_message_unref (message);
477               BUS_SET_OOM (error);
478               goto error;
479             }
480         }
481
482       bus_pending_activation_entry_free (entry);
483       
484       _dbus_list_remove_link (&pending_activation->entries, link);      
485       link = next;
486     }
487   
488   _dbus_hash_table_remove_string (activation->pending_activations, service_name);
489
490   return TRUE;
491
492  error:
493   _dbus_hash_table_remove_string (activation->pending_activations, service_name);
494   return FALSE;
495 }
496
497 dbus_bool_t
498 bus_activation_activate_service (BusActivation  *activation,
499                                  DBusConnection *connection,
500                                  BusTransaction *transaction,
501                                  DBusMessage    *activation_message,
502                                  const char     *service_name,
503                                  DBusError      *error)
504 {
505   BusActivationEntry *entry;
506   BusPendingActivation *pending_activation;
507   BusPendingActivationEntry *pending_activation_entry;
508   DBusMessage *message;
509   DBusString service_str;
510   char *argv[2];
511   dbus_bool_t retval;
512
513   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
514   
515   entry = _dbus_hash_table_lookup_string (activation->entries, service_name);
516
517   if (!entry)
518     {
519       dbus_set_error (error, DBUS_ERROR_ACTIVATE_SERVICE_NOT_FOUND,
520                       "The service %s was not found in the activation entry list",
521                       service_name);
522       return FALSE;
523     }
524
525   /* Check if the service is active */
526   _dbus_string_init_const (&service_str, service_name);
527   if (bus_registry_lookup (bus_context_get_registry (activation->context), &service_str) != NULL)
528     {
529       message = dbus_message_new_reply (activation_message);
530
531       if (!message)
532         {
533           BUS_SET_OOM (error);
534           return FALSE;
535         }
536
537       if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS) ||
538           !dbus_message_append_args (message,
539                                      DBUS_TYPE_UINT32, DBUS_ACTIVATION_REPLY_ALREADY_ACTIVE, 
540                                      0))
541         {
542           BUS_SET_OOM (error);
543           dbus_message_unref (message);
544           return FALSE;
545         }
546
547       retval = bus_transaction_send_message (transaction, connection, message);
548       dbus_message_unref (message);
549       if (!retval)
550         BUS_SET_OOM (error);
551
552       return retval;
553     }
554
555   pending_activation_entry = dbus_new0 (BusPendingActivationEntry, 1);
556   if (!pending_activation_entry)
557     {
558       BUS_SET_OOM (error);
559       return FALSE;
560     }
561
562   pending_activation_entry->activation_message = activation_message;
563   dbus_message_ref (activation_message);
564   pending_activation_entry->connection = connection;
565   dbus_connection_ref (connection);
566   
567   /* Check if the service is being activated */
568   pending_activation = _dbus_hash_table_lookup_string (activation->pending_activations, service_name);
569   if (pending_activation)
570     {
571       if (!_dbus_list_append (&pending_activation->entries, pending_activation_entry))
572         {
573           BUS_SET_OOM (error);
574           bus_pending_activation_entry_free (pending_activation_entry);
575
576           return FALSE;
577         }
578     }
579   else
580     {
581       pending_activation = dbus_new0 (BusPendingActivation, 1);
582       if (!pending_activation)
583         {
584           BUS_SET_OOM (error);
585           bus_pending_activation_entry_free (pending_activation_entry);   
586           return FALSE;
587         }
588       pending_activation->service_name = _dbus_strdup (service_name);
589       if (!pending_activation->service_name)
590         {
591           BUS_SET_OOM (error);
592           bus_pending_activation_free (pending_activation);
593           bus_pending_activation_entry_free (pending_activation_entry);   
594           return FALSE;
595         }
596
597       if (!_dbus_list_append (&pending_activation->entries, pending_activation_entry))
598         {
599           BUS_SET_OOM (error);
600           bus_pending_activation_free (pending_activation);
601           bus_pending_activation_entry_free (pending_activation_entry);   
602           return FALSE;
603         }
604       
605       if (!_dbus_hash_table_insert_string (activation->pending_activations,
606                                            pending_activation->service_name, pending_activation))
607         {
608           BUS_SET_OOM (error);
609           bus_pending_activation_free (pending_activation);
610           return FALSE;
611         }
612     }
613   
614   /* FIXME we need to support a full command line, not just a single
615    * argv[0]
616    */
617   
618   /* Now try to spawn the process */
619   argv[0] = entry->exec;
620   argv[1] = NULL;
621
622   if (!_dbus_spawn_async_with_babysitter (NULL, argv,
623                                           child_setup, activation, 
624                                           error))
625     {
626       _dbus_hash_table_remove_string (activation->pending_activations,
627                                       pending_activation->service_name);
628       return FALSE;
629     }
630   
631   return TRUE;
632 }