2003-03-12 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  *
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 "utils.h"
26 #include <dbus/dbus-internals.h>
27 #include <dbus/dbus-hash.h>
28 #include <sys/types.h>
29 #include <dirent.h>
30 #include <errno.h>
31
32 #define DBUS_SERVICE_SECTION "D-BUS Service"
33 #define DBUS_SERVICE_NAME "Name"
34 #define DBUS_SERVICE_EXEC "Exec"
35
36 struct BusActivation
37 {
38   int refcount;
39   DBusHashTable *entries;
40   char *server_address;
41 };
42
43 typedef struct
44 {
45   char *name;
46   char *exec;
47 } BusActivationEntry;
48
49 static void
50 bus_activation_entry_free (BusActivationEntry *entry)
51 {
52   if (!entry)
53     return;
54   
55   dbus_free (entry->name);
56   dbus_free (entry->exec);
57 }
58
59 static dbus_bool_t
60 add_desktop_file_entry (BusActivation  *activation,
61                         BusDesktopFile *desktop_file,
62                         DBusError      *error)
63 {
64   char *name, *exec;
65   BusActivationEntry *entry;
66
67   name = NULL;
68   exec = NULL;
69   entry = NULL;
70   
71   if (!bus_desktop_file_get_string (desktop_file,
72                                     DBUS_SERVICE_SECTION,
73                                     DBUS_SERVICE_NAME,
74                                     &name))
75     {
76       dbus_set_error (error, DBUS_ERROR_FAILED,
77                       "No \""DBUS_SERVICE_NAME"\" key in .service file\n");
78       goto failed;
79     }
80
81   if (!bus_desktop_file_get_string (desktop_file,
82                                     DBUS_SERVICE_SECTION,
83                                     DBUS_SERVICE_EXEC,
84                                     &exec))
85     {
86       dbus_set_error (error, DBUS_ERROR_FAILED,
87                       "No \""DBUS_SERVICE_EXEC"\" key in .service file\n");
88       goto failed;
89     }
90
91   /* FIXME we need a better-defined algorithm for which service file to
92    * pick than "whichever one is first in the directory listing"
93    */
94   if (_dbus_hash_table_lookup_string (activation->entries, name))
95     {
96       dbus_set_error (error, DBUS_ERROR_FAILED,
97                       "Service %s already exists in activation entry list\n", name);
98       goto failed;
99     }
100   
101   entry = dbus_new0 (BusActivationEntry, 1);
102   if (entry == NULL)
103     {
104       BUS_SET_OOM (error);
105       goto failed;
106     }
107   
108   entry->name = name;
109   entry->exec = exec;
110
111   if (!_dbus_hash_table_insert_string (activation->entries, entry->name, entry))
112     {
113       BUS_SET_OOM (error);
114       goto failed;
115     }
116
117   _dbus_verbose ("Added \"%s\" to list of services\n", entry->name);
118   
119   return TRUE;
120
121  failed:
122   dbus_free (name);
123   dbus_free (exec);
124   dbus_free (entry);
125   
126   return FALSE;
127 }
128
129 /* warning: this doesn't fully "undo" itself on failure, i.e. doesn't strip
130  * hash entries it already added.
131  */
132 static dbus_bool_t
133 load_directory (BusActivation *activation,
134                 const char    *directory,
135                 DBusError     *error)
136 {
137   DBusDirIter *iter;
138   DBusString dir, filename;
139   DBusString full_path;
140   BusDesktopFile *desktop_file;
141   DBusError tmp_error;
142   
143   _dbus_string_init_const (&dir, directory);
144
145   iter = NULL;
146   desktop_file = NULL;
147   
148   if (!_dbus_string_init (&filename, _DBUS_INT_MAX))
149     {
150       BUS_SET_OOM (error);
151       return FALSE;
152     }
153
154   if (!_dbus_string_init (&full_path, _DBUS_INT_MAX))
155     {
156       BUS_SET_OOM (error);
157       _dbus_string_free (&filename);
158       return FALSE;
159     }
160
161   /* from this point it's safe to "goto failed" */
162   
163   iter = _dbus_directory_open (&dir, error);
164   if (iter == NULL)
165     {
166       _dbus_verbose ("Failed to open directory %s: %s\n",
167                      directory, error ? error->message : "unknown");
168       goto failed;
169     }
170   
171   /* Now read the files */
172   dbus_error_init (&tmp_error);
173   while (_dbus_directory_get_next_file (iter, &filename, &tmp_error))
174     {
175       _dbus_assert (!dbus_error_is_set (&tmp_error));
176       
177       _dbus_string_set_length (&full_path, 0);
178       
179       if (!_dbus_string_append (&full_path, directory) ||
180           !_dbus_concat_dir_and_file (&full_path, &filename))
181         {
182           BUS_SET_OOM (error);
183           goto failed;
184         }
185       
186       if (!_dbus_string_ends_with_c_str (&filename, ".service"))
187         {
188           const char *filename_c;
189           _dbus_string_get_const_data (&filename, &filename_c);
190           _dbus_verbose ("Skipping non-.service file %s\n",
191                          filename_c);
192           continue;
193         }
194       
195       desktop_file = bus_desktop_file_load (&full_path, &tmp_error);
196
197       if (desktop_file == NULL)
198         {
199           const char *full_path_c;
200
201           _dbus_string_get_const_data (&full_path, &full_path_c);
202           
203           _dbus_verbose ("Could not load %s: %s\n", full_path_c,
204                          tmp_error.message);
205
206           if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY))
207             {
208               dbus_move_error (&tmp_error, error);
209               goto failed;
210             }
211           
212           dbus_error_free (&tmp_error);
213           continue;
214         }
215
216       if (!add_desktop_file_entry (activation, desktop_file, &tmp_error))
217         {
218           const char *full_path_c;
219
220           bus_desktop_file_free (desktop_file);
221           desktop_file = NULL;
222           
223           _dbus_string_get_const_data (&full_path, &full_path_c);
224           
225           _dbus_verbose ("Could not add %s to activation entry list: %s\n",
226                          full_path_c, tmp_error.message);
227
228           if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY))
229             {
230               dbus_move_error (&tmp_error, error);
231               goto failed;
232             }
233
234           dbus_error_free (&tmp_error);
235           continue;
236         }
237       else
238         {
239           bus_desktop_file_free (desktop_file);
240           desktop_file = NULL;
241           continue;
242         }
243     }
244
245   if (dbus_error_is_set (&tmp_error))
246     {
247       dbus_move_error (&tmp_error, error);
248       goto failed;
249     }
250   
251   return TRUE;
252   
253  failed:
254   _DBUS_ASSERT_ERROR_IS_SET (error);
255   
256   if (iter != NULL)
257     _dbus_directory_close (iter);
258   if (desktop_file)
259     bus_desktop_file_free (desktop_file);
260   _dbus_string_free (&filename);
261   _dbus_string_free (&full_path);
262   
263   return FALSE;
264 }
265
266 BusActivation*
267 bus_activation_new (const char  *address,
268                     const char **directories,
269                     DBusError   *error)
270 {
271   int i;
272   BusActivation *activation;
273
274   activation = dbus_new0 (BusActivation, 1);
275   if (activation == NULL)
276     {
277       BUS_SET_OOM (error);
278       return NULL;
279     }
280   
281   activation->refcount = 1;
282   
283   /* FIXME: We should split up the server addresses. */
284   activation->server_address = _dbus_strdup (address);
285   if (activation->server_address == NULL)
286     {
287       BUS_SET_OOM (error);
288       goto failed;
289     }
290   
291   activation->entries = _dbus_hash_table_new (DBUS_HASH_STRING, NULL,
292                                              (DBusFreeFunction)bus_activation_entry_free);
293   if (activation->entries == NULL)
294     {      
295       BUS_SET_OOM (error);
296       goto failed;
297     }
298
299   /* Load service files */
300   i = 0;
301   while (directories[i] != NULL)
302     {
303       if (!load_directory (activation, directories[i], error))
304         goto failed;
305       ++i;
306     }
307
308   return activation;
309   
310  failed:
311   bus_activation_unref (activation);  
312   return NULL;
313 }
314
315 void
316 bus_activation_ref (BusActivation *activation)
317 {
318   _dbus_assert (activation->refcount > 0);
319   
320   activation->refcount += 1;
321 }
322
323 void
324 bus_activation_unref (BusActivation *activation)
325 {
326   _dbus_assert (activation->refcount > 0);
327
328   activation->refcount -= 1;
329
330   if (activation->refcount == 0)
331     {
332       dbus_free (activation->server_address);
333       if (activation->entries)
334         _dbus_hash_table_unref (activation->entries);
335       dbus_free (activation);
336     }
337 }
338
339 static void
340 child_setup (void *data)
341 {
342   BusActivation *activation = data;
343   
344   /* If no memory, we simply have the child exit, so it won't try
345    * to connect to the wrong thing.
346    */
347   if (!_dbus_setenv ("DBUS_ADDRESS", activation->server_address))
348     _dbus_exit (1);
349 }
350
351 dbus_bool_t
352 bus_activation_activate_service (BusActivation *activation,
353                                  const char    *service_name,
354                                  DBusError     *error)
355 {
356   BusActivationEntry *entry;
357   char *argv[2];
358   
359   entry = _dbus_hash_table_lookup_string (activation->entries, service_name);
360
361   if (!entry)
362     {
363       dbus_set_error (error, DBUS_ERROR_ACTIVATE_SERVICE_NOT_FOUND,
364                       "The service %s was not found in the activation entry list",
365                       service_name);
366       return FALSE;
367     }
368
369   /* FIXME we need to support a full command line, not just a single
370    * argv[0]
371    */
372   
373   /* Now try to spawn the process */
374   argv[0] = entry->exec;
375   argv[1] = NULL;
376
377   if (!_dbus_spawn_async (argv,
378                           child_setup, activation, 
379                           error))
380     return FALSE;
381
382   return TRUE;
383 }