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