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