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