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 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 192 static gboolean 193 run_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 209 static gboolean 210 find_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 */ 235 gboolean 236 run_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 320 static void 321 kill_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 340 static void 341 do_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 */ 350 void 351 run_kill_udi(gchar *udi) 352 { 353 do_kill_udi(udi); 354 g_hash_table_remove(udi_hash, udi); 355 } 356 357 static gboolean 358 hash_kill_udi(gpointer key, gpointer value, gpointer user_data) { 359 do_kill_udi(key); 360 return TRUE; 361 } 362 363 /* Kill all running request*/ 364 void 365 run_kill_all() 366 { 367 g_hash_table_foreach_remove(udi_hash, hash_kill_udi, NULL); 368 } 369 370 void 371 run_init() 372 { 373 udi_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); 374 } 375