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