Add an option to allow processes spawn with pipes
[framework/connectivity/connman.git] / src / task.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2009  Intel Corporation. All rights reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <unistd.h>
27 #include <stdarg.h>
28 #include <sys/wait.h>
29
30 #include <glib.h>
31
32 #include "connman.h"
33
34 struct notify_data {
35         connman_task_notify_t func;
36         void *data;
37 };
38
39 struct connman_task {
40         char *path;
41         pid_t pid;
42         GPtrArray *argv;
43         GPtrArray *envp;
44         connman_task_exit_t exit_func;
45         void *exit_data;
46         GHashTable *notify;
47 };
48
49 static GHashTable *task_hash = NULL;
50
51 static volatile gint task_counter;
52
53 static DBusConnection *connection;
54
55 static void free_pointer(gpointer data, gpointer user_data)
56 {
57         g_free(data);
58 }
59
60 static void free_task(gpointer data)
61 {
62         struct connman_task *task = data;
63
64         DBG("task %p", task);
65
66         g_hash_table_destroy(task->notify);
67         task->notify = NULL;
68
69         if (task->pid > 0)
70                 kill(task->pid, SIGTERM);
71
72         g_ptr_array_foreach(task->envp, free_pointer, NULL);
73         g_ptr_array_free(task->envp, TRUE);
74
75         g_ptr_array_foreach(task->argv, free_pointer, NULL);
76         g_ptr_array_free(task->argv, TRUE);
77
78         g_free(task->path);
79         g_free(task);
80 }
81
82 /**
83  * connman_task_create:
84  * @program: name of executable
85  *
86  * Allocate a new task of given #program
87  *
88  * Returns: a newly-allocated #connman_task structure
89  */
90 struct connman_task *connman_task_create(const char *program)
91 {
92         struct connman_task *task;
93         gint counter;
94         char *str;
95
96         DBG("");
97
98         task = g_try_new0(struct connman_task, 1);
99         if (task == NULL)
100                 return NULL;
101
102         counter = g_atomic_int_exchange_and_add(&task_counter, 1);
103
104         task->path = g_strdup_printf("/task/%d", counter);
105         task->pid = -1;
106
107         task->argv = g_ptr_array_new();
108         task->envp = g_ptr_array_new();
109
110         str = g_strdup(program);
111         g_ptr_array_add(task->argv, str);
112
113         task->notify = g_hash_table_new_full(g_str_hash, g_str_equal,
114                                                         g_free, g_free);
115
116         DBG("task %p", task);
117
118         g_hash_table_insert(task_hash, task->path, task);
119
120         return task;
121 }
122
123 /**
124  * connman_task_destory:
125  * @task: task structure
126  *
127  * Remove and destory #task
128  */
129 void connman_task_destroy(struct connman_task *task)
130 {
131         DBG("task %p", task);
132
133         g_hash_table_remove(task_hash, task->path);
134 }
135
136 /**
137  * connman_task_get_path:
138  * @task: task structure
139  *
140  * Get object path
141  */
142 const char *connman_task_get_path(struct connman_task *task)
143 {
144         return task->path;
145 }
146
147 /**
148  * connman_task_add_argument:
149  * @task: task structure
150  * @name: argument name
151  * @format: format string
152  * @Varargs: list of arguments
153  *
154  * Add a new command line argument
155  */
156 int connman_task_add_argument(struct connman_task *task,
157                                 const char *name, const char *format, ...)
158 {
159         va_list ap;
160         char *str;
161
162         DBG("task %p arg %s", task, name);
163
164         if (name == NULL)
165                 return -EINVAL;
166
167         str = g_strdup(name);
168         g_ptr_array_add(task->argv, str);
169
170         va_start(ap, format);
171
172         if (format != NULL) {
173                 str = g_strdup_vprintf(format, ap);
174                 g_ptr_array_add(task->argv, str);
175         }
176
177         va_end(ap);
178
179         return 0;
180 }
181
182 /**
183  * connman_task_add_variable:
184  * @task: task structure
185  * @key: variable name
186  * @format: format string
187  * @Varargs: list of arguments
188  *
189  * Add a new environment variable
190  */
191 int connman_task_add_variable(struct connman_task *task,
192                                 const char *key, const char *format, ...)
193 {
194         va_list ap;
195         char *str, *val;
196
197         DBG("task %p key %s", task, key);
198
199         if (key == NULL)
200                 return -EINVAL;
201
202         va_start(ap, format);
203
204         val = g_strdup_vprintf(format, ap);
205         str = g_strdup_printf("%s=%s", key, format ? format : "");
206         g_ptr_array_add(task->envp, str);
207         g_free(val);
208
209         va_end(ap);
210
211         return 0;
212 }
213
214 /**
215  * connman_task_set_notify:
216  * @task: task structure
217  * @member: notifcation method name
218  * @function: notification callback
219  * @user_data: optional notification user data
220  *
221  * Set notification handler for #member
222  */
223 int connman_task_set_notify(struct connman_task *task, const char *member,
224                         connman_task_notify_t function, void *user_data)
225 {
226         struct notify_data *notify;
227
228         DBG("task %p", task);
229
230         notify = g_try_new0(struct notify_data, 1);
231         if (notify == NULL)
232                 return -ENOMEM;
233
234         notify->func = function;
235         notify->data = user_data;
236
237         g_hash_table_insert(task->notify, g_strdup(member), notify);
238
239         return 0;
240 }
241
242 static void task_died(GPid pid, gint status, gpointer user_data)
243 {
244         struct connman_task *task = user_data;
245
246         if (WIFEXITED(status))
247                 DBG("task %p exit status %d", task, WEXITSTATUS(status));
248         else
249                 DBG("task %p signal %d", task, WTERMSIG(status));
250
251         g_spawn_close_pid(pid);
252         task->pid = -1;
253
254         if (task->exit_func)
255                 task->exit_func(task, task->exit_data);
256 }
257
258 static void task_setup(gpointer user_data)
259 {
260         struct connman_task *task = user_data;
261
262         DBG("task %p", task);
263 }
264
265 /**
266  * connman_task_run:
267  * @task: task structure
268  * @function: exit callback
269  * @user_data: optional exit user data
270  * @fd: optional spawn with pipe
271  *
272  * Execute program specified by #task
273  */
274 int connman_task_run(struct connman_task *task,
275                         connman_task_exit_t function, void *user_data,
276                         int *fd, int *standard_output, int *standard_error)
277 {
278         gboolean result;
279         GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD |
280                                                 G_SPAWN_STDOUT_TO_DEV_NULL;
281         char **argv, **envp;
282
283         DBG("task %p", task);
284
285         if (task->pid > 0)
286                 return -EALREADY;
287
288         if (standard_output == NULL)
289                 flags |= G_SPAWN_STDOUT_TO_DEV_NULL;
290         if (standard_error == NULL)
291                 flags |= G_SPAWN_STDERR_TO_DEV_NULL;
292
293         task->exit_func = function;
294         task->exit_data = user_data;
295
296         if (g_ptr_array_index(task->argv, task->argv->len - 1) != NULL)
297                 g_ptr_array_add(task->argv, NULL);
298
299         if (task->envp->len == 0 || g_ptr_array_index(task->envp,
300                                         task->envp->len - 1) != NULL) {
301                 if (g_hash_table_size(task->notify) > 0) {
302                         const char *busname;
303                         char *str;
304
305                         busname = dbus_bus_get_unique_name(connection);
306                         str = g_strdup_printf("CONNMAN_BUSNAME=%s", busname);
307                         g_ptr_array_add(task->envp, str);
308
309                         str = g_strdup_printf("CONNMAN_INTERFACE=%s",
310                                                 CONNMAN_TASK_INTERFACE);
311                         g_ptr_array_add(task->envp, str);
312
313                         str = g_strdup_printf("CONNMAN_PATH=%s", task->path);
314                         g_ptr_array_add(task->envp, str);
315                 }
316
317                 g_ptr_array_add(task->envp, NULL);
318         }
319
320         argv = (char **) task->argv->pdata;
321         envp = (char **) task->envp->pdata;
322
323         result = g_spawn_async_with_pipes(NULL, argv, envp,
324                                           G_SPAWN_DO_NOT_REAP_CHILD,
325                                           task_setup, task, &task->pid,
326                                           fd, standard_output,
327                                           standard_output, NULL);
328         if (result == FALSE) {
329                 connman_error("Failed to spawn %s", argv[0]);
330                 return -EIO;
331         }
332
333         g_child_watch_add(task->pid, task_died, task);
334
335         return 0;
336 }
337
338 /**
339  * connman_task_stop:
340  * @task: task structure
341  *
342  * Stop program specified by #task
343  */
344 int connman_task_stop(struct connman_task *task)
345 {
346         DBG("task %p", task);
347
348         if (task->pid > 0)
349                 kill(task->pid, SIGTERM);
350
351         return 0;
352 }
353
354 static DBusHandlerResult task_filter(DBusConnection *connection,
355                                         DBusMessage *message, void *user_data)
356 {
357         struct connman_task *task;
358         struct notify_data *notify;
359         const char *path, *member;
360
361         if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
362                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
363
364         if (dbus_message_has_interface(message,
365                                         CONNMAN_TASK_INTERFACE) == FALSE)
366                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
367
368         path = dbus_message_get_path(message);
369         if (path == NULL)
370                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
371
372         task = g_hash_table_lookup(task_hash, path);
373         if (task == NULL)
374                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
375
376         if (dbus_message_get_no_reply(message) == FALSE) {
377                 DBusMessage *reply;
378                 dbus_bool_t result;
379
380                 reply = dbus_message_new_method_return(message);
381                 if (reply == NULL)
382                         return DBUS_HANDLER_RESULT_NEED_MEMORY;
383
384                 result = dbus_connection_send(connection, reply, NULL);
385
386                 dbus_message_unref(reply);
387         }
388
389         member = dbus_message_get_member(message);
390         if (member == NULL)
391                 return DBUS_HANDLER_RESULT_HANDLED;
392
393         notify = g_hash_table_lookup(task->notify, member);
394         if (notify == NULL)
395                 return DBUS_HANDLER_RESULT_HANDLED;
396
397         if (notify->func)
398                 notify->func(task, message, notify->data);
399
400         return DBUS_HANDLER_RESULT_HANDLED;
401 }
402
403 static const char *task_rule = "type=method_call"
404                                         ",interface=" CONNMAN_TASK_INTERFACE;
405
406 int __connman_task_init(void)
407 {
408         DBG("");
409
410         connection = connman_dbus_get_connection();
411
412         dbus_connection_add_filter(connection, task_filter, NULL, NULL);
413
414         g_atomic_int_set(&task_counter, 0);
415
416         task_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
417                                                         NULL, free_task);
418
419         dbus_bus_add_match(connection, task_rule, NULL);
420         dbus_connection_flush(connection);
421
422         return 0;
423 }
424
425 void __connman_task_cleanup(void)
426 {
427         DBG("");
428
429         dbus_bus_remove_match(connection, task_rule, NULL);
430         dbus_connection_flush(connection);
431
432         g_hash_table_destroy(task_hash);
433         task_hash = NULL;
434
435         dbus_connection_remove_filter(connection, task_filter, NULL);
436
437         dbus_connection_unref(connection);
438 }