runner.c revision 3168:18866604610a
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
50GHashTable *udi_hash = NULL;
51
52typedef 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
64static void
65del_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
85run_request *
86new_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
94void
95del_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
106static void
107send_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
130static void
131remove_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
142static void
143run_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
172out:
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
192static gboolean
193run_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
209static gboolean
210find_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 */
235gboolean
236run_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
320static void
321kill_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
340static void
341do_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 */
350void
351run_kill_udi(gchar *udi)
352{
353	do_kill_udi(udi);
354	g_hash_table_remove(udi_hash, udi);
355}
356
357static gboolean
358hash_kill_udi(gpointer key, gpointer value, gpointer user_data) {
359	do_kill_udi(key);
360	return TRUE;
361}
362
363/* Kill all running request*/
364void
365run_kill_all()
366{
367	g_hash_table_foreach_remove(udi_hash, hash_kill_udi, NULL);
368}
369
370void
371run_init()
372{
373	udi_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
374}
375