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