*
* 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
#include <config.h>
#endif
+#include <errno.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <sys/wait.h>
+#include <signal.h>
+
#include <glib.h>
#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 gint task_counter;
+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)
{
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);
}
-struct connman_task *connman_task_create(void)
+/**
+ * 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)
+ if (!task)
return NULL;
- counter = g_atomic_int_exchange_and_add(&task_counter, 1);
+ 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);
}
-static DBusHandlerResult task_filter(DBusConnection *connection,
+/**
+ * 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)
+ return -EINVAL;
+
+ str = g_strdup(name);
+ g_ptr_array_add(task->argv, str);
+
+ va_start(ap, format);
+
+ if (format) {
+ 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)
+ 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)
+ 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;
+ bool result;
+ char **argv, **envp;
+
+ DBG("task %p", task);
+
+ if (task->pid > 0)
+ return -EALREADY;
+
+ if (!stdout_fd)
+ flags |= G_SPAWN_STDOUT_TO_DEV_NULL;
+
+ if (!stderr_fd)
+ 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))
+ g_ptr_array_add(task->argv, NULL);
+
+ if (task->envp->len == 0 ||
+ g_ptr_array_index(task->envp, task->envp->len - 1)) {
+ 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) {
+ 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_idle_add(check_kill, GINT_TO_POINTER(task->pid));
+ }
+
+ return 0;
+}
+
+static DBusHandlerResult task_filter(DBusConnection *conn,
DBusMessage *message, void *user_data)
{
struct connman_task *task;
- const char *path;
+ 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)
+ if (!dbus_message_has_interface(message, CONNMAN_TASK_INTERFACE))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
path = dbus_message_get_path(message);
- if (path == NULL)
+ if (!path)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
task = g_hash_table_lookup(task_hash, path);
- if (task == NULL)
+ if (!task)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ member = dbus_message_get_member(message);
+ if (!member)
+ goto send_reply;
+
+ notify = g_hash_table_lookup(task->notify, member);
+ if (!notify)
+ goto send_reply;
+
+ if (notify->func)
+ reply = notify->func(task, message, notify->data);
+
+send_reply:
+ if (!dbus_message_get_no_reply(message) &&
+ !reply) {
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ }
+
+ if (reply) {
+ dbus_connection_send(conn, reply, NULL);
+
+ dbus_message_unref(reply);
+ }
+
return DBUS_HANDLER_RESULT_HANDLED;
}
static const char *task_rule = "type=method_call"
",interface=" CONNMAN_TASK_INTERFACE;
-static DBusConnection *connection;
-
int __connman_task_init(void)
{
DBG("");
dbus_connection_add_filter(connection, task_filter, NULL, NULL);
- g_atomic_int_set(&task_counter, 0);
+ task_counter = 0;
+ __sync_synchronize();
task_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
NULL, free_task);