118c2aff7Sartem /*************************************************************************** 218c2aff7Sartem * CVSID: $Id$ 318c2aff7Sartem * 418c2aff7Sartem * runner.c - Process running code 518c2aff7Sartem * 618c2aff7Sartem * Copyright (C) 2006 Sjoerd Simons, <sjoerd@luon.net> 7*3ab06c27SMilan Jurik * Copyright (C) 2007 Codethink Ltd. Author Rob Taylor <rob.taylor@codethink.co.uk> 818c2aff7Sartem * 918c2aff7Sartem * Licensed under the Academic Free License version 2.1 1018c2aff7Sartem * 1118c2aff7Sartem * This program is free software; you can redistribute it and/or modify 1218c2aff7Sartem * it under the terms of the GNU General Public License as published by 1318c2aff7Sartem * the Free Software Foundation; either version 2 of the License, or 1418c2aff7Sartem * (at your option) any later version. 1518c2aff7Sartem * 1618c2aff7Sartem * This program is distributed in the hope that it will be useful, 1718c2aff7Sartem * but WITHOUT ANY WARRANTY; without even the implied warranty of 1818c2aff7Sartem * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1918c2aff7Sartem * GNU General Public License for more details. 2018c2aff7Sartem * 2118c2aff7Sartem * You should have received a copy of the GNU General Public License 2218c2aff7Sartem * along with this program; if not, write to the Free Software 2318c2aff7Sartem * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 2418c2aff7Sartem * 2518c2aff7Sartem **************************************************************************/ 2618c2aff7Sartem #include <stdio.h> 2718c2aff7Sartem #include <unistd.h> 2818c2aff7Sartem #include <stdlib.h> 2918c2aff7Sartem #include <sys/types.h> 3018c2aff7Sartem #include <sys/stat.h> 3118c2aff7Sartem #include <sys/wait.h> 3218c2aff7Sartem #include <signal.h> 3318c2aff7Sartem #include <string.h> 3418c2aff7Sartem 3518c2aff7Sartem #define DBUS_API_SUBJECT_TO_CHANGE 3618c2aff7Sartem #include <dbus/dbus-glib-lowlevel.h> 3718c2aff7Sartem 3818c2aff7Sartem #include <glib.h> 3918c2aff7Sartem #include "utils.h" 4018c2aff7Sartem #include "runner.h" 4118c2aff7Sartem 4218c2aff7Sartem /* Successful run of the program */ 4318c2aff7Sartem #define HALD_RUN_SUCCESS 0x0 4418c2aff7Sartem /* Process was killed because of running too long */ 4518c2aff7Sartem #define HALD_RUN_TIMEOUT 0x1 4618c2aff7Sartem /* Failed to start for some reason */ 4718c2aff7Sartem #define HALD_RUN_FAILED 0x2 4818c2aff7Sartem /* Killed on purpose, e.g. hal_util_kill_device_helpers */ 4918c2aff7Sartem #define HALD_RUN_KILLED 0x4 5018c2aff7Sartem 5118c2aff7Sartem GHashTable *udi_hash = NULL; 52*3ab06c27SMilan Jurik GList *singletons = NULL; 5318c2aff7Sartem 5418c2aff7Sartem typedef struct { 5518c2aff7Sartem run_request *r; 5618c2aff7Sartem DBusMessage *msg; 5718c2aff7Sartem DBusConnection *con; 5818c2aff7Sartem GPid pid; 5918c2aff7Sartem gint stderr_v; 6018c2aff7Sartem guint watch; 6118c2aff7Sartem guint timeout; 6218c2aff7Sartem gboolean sent_kill; 6318c2aff7Sartem gboolean emit_pid_exited; 6418c2aff7Sartem } run_data; 6518c2aff7Sartem 6618c2aff7Sartem static void 6718c2aff7Sartem del_run_data(run_data *rd) 6818c2aff7Sartem { 6918c2aff7Sartem if (rd == NULL) 7018c2aff7Sartem return; 7118c2aff7Sartem 7218c2aff7Sartem del_run_request(rd->r); 7318c2aff7Sartem if (rd->msg) 7418c2aff7Sartem dbus_message_unref(rd->msg); 7518c2aff7Sartem 7618c2aff7Sartem g_spawn_close_pid(rd->pid); 7718c2aff7Sartem 7818c2aff7Sartem if (rd->stderr_v >= 0) 7918c2aff7Sartem close(rd->stderr_v); 8018c2aff7Sartem 8118c2aff7Sartem if (rd->timeout != 0) 8218c2aff7Sartem g_source_remove(rd->timeout); 8318c2aff7Sartem 8418c2aff7Sartem g_free(rd); 8518c2aff7Sartem } 8618c2aff7Sartem 8718c2aff7Sartem run_request * 8818c2aff7Sartem new_run_request(void) 8918c2aff7Sartem { 9018c2aff7Sartem run_request *result; 9118c2aff7Sartem result = g_new0(run_request, 1); 9218c2aff7Sartem g_assert(result != NULL); 9318c2aff7Sartem return result; 9418c2aff7Sartem } 9518c2aff7Sartem 9618c2aff7Sartem void 9718c2aff7Sartem del_run_request(run_request *r) 9818c2aff7Sartem { 9918c2aff7Sartem if (r == NULL) 10018c2aff7Sartem return; 10118c2aff7Sartem g_free(r->udi); 10218c2aff7Sartem free_string_array(r->environment); 10318c2aff7Sartem free_string_array(r->argv); 10418c2aff7Sartem g_free(r->input); 10518c2aff7Sartem g_free(r); 10618c2aff7Sartem } 10718c2aff7Sartem 10818c2aff7Sartem static void 10918c2aff7Sartem send_reply(DBusConnection *con, DBusMessage *msg, guint32 exit_type, gint32 return_code, gchar **error) 11018c2aff7Sartem { 11118c2aff7Sartem DBusMessage *reply; 11218c2aff7Sartem DBusMessageIter iter; 11318c2aff7Sartem int i; 11418c2aff7Sartem 11518c2aff7Sartem if (con == NULL || msg == NULL) 11618c2aff7Sartem return; 11718c2aff7Sartem 11818c2aff7Sartem reply = dbus_message_new_method_return(msg); 11918c2aff7Sartem g_assert(reply != NULL); 12018c2aff7Sartem 12118c2aff7Sartem dbus_message_iter_init_append(reply, &iter); 12218c2aff7Sartem dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &exit_type); 12318c2aff7Sartem dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &return_code); 12418c2aff7Sartem if (error != NULL) for (i = 0; error[i] != NULL; i++) { 12518c2aff7Sartem dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &error[i]); 12618c2aff7Sartem } 12718c2aff7Sartem 12818c2aff7Sartem dbus_connection_send(con, reply, NULL); 12918c2aff7Sartem dbus_message_unref(reply); 13018c2aff7Sartem } 13118c2aff7Sartem 13218c2aff7Sartem static void 133*3ab06c27SMilan Jurik remove_run_data(run_data *rd) 13418c2aff7Sartem { 13518c2aff7Sartem GList *list; 13618c2aff7Sartem 137*3ab06c27SMilan Jurik if (rd->r->is_singleton) { 138*3ab06c27SMilan Jurik singletons = g_list_remove(singletons, rd); 139*3ab06c27SMilan Jurik } else { 14018c2aff7Sartem /* Remove to the hashtable */ 14118c2aff7Sartem list = (GList *)g_hash_table_lookup(udi_hash, rd->r->udi); 14218c2aff7Sartem list = g_list_remove(list, rd); 14318c2aff7Sartem /* The hash table will take care to not leak the dupped string */ 14418c2aff7Sartem g_hash_table_insert(udi_hash, g_strdup(rd->r->udi), list); 14518c2aff7Sartem } 146*3ab06c27SMilan Jurik } 14718c2aff7Sartem 14818c2aff7Sartem static void 14918c2aff7Sartem run_exited(GPid pid, gint status, gpointer data) 15018c2aff7Sartem { 15118c2aff7Sartem run_data *rd = (run_data *)data; 15218c2aff7Sartem char **error = NULL; 15318c2aff7Sartem 154*3ab06c27SMilan Jurik printf("pid %d: rc=%d signaled=%d: %s\n", 155*3ab06c27SMilan Jurik pid, WEXITSTATUS(status), WIFSIGNALED(status), rd->r->argv[0]); 15618c2aff7Sartem rd->watch = 0; 15718c2aff7Sartem if (rd->sent_kill == TRUE) { 15818c2aff7Sartem /* We send it a kill, so ignore */ 15918c2aff7Sartem del_run_data(rd); 16018c2aff7Sartem return; 16118c2aff7Sartem } 16218c2aff7Sartem /* Check if it was a normal exit */ 16318c2aff7Sartem if (!WIFEXITED(status)) { 16418c2aff7Sartem /* No not normal termination ? crash ? */ 16518c2aff7Sartem send_reply(rd->con, rd->msg, HALD_RUN_FAILED, 0, NULL); 16618c2aff7Sartem goto out; 16718c2aff7Sartem } 16818c2aff7Sartem /* normal exit */ 16918c2aff7Sartem if (rd->stderr_v >= 0) { 17018c2aff7Sartem /* Need to read stderr */ 17118c2aff7Sartem error = get_string_array_from_fd(rd->stderr_v); 17218c2aff7Sartem close(rd->stderr_v); 17318c2aff7Sartem rd->stderr_v = -1; 17418c2aff7Sartem } 17518c2aff7Sartem if (rd->msg != NULL) 17618c2aff7Sartem send_reply(rd->con, rd->msg, HALD_RUN_SUCCESS, WEXITSTATUS(status), error); 17718c2aff7Sartem free_string_array(error); 17818c2aff7Sartem 17918c2aff7Sartem out: 180*3ab06c27SMilan Jurik remove_run_data (rd); 18118c2aff7Sartem 18218c2aff7Sartem /* emit a signal that this PID exited */ 18318c2aff7Sartem if(rd->con != NULL && rd->emit_pid_exited) { 18418c2aff7Sartem DBusMessage *signal; 185*3ab06c27SMilan Jurik gint64 ppid = rd->pid; 18618c2aff7Sartem signal = dbus_message_new_signal ("/org/freedesktop/HalRunner", 18718c2aff7Sartem "org.freedesktop.HalRunner", 18818c2aff7Sartem "StartedProcessExited"); 18918c2aff7Sartem dbus_message_append_args (signal, 190*3ab06c27SMilan Jurik DBUS_TYPE_INT64, &(ppid), 19118c2aff7Sartem DBUS_TYPE_INVALID); 19218c2aff7Sartem dbus_connection_send(rd->con, signal, NULL); 19318c2aff7Sartem } 19418c2aff7Sartem 19518c2aff7Sartem del_run_data(rd); 19618c2aff7Sartem } 19718c2aff7Sartem 19818c2aff7Sartem static gboolean 19918c2aff7Sartem run_timedout(gpointer data) { 20018c2aff7Sartem run_data *rd = (run_data *)data; 20118c2aff7Sartem /* Time is up, kill the process, send reply that it was killed! 20218c2aff7Sartem * Don't wait for exit, because it could hang in state D 20318c2aff7Sartem */ 20418c2aff7Sartem kill(rd->pid, SIGTERM); 20518c2aff7Sartem /* Ensure the timeout is not removed in the delete */ 20618c2aff7Sartem rd->timeout = 0; 20718c2aff7Sartem /* So the exit watch will know it's killed in case it runs*/ 20818c2aff7Sartem rd->sent_kill = TRUE; 20918c2aff7Sartem 21018c2aff7Sartem send_reply(rd->con, rd->msg, HALD_RUN_TIMEOUT, 0, NULL); 211*3ab06c27SMilan Jurik remove_run_data (rd); 21218c2aff7Sartem return FALSE; 21318c2aff7Sartem } 21418c2aff7Sartem 21518c2aff7Sartem static gboolean 21618c2aff7Sartem find_program(char **argv) 21718c2aff7Sartem { 21818c2aff7Sartem /* Search for the program in the dirs where it's allowed to be */ 21918c2aff7Sartem char *program; 22018c2aff7Sartem char *path = NULL; 22118c2aff7Sartem 22218c2aff7Sartem if (argv[0] == NULL) 22318c2aff7Sartem return FALSE; 22418c2aff7Sartem 22518c2aff7Sartem program = g_path_get_basename(argv[0]); 22618c2aff7Sartem 22718c2aff7Sartem /* first search $PATH to make e.g. run-hald.sh work */ 22818c2aff7Sartem path = g_find_program_in_path (program); 22918c2aff7Sartem g_free(program); 23018c2aff7Sartem if (path == NULL) 23118c2aff7Sartem return FALSE; 23218c2aff7Sartem else { 23318c2aff7Sartem /* Replace program in argv[0] with the full path */ 23418c2aff7Sartem g_free(argv[0]); 23518c2aff7Sartem argv[0] = path; 23618c2aff7Sartem } 23718c2aff7Sartem return TRUE; 23818c2aff7Sartem } 23918c2aff7Sartem 24018c2aff7Sartem /* Run the given request and reply it's result on msg */ 24118c2aff7Sartem gboolean 24218c2aff7Sartem run_request_run (run_request *r, DBusConnection *con, DBusMessage *msg, GPid *out_pid) 24318c2aff7Sartem { 24418c2aff7Sartem GPid pid; 24518c2aff7Sartem GError *error = NULL; 24618c2aff7Sartem gint *stdin_p = NULL; 24718c2aff7Sartem gint *stderr_p = NULL; 24818c2aff7Sartem gint stdin_v; 24918c2aff7Sartem gint stderr_v = -1; 25018c2aff7Sartem run_data *rd = NULL; 25118c2aff7Sartem gboolean program_exists = FALSE; 25218c2aff7Sartem char *program_dir = NULL; 25318c2aff7Sartem GList *list; 25418c2aff7Sartem 255*3ab06c27SMilan Jurik printf("Run started %s (%u) (%d) \n!", r->argv[0], r->timeout, 25618c2aff7Sartem r->error_on_stderr); 25718c2aff7Sartem if (r->input != NULL) { 25818c2aff7Sartem stdin_p = &stdin_v; 25918c2aff7Sartem } 26018c2aff7Sartem if (r->error_on_stderr) { 26118c2aff7Sartem stderr_p = &stderr_v; 26218c2aff7Sartem } 26318c2aff7Sartem 26418c2aff7Sartem program_exists = find_program(r->argv); 26518c2aff7Sartem 2667544909dSartem if (program_exists) { 26718c2aff7Sartem program_dir = g_path_get_dirname (r->argv[0]); 26818c2aff7Sartem printf(" full path is '%s', program_dir is '%s'\n", r->argv[0], program_dir); 2697544909dSartem } 27018c2aff7Sartem 27118c2aff7Sartem if (!program_exists || 27218c2aff7Sartem !g_spawn_async_with_pipes(program_dir, r->argv, r->environment, 27318c2aff7Sartem G_SPAWN_DO_NOT_REAP_CHILD, 27418c2aff7Sartem NULL, NULL, &pid, 27518c2aff7Sartem stdin_p, NULL, stderr_p, &error)) { 27618c2aff7Sartem g_free (program_dir); 27718c2aff7Sartem del_run_request(r); 27818c2aff7Sartem if (con && msg) 27918c2aff7Sartem send_reply(con, msg, HALD_RUN_FAILED, 0, NULL); 28018c2aff7Sartem return FALSE; 28118c2aff7Sartem } 28218c2aff7Sartem g_free (program_dir); 28318c2aff7Sartem 28418c2aff7Sartem if (r->input) { 285b941d3fcSartem if (write(stdin_v, r->input, strlen(r->input)) != (ssize_t) strlen(r->input)) 286*3ab06c27SMilan Jurik printf("Warning: Error while writing r->input (%s) to stdin_v.\n", r->input); 28718c2aff7Sartem close(stdin_v); 28818c2aff7Sartem } 28918c2aff7Sartem 29018c2aff7Sartem rd = g_new0(run_data,1); 29118c2aff7Sartem g_assert(rd != NULL); 29218c2aff7Sartem rd->r = r; 29318c2aff7Sartem rd->msg = msg; 29418c2aff7Sartem if (msg != NULL) 29518c2aff7Sartem dbus_message_ref(msg); 29618c2aff7Sartem 29718c2aff7Sartem rd->con = con; 29818c2aff7Sartem rd->pid = pid; 29918c2aff7Sartem rd->stderr_v = stderr_v; 30018c2aff7Sartem rd->sent_kill = FALSE; 30118c2aff7Sartem 30218c2aff7Sartem /* Add watch for exit of the program */ 30318c2aff7Sartem rd->watch = g_child_watch_add(pid, run_exited, rd); 30418c2aff7Sartem 30518c2aff7Sartem /* Add timeout if needed */ 30618c2aff7Sartem if (r->timeout > 0) 30718c2aff7Sartem rd->timeout = g_timeout_add(r->timeout, run_timedout, rd); 30818c2aff7Sartem else 30918c2aff7Sartem rd->timeout = 0; 31018c2aff7Sartem 311*3ab06c27SMilan Jurik if (r->is_singleton) { 312*3ab06c27SMilan Jurik singletons = g_list_prepend(singletons, rd); 313*3ab06c27SMilan Jurik } else { 31418c2aff7Sartem /* Add to the hashtable */ 31518c2aff7Sartem list = (GList *)g_hash_table_lookup(udi_hash, r->udi); 31618c2aff7Sartem list = g_list_prepend(list, rd); 31718c2aff7Sartem 31818c2aff7Sartem /* The hash table will take care to not leak the dupped string */ 31918c2aff7Sartem g_hash_table_insert(udi_hash, g_strdup(r->udi), list); 320*3ab06c27SMilan Jurik } 32118c2aff7Sartem 32218c2aff7Sartem /* send back PID if requested.. and only emit StartedProcessExited in this case */ 32318c2aff7Sartem if (out_pid != NULL) { 32418c2aff7Sartem *out_pid = pid; 32518c2aff7Sartem rd->emit_pid_exited = TRUE; 32618c2aff7Sartem } 32718c2aff7Sartem return TRUE; 32818c2aff7Sartem } 32918c2aff7Sartem 33018c2aff7Sartem static void 33118c2aff7Sartem kill_rd(gpointer data, gpointer user_data) 33218c2aff7Sartem { 33318c2aff7Sartem run_data *rd = (run_data *)data; 33418c2aff7Sartem 33518c2aff7Sartem kill(rd->pid, SIGTERM); 33618c2aff7Sartem printf("Sent kill to %d\n", rd->pid); 33718c2aff7Sartem if (rd->timeout != 0) { 33818c2aff7Sartem /* Remove the timeout watch */ 33918c2aff7Sartem g_source_remove(rd->timeout); 34018c2aff7Sartem rd->timeout = 0; 34118c2aff7Sartem } 34218c2aff7Sartem 34318c2aff7Sartem /* So the exit watch will know it's killed in case it runs */ 34418c2aff7Sartem rd->sent_kill = TRUE; 34518c2aff7Sartem 34618c2aff7Sartem if (rd->msg != NULL) 34718c2aff7Sartem send_reply(rd->con, rd->msg, HALD_RUN_KILLED, 0, NULL); 34818c2aff7Sartem } 34918c2aff7Sartem 35018c2aff7Sartem static void 35118c2aff7Sartem do_kill_udi(gchar *udi) 35218c2aff7Sartem { 35318c2aff7Sartem GList *list; 35418c2aff7Sartem list = (GList *)g_hash_table_lookup(udi_hash, udi); 35518c2aff7Sartem g_list_foreach(list, kill_rd, NULL); 35618c2aff7Sartem g_list_free(list); 35718c2aff7Sartem } 35818c2aff7Sartem 35918c2aff7Sartem /* Kill all running request for a udi */ 36018c2aff7Sartem void 36118c2aff7Sartem run_kill_udi(gchar *udi) 36218c2aff7Sartem { 36318c2aff7Sartem do_kill_udi(udi); 36418c2aff7Sartem g_hash_table_remove(udi_hash, udi); 36518c2aff7Sartem } 36618c2aff7Sartem 36718c2aff7Sartem static gboolean 36818c2aff7Sartem hash_kill_udi(gpointer key, gpointer value, gpointer user_data) { 36918c2aff7Sartem do_kill_udi(key); 37018c2aff7Sartem return TRUE; 37118c2aff7Sartem } 37218c2aff7Sartem 37318c2aff7Sartem /* Kill all running request*/ 37418c2aff7Sartem void 37518c2aff7Sartem run_kill_all() 37618c2aff7Sartem { 37718c2aff7Sartem g_hash_table_foreach_remove(udi_hash, hash_kill_udi, NULL); 378*3ab06c27SMilan Jurik g_list_foreach(singletons, kill_rd, NULL); 37918c2aff7Sartem } 38018c2aff7Sartem 38118c2aff7Sartem void 38218c2aff7Sartem run_init() 38318c2aff7Sartem { 38418c2aff7Sartem udi_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); 38518c2aff7Sartem } 386