X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=src%2Ftask.c;h=97de7e351c9c1948321e451095395fa782eb45ef;hb=230905c20905f2bc5ccf4b8fab75c1b5df2ac31d;hp=97a6592a1c43e25f66fab7585e03e7a202ed02ad;hpb=2690cb17a709c408105c3bb0e5815e5cb0aed818;p=framework%2Fconnectivity%2Fconnman.git diff --git a/src/task.c b/src/task.c index 97a6592..97de7e3 100644 --- a/src/task.c +++ b/src/task.c @@ -2,7 +2,7 @@ * * Connection Manager * - * Copyright (C) 2007-2009 Intel Corporation. All rights reserved. + * Copyright (C) 2007-2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -23,4 +23,477 @@ #include #endif +#include +#include +#include +#include +#include + +#include + #include "connman.h" + +struct notify_data { + connman_task_notify_t func; + void *data; +}; + +struct connman_task { + char *path; + pid_t pid; + guint child_watch; + GPtrArray *argv; + GPtrArray *envp; + connman_task_exit_t exit_func; + void *exit_data; + GHashTable *notify; +}; + +static GHashTable *task_hash = NULL; + +static volatile int task_counter; + +static DBusConnection *connection; + +static void free_pointer(gpointer data, gpointer user_data) +{ + g_free(data); +} + +static void free_task(gpointer data) +{ + struct connman_task *task = data; + + DBG("task %p", task); + + g_hash_table_destroy(task->notify); + task->notify = NULL; + + if (task->pid > 0) + kill(task->pid, SIGTERM); + + if (task->child_watch > 0) + g_source_remove(task->child_watch); + + g_ptr_array_foreach(task->envp, free_pointer, NULL); + g_ptr_array_free(task->envp, TRUE); + + g_ptr_array_foreach(task->argv, free_pointer, NULL); + g_ptr_array_free(task->argv, TRUE); + + g_free(task->path); + g_free(task); +} + +/** + * connman_task_create: + * @program: name of executable + * + * Allocate a new task of given #program + * + * Returns: a newly-allocated #connman_task structure + */ +struct connman_task *connman_task_create(const char *program) +{ + struct connman_task *task; + gint counter; + char *str; + + DBG(""); + + task = g_try_new0(struct connman_task, 1); + if (task == NULL) + return NULL; + + counter = __sync_fetch_and_add(&task_counter, 1); + + task->path = g_strdup_printf("/task/%d", counter); + task->pid = -1; + + task->argv = g_ptr_array_new(); + task->envp = g_ptr_array_new(); + + str = g_strdup(program); + g_ptr_array_add(task->argv, str); + + task->notify = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + + DBG("task %p", task); + + g_hash_table_insert(task_hash, task->path, task); + + return task; +} + +/** + * connman_task_destory: + * @task: task structure + * + * Remove and destory #task + */ +void connman_task_destroy(struct connman_task *task) +{ + DBG("task %p", task); + + g_hash_table_remove(task_hash, task->path); +} + +/** + * connman_task_get_path: + * @task: task structure + * + * Get object path + */ +const char *connman_task_get_path(struct connman_task *task) +{ + return task->path; +} + +/** + * connman_task_add_argument: + * @task: task structure + * @name: argument name + * @format: format string + * @Varargs: list of arguments + * + * Add a new command line argument + */ +int connman_task_add_argument(struct connman_task *task, + const char *name, const char *format, ...) +{ + va_list ap; + char *str; + + DBG("task %p arg %s", task, name); + + if (name == NULL) + return -EINVAL; + + str = g_strdup(name); + g_ptr_array_add(task->argv, str); + + va_start(ap, format); + + if (format != NULL) { + str = g_strdup_vprintf(format, ap); + g_ptr_array_add(task->argv, str); + } + + va_end(ap); + + return 0; +} + +/** + * connman_task_add_variable: + * @task: task structure + * @key: variable name + * @format: format string + * @Varargs: list of arguments + * + * Add a new environment variable + */ +int connman_task_add_variable(struct connman_task *task, + const char *key, const char *format, ...) +{ + va_list ap; + char *str, *val; + + DBG("task %p key %s", task, key); + + if (key == NULL) + return -EINVAL; + + va_start(ap, format); + + val = g_strdup_vprintf(format, ap); + str = g_strdup_printf("%s=%s", key, format ? format : ""); + g_ptr_array_add(task->envp, str); + g_free(val); + + va_end(ap); + + return 0; +} + +/** + * connman_task_set_notify: + * @task: task structure + * @member: notifcation method name + * @function: notification callback + * @user_data: optional notification user data + * + * Set notification handler for #member + */ +int connman_task_set_notify(struct connman_task *task, const char *member, + connman_task_notify_t function, void *user_data) +{ + struct notify_data *notify; + + DBG("task %p", task); + + notify = g_try_new0(struct notify_data, 1); + if (notify == NULL) + return -ENOMEM; + + notify->func = function; + notify->data = user_data; + + g_hash_table_replace(task->notify, g_strdup(member), notify); + + return 0; +} + +static void task_died(GPid pid, gint status, gpointer user_data) +{ + struct connman_task *task = user_data; + int exit_code; + + if (WIFEXITED(status)) { + exit_code = WEXITSTATUS(status); + DBG("task %p exit status %d", task, exit_code); + } else { + exit_code = 0; + DBG("task %p signal %d", task, WTERMSIG(status)); + } + + g_spawn_close_pid(pid); + task->pid = -1; + + task->child_watch = 0; + + if (task->exit_func) + task->exit_func(task, exit_code, task->exit_data); +} + +static void task_setup(gpointer user_data) +{ + sigset_t mask; + struct connman_task *task = user_data; + + DBG("task %p", task); + + sigemptyset(&mask); + if (sigprocmask(SIG_SETMASK, &mask, NULL) < 0) + connman_error("Failed to clean signal mask"); +} + +/** + * connman_task_run: + * @task: task structure + * @function: exit callback + * @user_data: optional exit user data + * @fd: optional spawn with pipe + * + * Execute program specified by #task + */ +int connman_task_run(struct connman_task *task, + connman_task_exit_t function, void *user_data, + int *stdin_fd, int *stdout_fd, int *stderr_fd) +{ + GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD; + gboolean result; + char **argv, **envp; + + DBG("task %p", task); + + if (task->pid > 0) + return -EALREADY; + + if (stdout_fd == NULL) + flags |= G_SPAWN_STDOUT_TO_DEV_NULL; + + if (stderr_fd == NULL) + flags |= G_SPAWN_STDERR_TO_DEV_NULL; + + task->exit_func = function; + task->exit_data = user_data; + + if (g_ptr_array_index(task->argv, task->argv->len - 1) != NULL) + g_ptr_array_add(task->argv, NULL); + + if (task->envp->len == 0 || g_ptr_array_index(task->envp, + task->envp->len - 1) != NULL) { + if (g_hash_table_size(task->notify) > 0) { + const char *busname; + char *str; + + busname = dbus_bus_get_unique_name(connection); + str = g_strdup_printf("CONNMAN_BUSNAME=%s", busname); + g_ptr_array_add(task->envp, str); + + str = g_strdup_printf("CONNMAN_INTERFACE=%s", + CONNMAN_TASK_INTERFACE); + g_ptr_array_add(task->envp, str); + + str = g_strdup_printf("CONNMAN_PATH=%s", task->path); + g_ptr_array_add(task->envp, str); + } + + g_ptr_array_add(task->envp, NULL); + } + + argv = (char **) task->argv->pdata; + envp = (char **) task->envp->pdata; + + result = g_spawn_async_with_pipes(NULL, argv, envp, flags, + task_setup, task, &task->pid, + stdin_fd, stdout_fd, stderr_fd, NULL); + if (result == FALSE) { + connman_error("Failed to spawn %s", argv[0]); + return -EIO; + } + + task->child_watch = g_child_watch_add(task->pid, task_died, task); + + return 0; +} + +static gboolean force_kill_timeout(gpointer user_data) +{ + pid_t pid = GPOINTER_TO_INT(user_data); + if (pid > 0) { + if (kill(pid, SIGKILL) == 0) + connman_warn("killing pid %d by force", pid); + } + + return FALSE; +} + +static gboolean kill_timeout(gpointer user_data) +{ + pid_t pid = GPOINTER_TO_INT(user_data); + if (pid > 0) { + if (kill(pid, SIGINT) == 0) + g_timeout_add_seconds(1, force_kill_timeout, + GINT_TO_POINTER(pid)); + } + + return FALSE; +} + +static gboolean check_kill(gpointer user_data) +{ + pid_t pid = GPOINTER_TO_INT(user_data); + if (pid > 0) { + if (kill(pid, 0) == 0) { + connman_info("pid %d was not killed, " + "retrying after 2 sec", pid); + g_timeout_add_seconds(2, kill_timeout, + GINT_TO_POINTER(pid)); + } + } + + return FALSE; +} + +/** + * connman_task_stop: + * @task: task structure + * + * Stop program specified by #task + */ +int connman_task_stop(struct connman_task *task) +{ + DBG("task %p", task); + + if (task->pid > 0) { + kill(task->pid, SIGTERM); + + g_timeout_add_seconds(0, check_kill, + GINT_TO_POINTER(task->pid)); + } + + return 0; +} + +static DBusHandlerResult task_filter(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct connman_task *task; + struct notify_data *notify; + const char *path, *member; + DBusMessage *reply = NULL; + + if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_has_interface(message, + CONNMAN_TASK_INTERFACE) == FALSE) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + path = dbus_message_get_path(message); + if (path == NULL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + task = g_hash_table_lookup(task_hash, path); + if (task == NULL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + member = dbus_message_get_member(message); + if (member == NULL) + goto send_reply; + + notify = g_hash_table_lookup(task->notify, member); + if (notify == NULL) + goto send_reply; + + if (notify->func) + reply = notify->func(task, message, notify->data); + +send_reply: + if (dbus_message_get_no_reply(message) == FALSE && + reply == NULL) { + + reply = dbus_message_new_method_return(message); + if (reply == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + if (reply != NULL) { + dbus_connection_send(connection, reply, NULL); + + dbus_message_unref(reply); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static const char *task_rule = "type=method_call" + ",interface=" CONNMAN_TASK_INTERFACE; + +int __connman_task_init(void) +{ + DBG(""); + + connection = connman_dbus_get_connection(); + + dbus_connection_add_filter(connection, task_filter, NULL, NULL); + + task_counter = 0; + __sync_synchronize(); + + task_hash = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, free_task); + + dbus_bus_add_match(connection, task_rule, NULL); + dbus_connection_flush(connection); + + return 0; +} + +void __connman_task_cleanup(void) +{ + DBG(""); + + dbus_bus_remove_match(connection, task_rule, NULL); + dbus_connection_flush(connection); + + g_hash_table_destroy(task_hash); + task_hash = NULL; + + dbus_connection_remove_filter(connection, task_filter, NULL); + + dbus_connection_unref(connection); +}