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
del_run_data(run_data * rd)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 *
new_run_request(void)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
del_run_request(run_request * r)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
send_reply(DBusConnection * con,DBusMessage * msg,guint32 exit_type,gint32 return_code,gchar ** error)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
remove_from_hash_table(run_data * rd)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
run_exited(GPid pid,gint status,gpointer data)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
run_timedout(gpointer data)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
find_program(char ** argv)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
run_request_run(run_request * r,DBusConnection * con,DBusMessage * msg,GPid * out_pid)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
kill_rd(gpointer data,gpointer user_data)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
do_kill_udi(gchar * udi)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
run_kill_udi(gchar * udi)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
hash_kill_udi(gpointer key,gpointer value,gpointer user_data)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
run_kill_all()365 run_kill_all()
366 {
367 g_hash_table_foreach_remove(udi_hash, hash_kill_udi, NULL);
368 }
369
370 void
run_init()371 run_init()
372 {
373 udi_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
374 }
375