9adc31561ae1e8a9d97b5e91819a17669be16e5e
[framework/connectivity/connman.git] / src / task.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2010  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 <errno.h>
27 #include <unistd.h>
28 #include <stdarg.h>
29 #include <sys/wait.h>
30 #include <signal.h>
31
32 #include <glib.h>
33
34 #include "connman.h"
35
36 struct notify_data {
37         connman_task_notify_t func;
38         void *data;
39 };
40
41 struct connman_task {
42         char *path;
43         pid_t pid;
44         guint child_watch;
45         GPtrArray *argv;
46         GPtrArray *envp;
47         connman_task_exit_t exit_func;
48         void *exit_data;
49         GHashTable *notify;
50 };
51
52 static GHashTable *task_hash = NULL;
53
54 static volatile int task_counter;
55
56 static DBusConnection *connection;
57
58 static void free_pointer(gpointer data, gpointer user_data)
59 {
60         g_free(data);
61 }
62
63 static void free_task(gpointer data)
64 {
65         struct connman_task *task = data;
66
67         DBG("task %p", task);
68
69         g_hash_table_destroy(task->notify);
70         task->notify = NULL;
71
72         if (task->pid > 0)
73                 kill(task->pid, SIGTERM);
74
75         if (task->child_watch > 0)
76                 g_source_remove(task->child_watch);
77
78         g_ptr_array_foreach(task->envp, free_pointer, NULL);
79         g_ptr_array_free(task->envp, TRUE);
80
81         g_ptr_array_foreach(task->argv, free_pointer, NULL);
82         g_ptr_array_free(task->argv, TRUE);
83
84         g_free(task->path);
85         g_free(task);
86 }
87
88 /**
89  * connman_task_create:
90  * @program: name of executable
91  *
92  * Allocate a new task of given #program
93  *
94  * Returns: a newly-allocated #connman_task structure
95  */
96 struct connman_task *connman_task_create(const char *program)
97 {
98         struct connman_task *task;
99         gint counter;
100         char *str;
101
102         DBG("");
103
104         task = g_try_new0(struct connman_task, 1);
105         if (task == NULL)
106                 return NULL;
107
108         counter = __sync_fetch_and_add(&task_counter, 1);
109
110         task->path = g_strdup_printf("/task/%d", counter);
111         task->pid = -1;
112
113         task->argv = g_ptr_array_new();
114         task->envp = g_ptr_array_new();
115
116         str = g_strdup(program);
117         g_ptr_array_add(task->argv, str);
118
119         task->notify = g_hash_table_new_full(g_str_hash, g_str_equal,
120                                                         g_free, g_free);
121
122         DBG("task %p", task);
123
124         g_hash_table_insert(task_hash, task->path, task);
125
126         return task;
127 }
128
129 /**
130  * connman_task_destory:
131  * @task: task structure
132  *
133  * Remove and destory #task
134  */
135 void connman_task_destroy(struct connman_task *task)
136 {
137         DBG("task %p", task);
138
139         g_hash_table_remove(task_hash, task->path);
140 }
141
142 /**
143  * connman_task_get_path:
144  * @task: task structure
145  *
146  * Get object path
147  */
148 const char *connman_task_get_path(struct connman_task *task)
149 {
150         return task->path;
151 }
152
153 /**
154  * connman_task_add_argument:
155  * @task: task structure
156  * @name: argument name
157  * @format: format string
158  * @Varargs: list of arguments
159  *
160  * Add a new command line argument
161  */
162 int connman_task_add_argument(struct connman_task *task,
163                                 const char *name, const char *format, ...)
164 {
165         va_list ap;
166         char *str;
167
168         DBG("task %p arg %s", task, name);
169
170         if (name == NULL)
171                 return -EINVAL;
172
173         str = g_strdup(name);
174         g_ptr_array_add(task->argv, str);
175
176         va_start(ap, format);
177
178         if (format != NULL) {
179                 str = g_strdup_vprintf(format, ap);
180                 g_ptr_array_add(task->argv, str);
181         }
182
183         va_end(ap);
184
185         return 0;
186 }
187
188 /**
189  * connman_task_add_variable:
190  * @task: task structure
191  * @key: variable name
192  * @format: format string
193  * @Varargs: list of arguments
194  *
195  * Add a new environment variable
196  */
197 int connman_task_add_variable(struct connman_task *task,
198                                 const char *key, const char *format, ...)
199 {
200         va_list ap;
201         char *str, *val;
202
203         DBG("task %p key %s", task, key);
204
205         if (key == NULL)
206                 return -EINVAL;
207
208         va_start(ap, format);
209
210         val = g_strdup_vprintf(format, ap);
211         str = g_strdup_printf("%s=%s", key, format ? format : "");
212         g_ptr_array_add(task->envp, str);
213         g_free(val);
214
215         va_end(ap);
216
217         return 0;
218 }
219
220 /**
221  * connman_task_set_notify:
222  * @task: task structure
223  * @member: notifcation method name
224  * @function: notification callback
225  * @user_data: optional notification user data
226  *
227  * Set notification handler for #member
228  */
229 int connman_task_set_notify(struct connman_task *task, const char *member,
230                         connman_task_notify_t function, void *user_data)
231 {
232         struct notify_data *notify;
233
234         DBG("task %p", task);
235
236         notify = g_try_new0(struct notify_data, 1);
237         if (notify == NULL)
238                 return -ENOMEM;
239
240         notify->func = function;
241         notify->data = user_data;
242
243         g_hash_table_replace(task->notify, g_strdup(member), notify);
244
245         return 0;
246 }
247
248 static void task_died(GPid pid, gint status, gpointer user_data)
249 {
250         struct connman_task *task = user_data;
251         int exit_code;
252
253         if (WIFEXITED(status)) {
254                 exit_code = WEXITSTATUS(status);
255                 DBG("task %p exit status %d", task, exit_code);
256         } else {
257                 exit_code = 0;
258                 DBG("task %p signal %d", task, WTERMSIG(status));
259         }
260
261         g_spawn_close_pid(pid);
262         task->pid = -1;
263
264         task->child_watch = 0;
265
266         if (task->exit_func)
267                 task->exit_func(task, exit_code, task->exit_data);
268 }
269
270 static void task_setup(gpointer user_data)
271 {
272         sigset_t mask;
273         struct connman_task *task = user_data;
274
275         DBG("task %p", task);
276
277         sigemptyset(&mask);
278         if (sigprocmask(SIG_SETMASK, &mask, NULL) < 0)
279                 connman_error("Failed to clean signal mask");
280 }
281
282 /**
283  * connman_task_run:
284  * @task: task structure
285  * @function: exit callback
286  * @user_data: optional exit user data
287  * @fd: optional spawn with pipe
288  *
289  * Execute program specified by #task
290  */
291 int connman_task_run(struct connman_task *task,
292                         connman_task_exit_t function, void *user_data,
293                         int *stdin_fd, int *stdout_fd, int *stderr_fd)
294 {
295         GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD;
296         gboolean result;
297         char **argv, **envp;
298
299         DBG("task %p", task);
300
301         if (task->pid > 0)
302                 return -EALREADY;
303
304         if (stdout_fd == NULL)
305                 flags |= G_SPAWN_STDOUT_TO_DEV_NULL;
306
307         if (stderr_fd == NULL)
308                 flags |= G_SPAWN_STDERR_TO_DEV_NULL;
309
310         task->exit_func = function;
311         task->exit_data = user_data;
312
313         if (g_ptr_array_index(task->argv, task->argv->len - 1) != NULL)
314                 g_ptr_array_add(task->argv, NULL);
315
316         if (task->envp->len == 0 || g_ptr_array_index(task->envp,
317                                         task->envp->len - 1) != NULL) {
318                 if (g_hash_table_size(task->notify) > 0) {
319                         const char *busname;
320                         char *str;
321
322                         busname = dbus_bus_get_unique_name(connection);
323                         str = g_strdup_printf("CONNMAN_BUSNAME=%s", busname);
324                         g_ptr_array_add(task->envp, str);
325
326                         str = g_strdup_printf("CONNMAN_INTERFACE=%s",
327                                                 CONNMAN_TASK_INTERFACE);
328                         g_ptr_array_add(task->envp, str);
329
330                         str = g_strdup_printf("CONNMAN_PATH=%s", task->path);
331                         g_ptr_array_add(task->envp, str);
332                 }
333
334                 g_ptr_array_add(task->envp, NULL);
335         }
336
337         argv = (char **) task->argv->pdata;
338         envp = (char **) task->envp->pdata;
339
340         result = g_spawn_async_with_pipes(NULL, argv, envp, flags,
341                                         task_setup, task, &task->pid,
342                                         stdin_fd, stdout_fd, stderr_fd, NULL);
343         if (result == FALSE) {
344                 connman_error("Failed to spawn %s", argv[0]);
345                 return -EIO;
346         }
347
348         task->child_watch = g_child_watch_add(task->pid, task_died, task);
349
350         return 0;
351 }
352
353 static gboolean force_kill_timeout(gpointer user_data)
354 {
355         pid_t pid = GPOINTER_TO_INT(user_data);
356         if (pid > 0) {
357                 if (kill(pid, SIGKILL) == 0)
358                         connman_warn("killing pid %d by force", pid);
359         }
360
361         return FALSE;
362 }
363
364 static gboolean kill_timeout(gpointer user_data)
365 {
366         pid_t pid = GPOINTER_TO_INT(user_data);
367         if (pid > 0) {
368                 if (kill(pid, SIGINT) == 0)
369                         g_timeout_add_seconds(1, force_kill_timeout,
370                                         GINT_TO_POINTER(pid));
371         }
372
373         return FALSE;
374 }
375
376 static gboolean check_kill(gpointer user_data)
377 {
378         pid_t pid = GPOINTER_TO_INT(user_data);
379         if (pid > 0) {
380                 if (kill(pid, 0) == 0) {
381                         connman_info("pid %d was not killed, "
382                                         "retrying after 2 sec", pid);
383                         g_timeout_add_seconds(2, kill_timeout,
384                                         GINT_TO_POINTER(pid));
385                 }
386         }
387
388         return FALSE;
389 }
390
391 /**
392  * connman_task_stop:
393  * @task: task structure
394  *
395  * Stop program specified by #task
396  */
397 int connman_task_stop(struct connman_task *task)
398 {
399         DBG("task %p", task);
400
401         if (task->pid > 0) {
402                 kill(task->pid, SIGTERM);
403
404                 g_timeout_add_seconds(0, check_kill,
405                                 GINT_TO_POINTER(task->pid));
406         }
407
408         return 0;
409 }
410
411 static DBusHandlerResult task_filter(DBusConnection *connection,
412                                         DBusMessage *message, void *user_data)
413 {
414         struct connman_task *task;
415         struct notify_data *notify;
416         const char *path, *member;
417         DBusMessage *reply = NULL;
418
419         if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
420                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
421
422         if (dbus_message_has_interface(message,
423                                         CONNMAN_TASK_INTERFACE) == FALSE)
424                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
425
426         path = dbus_message_get_path(message);
427         if (path == NULL)
428                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
429
430         task = g_hash_table_lookup(task_hash, path);
431         if (task == NULL)
432                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
433
434         member = dbus_message_get_member(message);
435         if (member == NULL)
436                 goto send_reply;
437
438         notify = g_hash_table_lookup(task->notify, member);
439         if (notify == NULL)
440                 goto send_reply;
441
442         if (notify->func)
443                 reply = notify->func(task, message, notify->data);
444
445 send_reply:
446         if (dbus_message_get_no_reply(message) == FALSE &&
447                                                 reply == NULL) {
448
449                 reply = dbus_message_new_method_return(message);
450                 if (reply == NULL)
451                         return DBUS_HANDLER_RESULT_NEED_MEMORY;
452         }
453
454         if (reply != NULL) {
455                 dbus_connection_send(connection, reply, NULL);
456
457                 dbus_message_unref(reply);
458         }
459
460         return DBUS_HANDLER_RESULT_HANDLED;
461 }
462
463 static const char *task_rule = "type=method_call"
464                                         ",interface=" CONNMAN_TASK_INTERFACE;
465
466 int __connman_task_init(void)
467 {
468         DBG("");
469
470         connection = connman_dbus_get_connection();
471
472         dbus_connection_add_filter(connection, task_filter, NULL, NULL);
473
474         task_counter = 0;
475         __sync_synchronize();
476
477         task_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
478                                                         NULL, free_task);
479
480         dbus_bus_add_match(connection, task_rule, NULL);
481         dbus_connection_flush(connection);
482
483         return 0;
484 }
485
486 void __connman_task_cleanup(void)
487 {
488         DBG("");
489
490         dbus_bus_remove_match(connection, task_rule, NULL);
491         dbus_connection_flush(connection);
492
493         g_hash_table_destroy(task_hash);
494         task_hash = NULL;
495
496         dbus_connection_remove_filter(connection, task_filter, NULL);
497
498         dbus_connection_unref(connection);
499 }