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