#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#define DBUS_API_SUBJECT_TO_CHANGE
#include <dbus/dbus-glib-lowlevel.h>
#include <glib.h>
#include "utils.h"
#include "runner.h"
#define HALD_RUN_SUCCESS 0x0
#define HALD_RUN_TIMEOUT 0x1
#define HALD_RUN_FAILED 0x2
#define HALD_RUN_KILLED 0x4
GHashTable *udi_hash = NULL;
GList *singletons = NULL;
typedef struct {
run_request *r;
DBusMessage *msg;
DBusConnection *con;
GPid pid;
gint stderr_v;
guint watch;
guint timeout;
gboolean sent_kill;
gboolean emit_pid_exited;
} run_data;
static void
del_run_data(run_data *rd)
{
if (rd == NULL)
return;
del_run_request(rd->r);
if (rd->msg)
dbus_message_unref(rd->msg);
g_spawn_close_pid(rd->pid);
if (rd->stderr_v >= 0)
close(rd->stderr_v);
if (rd->timeout != 0)
g_source_remove(rd->timeout);
g_free(rd);
}
run_request *
new_run_request(void)
{
run_request *result;
result = g_new0(run_request, 1);
g_assert(result != NULL);
return result;
}
void
del_run_request(run_request *r)
{
if (r == NULL)
return;
g_free(r->udi);
free_string_array(r->environment);
free_string_array(r->argv);
g_free(r->input);
g_free(r);
}
static void
send_reply(DBusConnection *con, DBusMessage *msg, guint32 exit_type, gint32 return_code, gchar **error)
{
DBusMessage *reply;
DBusMessageIter iter;
int i;
if (con == NULL || msg == NULL)
return;
reply = dbus_message_new_method_return(msg);
g_assert(reply != NULL);
dbus_message_iter_init_append(reply, &iter);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &exit_type);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &return_code);
if (error != NULL) for (i = 0; error[i] != NULL; i++) {
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &error[i]);
}
dbus_connection_send(con, reply, NULL);
dbus_message_unref(reply);
}
static void
remove_run_data(run_data *rd)
{
GList *list;
if (rd->r->is_singleton) {
singletons = g_list_remove(singletons, rd);
} else {
list = (GList *)g_hash_table_lookup(udi_hash, rd->r->udi);
list = g_list_remove(list, rd);
g_hash_table_insert(udi_hash, g_strdup(rd->r->udi), list);
}
}
static void
run_exited(GPid pid, gint status, gpointer data)
{
run_data *rd = (run_data *)data;
char **error = NULL;
printf("pid %d: rc=%d signaled=%d: %s\n",
pid, WEXITSTATUS(status), WIFSIGNALED(status), rd->r->argv[0]);
rd->watch = 0;
if (rd->sent_kill == TRUE) {
del_run_data(rd);
return;
}
if (!WIFEXITED(status)) {
send_reply(rd->con, rd->msg, HALD_RUN_FAILED, 0, NULL);
goto out;
}
if (rd->stderr_v >= 0) {
error = get_string_array_from_fd(rd->stderr_v);
close(rd->stderr_v);
rd->stderr_v = -1;
}
if (rd->msg != NULL)
send_reply(rd->con, rd->msg, HALD_RUN_SUCCESS, WEXITSTATUS(status), error);
free_string_array(error);
out:
remove_run_data (rd);
if(rd->con != NULL && rd->emit_pid_exited) {
DBusMessage *signal;
gint64 ppid = rd->pid;
signal = dbus_message_new_signal ("/org/freedesktop/HalRunner",
"org.freedesktop.HalRunner",
"StartedProcessExited");
dbus_message_append_args (signal,
DBUS_TYPE_INT64, &(ppid),
DBUS_TYPE_INVALID);
dbus_connection_send(rd->con, signal, NULL);
}
del_run_data(rd);
}
static gboolean
run_timedout(gpointer data) {
run_data *rd = (run_data *)data;
kill(rd->pid, SIGTERM);
rd->timeout = 0;
rd->sent_kill = TRUE;
send_reply(rd->con, rd->msg, HALD_RUN_TIMEOUT, 0, NULL);
remove_run_data (rd);
return FALSE;
}
static gboolean
find_program(char **argv)
{
char *program;
char *path = NULL;
if (argv[0] == NULL)
return FALSE;
program = g_path_get_basename(argv[0]);
path = g_find_program_in_path (program);
g_free(program);
if (path == NULL)
return FALSE;
else {
g_free(argv[0]);
argv[0] = path;
}
return TRUE;
}
gboolean
run_request_run (run_request *r, DBusConnection *con, DBusMessage *msg, GPid *out_pid)
{
GPid pid;
GError *error = NULL;
gint *stdin_p = NULL;
gint *stderr_p = NULL;
gint stdin_v;
gint stderr_v = -1;
run_data *rd = NULL;
gboolean program_exists = FALSE;
char *program_dir = NULL;
GList *list;
printf("Run started %s (%u) (%d) \n!", r->argv[0], r->timeout,
r->error_on_stderr);
if (r->input != NULL) {
stdin_p = &stdin_v;
}
if (r->error_on_stderr) {
stderr_p = &stderr_v;
}
program_exists = find_program(r->argv);
if (program_exists) {
program_dir = g_path_get_dirname (r->argv[0]);
printf(" full path is '%s', program_dir is '%s'\n", r->argv[0], program_dir);
}
if (!program_exists ||
!g_spawn_async_with_pipes(program_dir, r->argv, r->environment,
G_SPAWN_DO_NOT_REAP_CHILD,
NULL, NULL, &pid,
stdin_p, NULL, stderr_p, &error)) {
g_free (program_dir);
del_run_request(r);
if (con && msg)
send_reply(con, msg, HALD_RUN_FAILED, 0, NULL);
return FALSE;
}
g_free (program_dir);
if (r->input) {
if (write(stdin_v, r->input, strlen(r->input)) != (ssize_t) strlen(r->input))
printf("Warning: Error while writing r->input (%s) to stdin_v.\n", r->input);
close(stdin_v);
}
rd = g_new0(run_data,1);
g_assert(rd != NULL);
rd->r = r;
rd->msg = msg;
if (msg != NULL)
dbus_message_ref(msg);
rd->con = con;
rd->pid = pid;
rd->stderr_v = stderr_v;
rd->sent_kill = FALSE;
rd->watch = g_child_watch_add(pid, run_exited, rd);
if (r->timeout > 0)
rd->timeout = g_timeout_add(r->timeout, run_timedout, rd);
else
rd->timeout = 0;
if (r->is_singleton) {
singletons = g_list_prepend(singletons, rd);
} else {
list = (GList *)g_hash_table_lookup(udi_hash, r->udi);
list = g_list_prepend(list, rd);
g_hash_table_insert(udi_hash, g_strdup(r->udi), list);
}
if (out_pid != NULL) {
*out_pid = pid;
rd->emit_pid_exited = TRUE;
}
return TRUE;
}
static void
kill_rd(gpointer data, gpointer user_data)
{
run_data *rd = (run_data *)data;
kill(rd->pid, SIGTERM);
printf("Sent kill to %d\n", rd->pid);
if (rd->timeout != 0) {
g_source_remove(rd->timeout);
rd->timeout = 0;
}
rd->sent_kill = TRUE;
if (rd->msg != NULL)
send_reply(rd->con, rd->msg, HALD_RUN_KILLED, 0, NULL);
}
static void
do_kill_udi(gchar *udi)
{
GList *list;
list = (GList *)g_hash_table_lookup(udi_hash, udi);
g_list_foreach(list, kill_rd, NULL);
g_list_free(list);
}
void
run_kill_udi(gchar *udi)
{
do_kill_udi(udi);
g_hash_table_remove(udi_hash, udi);
}
static gboolean
hash_kill_udi(gpointer key, gpointer value, gpointer user_data) {
do_kill_udi(key);
return TRUE;
}
void
run_kill_all()
{
g_hash_table_foreach_remove(udi_hash, hash_kill_udi, NULL);
g_list_foreach(singletons, kill_rd, NULL);
}
void
run_init()
{
udi_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
}