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 50 GHashTable *udi_hash = NULL; 51 52 typedef 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 64 static void 65 del_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 85 run_request * 86 new_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 94 void 95 del_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 106 static void 107 send_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 130 static void 131 remove_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 142 static void 143 run_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 172 out: 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 signal = dbus_message_new_signal ("/org/freedesktop/HalRunner", 179 "org.freedesktop.HalRunner", 180 "StartedProcessExited"); 181 dbus_message_append_args (signal, 182 DBUS_TYPE_INT64, &(rd->pid), 183 DBUS_TYPE_INVALID); 184 dbus_connection_send(rd->con, signal, NULL); 185 } 186 187 del_run_data(rd); 188 } 189 190 static gboolean 191 run_timedout(gpointer data) { 192 run_data *rd = (run_data *)data; 193 /* Time is up, kill the process, send reply that it was killed! 194 * Don't wait for exit, because it could hang in state D 195 */ 196 kill(rd->pid, SIGTERM); 197 /* Ensure the timeout is not removed in the delete */ 198 rd->timeout = 0; 199 /* So the exit watch will know it's killed in case it runs*/ 200 rd->sent_kill = TRUE; 201 202 send_reply(rd->con, rd->msg, HALD_RUN_TIMEOUT, 0, NULL); 203 remove_from_hash_table(rd); 204 return FALSE; 205 } 206 207 static gboolean 208 find_program(char **argv) 209 { 210 /* Search for the program in the dirs where it's allowed to be */ 211 char *program; 212 char *path = NULL; 213 214 if (argv[0] == NULL) 215 return FALSE; 216 217 program = g_path_get_basename(argv[0]); 218 219 /* first search $PATH to make e.g. run-hald.sh work */ 220 path = g_find_program_in_path (program); 221 g_free(program); 222 if (path == NULL) 223 return FALSE; 224 else { 225 /* Replace program in argv[0] with the full path */ 226 g_free(argv[0]); 227 argv[0] = path; 228 } 229 return TRUE; 230 } 231 232 /* Run the given request and reply it's result on msg */ 233 gboolean 234 run_request_run (run_request *r, DBusConnection *con, DBusMessage *msg, GPid *out_pid) 235 { 236 GPid pid; 237 GError *error = NULL; 238 gint *stdin_p = NULL; 239 gint *stderr_p = NULL; 240 gint stdin_v; 241 gint stderr_v = -1; 242 run_data *rd = NULL; 243 gboolean program_exists = FALSE; 244 char *program_dir = NULL; 245 GList *list; 246 247 printf("Run started %s (%d) (%d) \n!", r->argv[0], r->timeout, 248 r->error_on_stderr); 249 if (r->input != NULL) { 250 stdin_p = &stdin_v; 251 } 252 if (r->error_on_stderr) { 253 stderr_p = &stderr_v; 254 } 255 256 program_exists = find_program(r->argv); 257 258 if (program_exists) { 259 program_dir = g_path_get_dirname (r->argv[0]); 260 printf(" full path is '%s', program_dir is '%s'\n", r->argv[0], program_dir); 261 } 262 263 if (!program_exists || 264 !g_spawn_async_with_pipes(program_dir, r->argv, r->environment, 265 G_SPAWN_DO_NOT_REAP_CHILD, 266 NULL, NULL, &pid, 267 stdin_p, NULL, stderr_p, &error)) { 268 g_free (program_dir); 269 del_run_request(r); 270 if (con && msg) 271 send_reply(con, msg, HALD_RUN_FAILED, 0, NULL); 272 return FALSE; 273 } 274 g_free (program_dir); 275 276 if (r->input) { 277 if (write(stdin_v, r->input, strlen(r->input)) != (ssize_t) strlen(r->input)); 278 printf("Warning: Error while wite r->input (%s) to stdin_v.\n", r->input); 279 close(stdin_v); 280 } 281 282 rd = g_new0(run_data,1); 283 g_assert(rd != NULL); 284 rd->r = r; 285 rd->msg = msg; 286 if (msg != NULL) 287 dbus_message_ref(msg); 288 289 rd->con = con; 290 rd->pid = pid; 291 rd->stderr_v = stderr_v; 292 rd->sent_kill = FALSE; 293 294 /* Add watch for exit of the program */ 295 rd->watch = g_child_watch_add(pid, run_exited, rd); 296 297 /* Add timeout if needed */ 298 if (r->timeout > 0) 299 rd->timeout = g_timeout_add(r->timeout, run_timedout, rd); 300 else 301 rd->timeout = 0; 302 303 /* Add to the hashtable */ 304 list = (GList *)g_hash_table_lookup(udi_hash, r->udi); 305 list = g_list_prepend(list, rd); 306 307 /* The hash table will take care to not leak the dupped string */ 308 g_hash_table_insert(udi_hash, g_strdup(r->udi), list); 309 310 /* send back PID if requested.. and only emit StartedProcessExited in this case */ 311 if (out_pid != NULL) { 312 *out_pid = pid; 313 rd->emit_pid_exited = TRUE; 314 } 315 return TRUE; 316 } 317 318 static void 319 kill_rd(gpointer data, gpointer user_data) 320 { 321 run_data *rd = (run_data *)data; 322 323 kill(rd->pid, SIGTERM); 324 printf("Sent kill to %d\n", rd->pid); 325 if (rd->timeout != 0) { 326 /* Remove the timeout watch */ 327 g_source_remove(rd->timeout); 328 rd->timeout = 0; 329 } 330 331 /* So the exit watch will know it's killed in case it runs */ 332 rd->sent_kill = TRUE; 333 334 if (rd->msg != NULL) 335 send_reply(rd->con, rd->msg, HALD_RUN_KILLED, 0, NULL); 336 } 337 338 static void 339 do_kill_udi(gchar *udi) 340 { 341 GList *list; 342 list = (GList *)g_hash_table_lookup(udi_hash, udi); 343 g_list_foreach(list, kill_rd, NULL); 344 g_list_free(list); 345 } 346 347 /* Kill all running request for a udi */ 348 void 349 run_kill_udi(gchar *udi) 350 { 351 do_kill_udi(udi); 352 g_hash_table_remove(udi_hash, udi); 353 } 354 355 static gboolean 356 hash_kill_udi(gpointer key, gpointer value, gpointer user_data) { 357 do_kill_udi(key); 358 return TRUE; 359 } 360 361 /* Kill all running request*/ 362 void 363 run_kill_all() 364 { 365 g_hash_table_foreach_remove(udi_hash, hash_kill_udi, NULL); 366 } 367 368 void 369 run_init() 370 { 371 udi_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); 372 } 373