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 
261 	printf("  full path is '%s', program_dir is '%s'\n", r->argv[0], program_dir);
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