/*************************************************************************** * CVSID: $Id$ * * runner.c - Process running code * * Copyright (C) 2006 Sjoerd Simons, * * Licensed under the Academic Free License version 2.1 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * **************************************************************************/ #include #include #include #include #include #include #include #include #define DBUS_API_SUBJECT_TO_CHANGE #include #include #include "utils.h" #include "runner.h" /* Successful run of the program */ #define HALD_RUN_SUCCESS 0x0 /* Process was killed because of running too long */ #define HALD_RUN_TIMEOUT 0x1 /* Failed to start for some reason */ #define HALD_RUN_FAILED 0x2 /* Killed on purpose, e.g. hal_util_kill_device_helpers */ #define HALD_RUN_KILLED 0x4 GHashTable *udi_hash = 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_from_hash_table(run_data *rd) { GList *list; /* Remove to the hashtable */ list = (GList *)g_hash_table_lookup(udi_hash, rd->r->udi); list = g_list_remove(list, rd); /* The hash table will take care to not leak the dupped string */ 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("%s exited\n", rd->r->argv[0]); rd->watch = 0; if (rd->sent_kill == TRUE) { /* We send it a kill, so ignore */ del_run_data(rd); return; } /* Check if it was a normal exit */ if (!WIFEXITED(status)) { /* No not normal termination ? crash ? */ send_reply(rd->con, rd->msg, HALD_RUN_FAILED, 0, NULL); goto out; } /* normal exit */ if (rd->stderr_v >= 0) { /* Need to read stderr */ 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_from_hash_table(rd); /* emit a signal that this PID exited */ if(rd->con != NULL && rd->emit_pid_exited) { DBusMessage *signal; signal = dbus_message_new_signal ("/org/freedesktop/HalRunner", "org.freedesktop.HalRunner", "StartedProcessExited"); dbus_message_append_args (signal, DBUS_TYPE_INT64, &(rd->pid), 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; /* Time is up, kill the process, send reply that it was killed! * Don't wait for exit, because it could hang in state D */ kill(rd->pid, SIGTERM); /* Ensure the timeout is not removed in the delete */ rd->timeout = 0; /* So the exit watch will know it's killed in case it runs*/ rd->sent_kill = TRUE; send_reply(rd->con, rd->msg, HALD_RUN_TIMEOUT, 0, NULL); remove_from_hash_table(rd); return FALSE; } static gboolean find_program(char **argv) { /* Search for the program in the dirs where it's allowed to be */ char *program; char *path = NULL; if (argv[0] == NULL) return FALSE; program = g_path_get_basename(argv[0]); /* first search $PATH to make e.g. run-hald.sh work */ path = g_find_program_in_path (program); g_free(program); if (path == NULL) return FALSE; else { /* Replace program in argv[0] with the full path */ g_free(argv[0]); argv[0] = path; } return TRUE; } /* Run the given request and reply it's result on msg */ 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 (%d) (%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 wite 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; /* Add watch for exit of the program */ rd->watch = g_child_watch_add(pid, run_exited, rd); /* Add timeout if needed */ if (r->timeout > 0) rd->timeout = g_timeout_add(r->timeout, run_timedout, rd); else rd->timeout = 0; /* Add to the hashtable */ list = (GList *)g_hash_table_lookup(udi_hash, r->udi); list = g_list_prepend(list, rd); /* The hash table will take care to not leak the dupped string */ g_hash_table_insert(udi_hash, g_strdup(r->udi), list); /* send back PID if requested.. and only emit StartedProcessExited in this case */ 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) { /* Remove the timeout watch */ g_source_remove(rd->timeout); rd->timeout = 0; } /* So the exit watch will know it's killed in case it runs */ 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); } /* Kill all running request for a udi */ 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; } /* Kill all running request*/ void run_kill_all() { g_hash_table_foreach_remove(udi_hash, hash_kill_udi, NULL); } void run_init() { udi_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); }