runner.c revision 3168:18866604610a
1/*************************************************************************** 2 * CVSID: $Id$ 3 * 4 * runner.c - Process running code 5 * 6 * Copyright (C) 2006 Sjoerd Simons, <sjoerd@luon.net> 7 * 8 * Licensed under the Academic Free License version 2.1 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or 13 * (at your option) any later version. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this program; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 * 24 **************************************************************************/ 25#include <stdio.h> 26#include <unistd.h> 27#include <stdlib.h> 28#include <sys/types.h> 29#include <sys/stat.h> 30#include <sys/wait.h> 31#include <signal.h> 32#include <string.h> 33 34#define DBUS_API_SUBJECT_TO_CHANGE 35#include <dbus/dbus-glib-lowlevel.h> 36 37#include <glib.h> 38#include "utils.h" 39#include "runner.h" 40 41/* Successful run of the program */ 42#define HALD_RUN_SUCCESS 0x0 43/* Process was killed because of running too long */ 44#define HALD_RUN_TIMEOUT 0x1 45/* Failed to start for some reason */ 46#define HALD_RUN_FAILED 0x2 47/* Killed on purpose, e.g. hal_util_kill_device_helpers */ 48#define HALD_RUN_KILLED 0x4 49 50GHashTable *udi_hash = NULL; 51 52typedef struct { 53 run_request *r; 54 DBusMessage *msg; 55 DBusConnection *con; 56 GPid pid; 57 gint stderr_v; 58 guint watch; 59 guint timeout; 60 gboolean sent_kill; 61 gboolean emit_pid_exited; 62} run_data; 63 64static void 65del_run_data(run_data *rd) 66{ 67 if (rd == NULL) 68 return; 69 70 del_run_request(rd->r); 71 if (rd->msg) 72 dbus_message_unref(rd->msg); 73 74 g_spawn_close_pid(rd->pid); 75 76 if (rd->stderr_v >= 0) 77 close(rd->stderr_v); 78 79 if (rd->timeout != 0) 80 g_source_remove(rd->timeout); 81 82 g_free(rd); 83} 84 85run_request * 86new_run_request(void) 87{ 88 run_request *result; 89 result = g_new0(run_request, 1); 90 g_assert(result != NULL); 91 return result; 92} 93 94void 95del_run_request(run_request *r) 96{ 97 if (r == NULL) 98 return; 99 g_free(r->udi); 100 free_string_array(r->environment); 101 free_string_array(r->argv); 102 g_free(r->input); 103 g_free(r); 104} 105 106static void 107send_reply(DBusConnection *con, DBusMessage *msg, guint32 exit_type, gint32 return_code, gchar **error) 108{ 109 DBusMessage *reply; 110 DBusMessageIter iter; 111 int i; 112 113 if (con == NULL || msg == NULL) 114 return; 115 116 reply = dbus_message_new_method_return(msg); 117 g_assert(reply != NULL); 118 119 dbus_message_iter_init_append(reply, &iter); 120 dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &exit_type); 121 dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &return_code); 122 if (error != NULL) for (i = 0; error[i] != NULL; i++) { 123 dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &error[i]); 124 } 125 126 dbus_connection_send(con, reply, NULL); 127 dbus_message_unref(reply); 128} 129 130static void 131remove_from_hash_table(run_data *rd) 132{ 133 GList *list; 134 135 /* Remove to the hashtable */ 136 list = (GList *)g_hash_table_lookup(udi_hash, rd->r->udi); 137 list = g_list_remove(list, rd); 138 /* The hash table will take care to not leak the dupped string */ 139 g_hash_table_insert(udi_hash, g_strdup(rd->r->udi), list); 140} 141 142static void 143run_exited(GPid pid, gint status, gpointer data) 144{ 145 run_data *rd = (run_data *)data; 146 char **error = NULL; 147 148 printf("%s exited\n", rd->r->argv[0]); 149 rd->watch = 0; 150 if (rd->sent_kill == TRUE) { 151 /* We send it a kill, so ignore */ 152 del_run_data(rd); 153 return; 154 } 155 /* Check if it was a normal exit */ 156 if (!WIFEXITED(status)) { 157 /* No not normal termination ? crash ? */ 158 send_reply(rd->con, rd->msg, HALD_RUN_FAILED, 0, NULL); 159 goto out; 160 } 161 /* normal exit */ 162 if (rd->stderr_v >= 0) { 163 /* Need to read stderr */ 164 error = get_string_array_from_fd(rd->stderr_v); 165 close(rd->stderr_v); 166 rd->stderr_v = -1; 167 } 168 if (rd->msg != NULL) 169 send_reply(rd->con, rd->msg, HALD_RUN_SUCCESS, WEXITSTATUS(status), error); 170 free_string_array(error); 171 172out: 173 remove_from_hash_table(rd); 174 175 /* emit a signal that this PID exited */ 176 if(rd->con != NULL && rd->emit_pid_exited) { 177 DBusMessage *signal; 178 dbus_int64_t pid64; 179 signal = dbus_message_new_signal ("/org/freedesktop/HalRunner", 180 "org.freedesktop.HalRunner", 181 "StartedProcessExited"); 182 pid64 = rd->pid; 183 dbus_message_append_args (signal, 184 DBUS_TYPE_INT64, &pid64, 185 DBUS_TYPE_INVALID); 186 dbus_connection_send(rd->con, signal, NULL); 187 } 188 189 del_run_data(rd); 190} 191 192static gboolean 193run_timedout(gpointer data) { 194 run_data *rd = (run_data *)data; 195 /* Time is up, kill the process, send reply that it was killed! 196 * Don't wait for exit, because it could hang in state D 197 */ 198 kill(rd->pid, SIGTERM); 199 /* Ensure the timeout is not removed in the delete */ 200 rd->timeout = 0; 201 /* So the exit watch will know it's killed in case it runs*/ 202 rd->sent_kill = TRUE; 203 204 send_reply(rd->con, rd->msg, HALD_RUN_TIMEOUT, 0, NULL); 205 remove_from_hash_table(rd); 206 return FALSE; 207} 208 209static gboolean 210find_program(char **argv) 211{ 212 /* Search for the program in the dirs where it's allowed to be */ 213 char *program; 214 char *path = NULL; 215 216 if (argv[0] == NULL) 217 return FALSE; 218 219 program = g_path_get_basename(argv[0]); 220 221 /* first search $PATH to make e.g. run-hald.sh work */ 222 path = g_find_program_in_path (program); 223 g_free(program); 224 if (path == NULL) 225 return FALSE; 226 else { 227 /* Replace program in argv[0] with the full path */ 228 g_free(argv[0]); 229 argv[0] = path; 230 } 231 return TRUE; 232} 233 234/* Run the given request and reply it's result on msg */ 235gboolean 236run_request_run (run_request *r, DBusConnection *con, DBusMessage *msg, GPid *out_pid) 237{ 238 GPid pid; 239 GError *error = NULL; 240 gint *stdin_p = NULL; 241 gint *stderr_p = NULL; 242 gint stdin_v; 243 gint stderr_v = -1; 244 run_data *rd = NULL; 245 gboolean program_exists = FALSE; 246 char *program_dir = NULL; 247 GList *list; 248 249 printf("Run started %s (%d) (%d) \n!", r->argv[0], r->timeout, 250 r->error_on_stderr); 251 if (r->input != NULL) { 252 stdin_p = &stdin_v; 253 } 254 if (r->error_on_stderr) { 255 stderr_p = &stderr_v; 256 } 257 258 program_exists = find_program(r->argv); 259 260 if (program_exists) { 261 program_dir = g_path_get_dirname (r->argv[0]); 262 printf(" full path is '%s', program_dir is '%s'\n", r->argv[0], program_dir); 263 } 264 265 if (!program_exists || 266 !g_spawn_async_with_pipes(program_dir, r->argv, r->environment, 267 G_SPAWN_DO_NOT_REAP_CHILD, 268 NULL, NULL, &pid, 269 stdin_p, NULL, stderr_p, &error)) { 270 g_free (program_dir); 271 del_run_request(r); 272 if (con && msg) 273 send_reply(con, msg, HALD_RUN_FAILED, 0, NULL); 274 return FALSE; 275 } 276 g_free (program_dir); 277 278 if (r->input) { 279 if (write(stdin_v, r->input, strlen(r->input)) != (ssize_t) strlen(r->input)); 280 printf("Warning: Error while wite r->input (%s) to stdin_v.\n", r->input); 281 close(stdin_v); 282 } 283 284 rd = g_new0(run_data,1); 285 g_assert(rd != NULL); 286 rd->r = r; 287 rd->msg = msg; 288 if (msg != NULL) 289 dbus_message_ref(msg); 290 291 rd->con = con; 292 rd->pid = pid; 293 rd->stderr_v = stderr_v; 294 rd->sent_kill = FALSE; 295 296 /* Add watch for exit of the program */ 297 rd->watch = g_child_watch_add(pid, run_exited, rd); 298 299 /* Add timeout if needed */ 300 if (r->timeout > 0) 301 rd->timeout = g_timeout_add(r->timeout, run_timedout, rd); 302 else 303 rd->timeout = 0; 304 305 /* Add to the hashtable */ 306 list = (GList *)g_hash_table_lookup(udi_hash, r->udi); 307 list = g_list_prepend(list, rd); 308 309 /* The hash table will take care to not leak the dupped string */ 310 g_hash_table_insert(udi_hash, g_strdup(r->udi), list); 311 312 /* send back PID if requested.. and only emit StartedProcessExited in this case */ 313 if (out_pid != NULL) { 314 *out_pid = pid; 315 rd->emit_pid_exited = TRUE; 316 } 317 return TRUE; 318} 319 320static void 321kill_rd(gpointer data, gpointer user_data) 322{ 323 run_data *rd = (run_data *)data; 324 325 kill(rd->pid, SIGTERM); 326 printf("Sent kill to %d\n", rd->pid); 327 if (rd->timeout != 0) { 328 /* Remove the timeout watch */ 329 g_source_remove(rd->timeout); 330 rd->timeout = 0; 331 } 332 333 /* So the exit watch will know it's killed in case it runs */ 334 rd->sent_kill = TRUE; 335 336 if (rd->msg != NULL) 337 send_reply(rd->con, rd->msg, HALD_RUN_KILLED, 0, NULL); 338} 339 340static void 341do_kill_udi(gchar *udi) 342{ 343 GList *list; 344 list = (GList *)g_hash_table_lookup(udi_hash, udi); 345 g_list_foreach(list, kill_rd, NULL); 346 g_list_free(list); 347} 348 349/* Kill all running request for a udi */ 350void 351run_kill_udi(gchar *udi) 352{ 353 do_kill_udi(udi); 354 g_hash_table_remove(udi_hash, udi); 355} 356 357static gboolean 358hash_kill_udi(gpointer key, gpointer value, gpointer user_data) { 359 do_kill_udi(key); 360 return TRUE; 361} 362 363/* Kill all running request*/ 364void 365run_kill_all() 366{ 367 g_hash_table_foreach_remove(udi_hash, hash_kill_udi, NULL); 368} 369 370void 371run_init() 372{ 373 udi_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); 374} 375