xref: /illumos-gate/usr/src/cmd/hal/hald-runner/runner.c (revision 20a7641f9918de8574b8b3b47dbe35c4bfc78df1)
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