Add support for variable task argument setup
[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  *
271  * Execute program specified by #task
272  */
273 int connman_task_run(struct connman_task *task,
274                         connman_task_exit_t function, void *user_data)
275 {
276         GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD |
277                                                 G_SPAWN_STDOUT_TO_DEV_NULL;
278         char **argv, **envp;
279
280         DBG("task %p", task);
281
282         if (task->pid > 0)
283                 return -EALREADY;
284
285         task->exit_func = function;
286         task->exit_data = user_data;
287
288         if (g_ptr_array_index(task->argv, task->argv->len - 1) != NULL)
289                 g_ptr_array_add(task->argv, NULL);
290
291         if (task->envp->len == 0 || g_ptr_array_index(task->envp,
292                                         task->envp->len - 1) != NULL) {
293                 if (g_hash_table_size(task->notify) > 0) {
294                         const char *busname;
295                         char *str;
296
297                         busname = dbus_bus_get_unique_name(connection);
298                         str = g_strdup_printf("CONNMAN_BUSNAME=%s", busname);
299                         g_ptr_array_add(task->envp, str);
300
301                         str = g_strdup_printf("CONNMAN_INTERFACE=%s",
302                                                 CONNMAN_TASK_INTERFACE);
303                         g_ptr_array_add(task->envp, str);
304
305                         str = g_strdup_printf("CONNMAN_PATH=%s", task->path);
306                         g_ptr_array_add(task->envp, str);
307                 }
308
309                 g_ptr_array_add(task->envp, NULL);
310         }
311
312         argv = (char **) task->argv->pdata;
313         envp = (char **) task->envp->pdata;
314
315         if (g_spawn_async(NULL, argv, envp, flags,
316                                 task_setup, task, &task->pid, NULL) == FALSE) {
317                 connman_error("Failed to spawn %s", argv[0]);
318                 return -EIO;
319         }
320
321         g_child_watch_add(task->pid, task_died, task);
322
323         return 0;
324 }
325
326 /**
327  * connman_task_stop:
328  * @task: task structure
329  *
330  * Stop program specified by #task
331  */
332 int connman_task_stop(struct connman_task *task)
333 {
334         DBG("task %p", task);
335
336         if (task->pid > 0)
337                 kill(task->pid, SIGTERM);
338
339         return 0;
340 }
341
342 static DBusHandlerResult task_filter(DBusConnection *connection,
343                                         DBusMessage *message, void *user_data)
344 {
345         struct connman_task *task;
346         struct notify_data *notify;
347         const char *path, *member;
348
349         if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
350                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
351
352         if (dbus_message_has_interface(message,
353                                         CONNMAN_TASK_INTERFACE) == FALSE)
354                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
355
356         path = dbus_message_get_path(message);
357         if (path == NULL)
358                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
359
360         task = g_hash_table_lookup(task_hash, path);
361         if (task == NULL)
362                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
363
364         if (dbus_message_get_no_reply(message) == FALSE) {
365                 DBusMessage *reply;
366                 dbus_bool_t result;
367
368                 reply = dbus_message_new_method_return(message);
369                 if (reply == NULL)
370                         return DBUS_HANDLER_RESULT_NEED_MEMORY;
371
372                 result = dbus_connection_send(connection, reply, NULL);
373
374                 dbus_message_unref(reply);
375         }
376
377         member = dbus_message_get_member(message);
378         if (member == NULL)
379                 return DBUS_HANDLER_RESULT_HANDLED;
380
381         notify = g_hash_table_lookup(task->notify, member);
382         if (notify == NULL)
383                 return DBUS_HANDLER_RESULT_HANDLED;
384
385         if (notify->func)
386                 notify->func(task, message, notify->data);
387
388         return DBUS_HANDLER_RESULT_HANDLED;
389 }
390
391 static const char *task_rule = "type=method_call"
392                                         ",interface=" CONNMAN_TASK_INTERFACE;
393
394 int __connman_task_init(void)
395 {
396         DBG("");
397
398         connection = connman_dbus_get_connection();
399
400         dbus_connection_add_filter(connection, task_filter, NULL, NULL);
401
402         g_atomic_int_set(&task_counter, 0);
403
404         task_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
405                                                         NULL, free_task);
406
407         dbus_bus_add_match(connection, task_rule, NULL);
408         dbus_connection_flush(connection);
409
410         return 0;
411 }
412
413 void __connman_task_cleanup(void)
414 {
415         DBG("");
416
417         dbus_bus_remove_match(connection, task_rule, NULL);
418         dbus_connection_flush(connection);
419
420         g_hash_table_destroy(task_hash);
421         task_hash = NULL;
422
423         dbus_connection_remove_filter(connection, task_filter, NULL);
424
425         dbus_connection_unref(connection);
426 }