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