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