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