118c2aff7Sartem /***************************************************************************
218c2aff7Sartem  * CVSID: $Id$
318c2aff7Sartem  *
418c2aff7Sartem  * runner.c - Process running code
518c2aff7Sartem  *
618c2aff7Sartem  * Copyright (C) 2006 Sjoerd Simons, <sjoerd@luon.net>
73ab06c27SMilan Jurik  * Copyright (C) 2007 Codethink Ltd. Author Rob Taylor <rob.taylor@codethink.co.uk>
818c2aff7Sartem  *
918c2aff7Sartem  * Licensed under the Academic Free License version 2.1
1018c2aff7Sartem  *
1118c2aff7Sartem  * This program is free software; you can redistribute it and/or modify
1218c2aff7Sartem  * it under the terms of the GNU General Public License as published by
1318c2aff7Sartem  * the Free Software Foundation; either version 2 of the License, or
1418c2aff7Sartem  * (at your option) any later version.
1518c2aff7Sartem  *
1618c2aff7Sartem  * This program is distributed in the hope that it will be useful,
1718c2aff7Sartem  * but WITHOUT ANY WARRANTY; without even the implied warranty of
1818c2aff7Sartem  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1918c2aff7Sartem  * GNU General Public License for more details.
2018c2aff7Sartem  *
2118c2aff7Sartem  * You should have received a copy of the GNU General Public License
2218c2aff7Sartem  * along with this program; if not, write to the Free Software
2318c2aff7Sartem  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
2418c2aff7Sartem  *
2518c2aff7Sartem  **************************************************************************/
2618c2aff7Sartem #include <stdio.h>
2718c2aff7Sartem #include <unistd.h>
2818c2aff7Sartem #include <stdlib.h>
2918c2aff7Sartem #include <sys/types.h>
3018c2aff7Sartem #include <sys/stat.h>
3118c2aff7Sartem #include <sys/wait.h>
3218c2aff7Sartem #include <signal.h>
3318c2aff7Sartem #include <string.h>
3418c2aff7Sartem 
35*55fea89dSDan Cross #define DBUS_API_SUBJECT_TO_CHANGE
3618c2aff7Sartem #include <dbus/dbus-glib-lowlevel.h>
3718c2aff7Sartem 
3818c2aff7Sartem #include <glib.h>
3918c2aff7Sartem #include "utils.h"
4018c2aff7Sartem #include "runner.h"
4118c2aff7Sartem 
4218c2aff7Sartem /* Successful run of the program */
43*55fea89dSDan Cross #define HALD_RUN_SUCCESS 0x0
4418c2aff7Sartem /* Process was killed because of running too long */
45*55fea89dSDan Cross #define  HALD_RUN_TIMEOUT 0x1
4618c2aff7Sartem /* Failed to start for some reason */
4718c2aff7Sartem #define HALD_RUN_FAILED 0x2
48*55fea89dSDan Cross /* Killed on purpose, e.g. hal_util_kill_device_helpers */
4918c2aff7Sartem #define HALD_RUN_KILLED 0x4
5018c2aff7Sartem 
5118c2aff7Sartem GHashTable *udi_hash = NULL;
523ab06c27SMilan Jurik GList *singletons = NULL;
5318c2aff7Sartem 
5418c2aff7Sartem typedef struct {
5518c2aff7Sartem 	run_request *r;
5618c2aff7Sartem 	DBusMessage *msg;
5718c2aff7Sartem 	DBusConnection *con;
5818c2aff7Sartem 	GPid pid;
5918c2aff7Sartem 	gint stderr_v;
6018c2aff7Sartem 	guint watch;
6118c2aff7Sartem 	guint timeout;
6218c2aff7Sartem 	gboolean sent_kill;
6318c2aff7Sartem 	gboolean emit_pid_exited;
6418c2aff7Sartem } run_data;
6518c2aff7Sartem 
6618c2aff7Sartem static void
del_run_data(run_data * rd)6718c2aff7Sartem del_run_data(run_data *rd)
6818c2aff7Sartem {
6918c2aff7Sartem 	if (rd == NULL)
7018c2aff7Sartem 		return;
7118c2aff7Sartem 
7218c2aff7Sartem 	del_run_request(rd->r);
7318c2aff7Sartem 	if (rd->msg)
7418c2aff7Sartem 		dbus_message_unref(rd->msg);
7518c2aff7Sartem 
7618c2aff7Sartem 	g_spawn_close_pid(rd->pid);
7718c2aff7Sartem 
7818c2aff7Sartem 	if (rd->stderr_v >= 0)
7918c2aff7Sartem 		close(rd->stderr_v);
8018c2aff7Sartem 
8118c2aff7Sartem 	if (rd->timeout != 0)
8218c2aff7Sartem 		g_source_remove(rd->timeout);
8318c2aff7Sartem 
8418c2aff7Sartem 	g_free(rd);
8518c2aff7Sartem }
8618c2aff7Sartem 
8718c2aff7Sartem run_request *
new_run_request(void)8818c2aff7Sartem new_run_request(void)
8918c2aff7Sartem {
9018c2aff7Sartem 	run_request *result;
9118c2aff7Sartem 	result = g_new0(run_request, 1);
9218c2aff7Sartem 	g_assert(result != NULL);
9318c2aff7Sartem 	return result;
9418c2aff7Sartem }
9518c2aff7Sartem 
9618c2aff7Sartem void
del_run_request(run_request * r)9718c2aff7Sartem del_run_request(run_request *r)
9818c2aff7Sartem {
9918c2aff7Sartem 	if (r == NULL)
10018c2aff7Sartem 		return;
10118c2aff7Sartem 	g_free(r->udi);
10218c2aff7Sartem 	free_string_array(r->environment);
10318c2aff7Sartem 	free_string_array(r->argv);
10418c2aff7Sartem 	g_free(r->input);
10518c2aff7Sartem 	g_free(r);
10618c2aff7Sartem }
10718c2aff7Sartem 
10818c2aff7Sartem static void
send_reply(DBusConnection * con,DBusMessage * msg,guint32 exit_type,gint32 return_code,gchar ** error)10918c2aff7Sartem send_reply(DBusConnection *con, DBusMessage *msg, guint32 exit_type, gint32 return_code, gchar **error)
11018c2aff7Sartem {
11118c2aff7Sartem 	DBusMessage *reply;
11218c2aff7Sartem 	DBusMessageIter iter;
11318c2aff7Sartem 	int i;
11418c2aff7Sartem 
115*55fea89dSDan Cross 	if (con == NULL || msg == NULL)
11618c2aff7Sartem 		return;
11718c2aff7Sartem 
11818c2aff7Sartem 	reply = dbus_message_new_method_return(msg);
11918c2aff7Sartem 	g_assert(reply != NULL);
12018c2aff7Sartem 
12118c2aff7Sartem 	dbus_message_iter_init_append(reply, &iter);
12218c2aff7Sartem 	dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &exit_type);
12318c2aff7Sartem 	dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &return_code);
12418c2aff7Sartem 	if (error != NULL) for (i = 0; error[i] != NULL; i++) {
12518c2aff7Sartem 		dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &error[i]);
12618c2aff7Sartem 	}
12718c2aff7Sartem 
12818c2aff7Sartem 	dbus_connection_send(con, reply, NULL);
12918c2aff7Sartem 	dbus_message_unref(reply);
13018c2aff7Sartem }
13118c2aff7Sartem 
13218c2aff7Sartem static void
remove_run_data(run_data * rd)1333ab06c27SMilan Jurik remove_run_data(run_data *rd)
13418c2aff7Sartem {
13518c2aff7Sartem 	GList *list;
13618c2aff7Sartem 
1373ab06c27SMilan Jurik 	if (rd->r->is_singleton) {
1383ab06c27SMilan Jurik 		singletons = g_list_remove(singletons, rd);
1393ab06c27SMilan Jurik 	} else {
1403ab06c27SMilan Jurik 		/* Remove to the hashtable */
1413ab06c27SMilan Jurik 		list = (GList *)g_hash_table_lookup(udi_hash, rd->r->udi);
1423ab06c27SMilan Jurik 		list = g_list_remove(list, rd);
1433ab06c27SMilan Jurik 		/* The hash table will take care to not leak the dupped string */
1443ab06c27SMilan Jurik 		g_hash_table_insert(udi_hash, g_strdup(rd->r->udi), list);
1453ab06c27SMilan Jurik 	}
14618c2aff7Sartem }
14718c2aff7Sartem 
14818c2aff7Sartem static void
run_exited(GPid pid,gint status,gpointer data)14918c2aff7Sartem run_exited(GPid pid, gint status, gpointer data)
15018c2aff7Sartem {
15118c2aff7Sartem 	run_data *rd = (run_data *)data;
15218c2aff7Sartem 	char **error = NULL;
15318c2aff7Sartem 
154*55fea89dSDan Cross 	printf("pid %d: rc=%d signaled=%d: %s\n",
1553ab06c27SMilan Jurik                pid, WEXITSTATUS(status), WIFSIGNALED(status), rd->r->argv[0]);
15618c2aff7Sartem 	rd->watch = 0;
15718c2aff7Sartem 	if (rd->sent_kill == TRUE) {
15818c2aff7Sartem 		/* We send it a kill, so ignore */
15918c2aff7Sartem 		del_run_data(rd);
16018c2aff7Sartem 		return;
16118c2aff7Sartem 	}
16218c2aff7Sartem 	/* Check if it was a normal exit */
16318c2aff7Sartem 	if (!WIFEXITED(status)) {
16418c2aff7Sartem 		/* No not normal termination ? crash ? */
16518c2aff7Sartem 		send_reply(rd->con, rd->msg, HALD_RUN_FAILED, 0, NULL);
16618c2aff7Sartem 		goto out;
16718c2aff7Sartem 	}
16818c2aff7Sartem 	/* normal exit */
16918c2aff7Sartem 	if (rd->stderr_v >= 0) {
17018c2aff7Sartem 		/* Need to read stderr */
17118c2aff7Sartem 		error = get_string_array_from_fd(rd->stderr_v);
17218c2aff7Sartem 		close(rd->stderr_v);
17318c2aff7Sartem 		rd->stderr_v = -1;
17418c2aff7Sartem 	}
17518c2aff7Sartem 	if (rd->msg != NULL)
17618c2aff7Sartem 		send_reply(rd->con, rd->msg, HALD_RUN_SUCCESS, WEXITSTATUS(status), error);
17718c2aff7Sartem 	free_string_array(error);
17818c2aff7Sartem 
17918c2aff7Sartem out:
1803ab06c27SMilan Jurik 	remove_run_data (rd);
181*55fea89dSDan Cross 
18218c2aff7Sartem 	/* emit a signal that this PID exited */
18318c2aff7Sartem 	if(rd->con != NULL && rd->emit_pid_exited) {
18418c2aff7Sartem 		DBusMessage *signal;
1853ab06c27SMilan Jurik 		gint64 ppid = rd->pid;
18618c2aff7Sartem 		signal = dbus_message_new_signal ("/org/freedesktop/HalRunner",
18718c2aff7Sartem 						  "org.freedesktop.HalRunner",
18818c2aff7Sartem 						  "StartedProcessExited");
189*55fea89dSDan Cross 		dbus_message_append_args (signal,
1903ab06c27SMilan Jurik 					  DBUS_TYPE_INT64, &(ppid),
19118c2aff7Sartem 					  DBUS_TYPE_INVALID);
19218c2aff7Sartem 		dbus_connection_send(rd->con, signal, NULL);
19318c2aff7Sartem 	}
194*55fea89dSDan Cross 
19518c2aff7Sartem 	del_run_data(rd);
19618c2aff7Sartem }
19718c2aff7Sartem 
19818c2aff7Sartem static gboolean
run_timedout(gpointer data)19918c2aff7Sartem run_timedout(gpointer data) {
20018c2aff7Sartem 	run_data *rd = (run_data *)data;
201*55fea89dSDan Cross 	/* Time is up, kill the process, send reply that it was killed!
20218c2aff7Sartem 	 * Don't wait for exit, because it could hang in state D
203*55fea89dSDan Cross 	 */
20418c2aff7Sartem 	kill(rd->pid, SIGTERM);
20518c2aff7Sartem 	/* Ensure the timeout is not removed in the delete */
20618c2aff7Sartem 	rd->timeout = 0;
20718c2aff7Sartem 	/* So the exit watch will know it's killed  in case it runs*/
20818c2aff7Sartem 	rd->sent_kill = TRUE;
20918c2aff7Sartem 
21018c2aff7Sartem 	send_reply(rd->con, rd->msg, HALD_RUN_TIMEOUT, 0, NULL);
2113ab06c27SMilan Jurik 	remove_run_data (rd);
21218c2aff7Sartem 	return FALSE;
21318c2aff7Sartem }
21418c2aff7Sartem 
21518c2aff7Sartem static gboolean
find_program(char ** argv)21618c2aff7Sartem find_program(char **argv)
21718c2aff7Sartem {
21818c2aff7Sartem 	/* Search for the program in the dirs where it's allowed to be */
21918c2aff7Sartem 	char *program;
22018c2aff7Sartem 	char *path = NULL;
22118c2aff7Sartem 
222*55fea89dSDan Cross 	if (argv[0] == NULL)
22318c2aff7Sartem 		return FALSE;
22418c2aff7Sartem 
22518c2aff7Sartem 	program = g_path_get_basename(argv[0]);
22618c2aff7Sartem 
22718c2aff7Sartem 	/* first search $PATH to make e.g. run-hald.sh work */
22818c2aff7Sartem 	path = g_find_program_in_path (program);
22918c2aff7Sartem 	g_free(program);
23018c2aff7Sartem 	if (path == NULL)
23118c2aff7Sartem 		return FALSE;
23218c2aff7Sartem 	else {
23318c2aff7Sartem 		/* Replace program in argv[0] with the full path */
23418c2aff7Sartem 		g_free(argv[0]);
23518c2aff7Sartem 		argv[0] = path;
23618c2aff7Sartem 	}
23718c2aff7Sartem 	return TRUE;
23818c2aff7Sartem }
23918c2aff7Sartem 
24018c2aff7Sartem /* Run the given request and reply it's result on msg */
24118c2aff7Sartem gboolean
run_request_run(run_request * r,DBusConnection * con,DBusMessage * msg,GPid * out_pid)24218c2aff7Sartem run_request_run (run_request *r, DBusConnection *con, DBusMessage *msg, GPid *out_pid)
24318c2aff7Sartem {
24418c2aff7Sartem 	GPid pid;
24518c2aff7Sartem 	GError *error = NULL;
24618c2aff7Sartem 	gint *stdin_p = NULL;
24718c2aff7Sartem 	gint *stderr_p = NULL;
24818c2aff7Sartem 	gint stdin_v;
24918c2aff7Sartem 	gint stderr_v = -1;
25018c2aff7Sartem 	run_data *rd = NULL;
25118c2aff7Sartem 	gboolean program_exists = FALSE;
25218c2aff7Sartem 	char *program_dir = NULL;
25318c2aff7Sartem 	GList *list;
25418c2aff7Sartem 
2553ab06c27SMilan Jurik 	printf("Run started %s (%u) (%d) \n!", r->argv[0], r->timeout,
25618c2aff7Sartem 		r->error_on_stderr);
25718c2aff7Sartem 	if (r->input != NULL) {
258*55fea89dSDan Cross 		stdin_p = &stdin_v;
25918c2aff7Sartem 	}
26018c2aff7Sartem 	if (r->error_on_stderr) {
26118c2aff7Sartem 		stderr_p = &stderr_v;
26218c2aff7Sartem 	}
26318c2aff7Sartem 
26418c2aff7Sartem 	program_exists = find_program(r->argv);
26518c2aff7Sartem 
2667544909dSartem 	if (program_exists) {
26718c2aff7Sartem 		program_dir = g_path_get_dirname (r->argv[0]);
2687544909dSartem 		printf("  full path is '%s', program_dir is '%s'\n", r->argv[0], program_dir);
2697544909dSartem 	}
27018c2aff7Sartem 
27118c2aff7Sartem 	if (!program_exists ||
27218c2aff7Sartem 		!g_spawn_async_with_pipes(program_dir, r->argv, r->environment,
27318c2aff7Sartem 		                          G_SPAWN_DO_NOT_REAP_CHILD,
27418c2aff7Sartem 		                          NULL, NULL, &pid,
27518c2aff7Sartem 		                          stdin_p, NULL, stderr_p, &error)) {
27618c2aff7Sartem 		g_free (program_dir);
27718c2aff7Sartem 		del_run_request(r);
27818c2aff7Sartem 		if (con && msg)
27918c2aff7Sartem 			send_reply(con, msg, HALD_RUN_FAILED, 0, NULL);
28018c2aff7Sartem 		return FALSE;
28118c2aff7Sartem 	}
28218c2aff7Sartem 	g_free (program_dir);
28318c2aff7Sartem 
28418c2aff7Sartem 	if (r->input) {
285b941d3fcSartem 		if (write(stdin_v, r->input, strlen(r->input)) != (ssize_t) strlen(r->input))
2863ab06c27SMilan Jurik 			printf("Warning: Error while writing r->input (%s) to stdin_v.\n", r->input);
28718c2aff7Sartem 		close(stdin_v);
28818c2aff7Sartem 	}
28918c2aff7Sartem 
29018c2aff7Sartem 	rd = g_new0(run_data,1);
29118c2aff7Sartem 	g_assert(rd != NULL);
29218c2aff7Sartem 	rd->r = r;
29318c2aff7Sartem 	rd->msg = msg;
29418c2aff7Sartem 	if (msg != NULL)
29518c2aff7Sartem 		dbus_message_ref(msg);
29618c2aff7Sartem 
29718c2aff7Sartem 	rd->con = con;
29818c2aff7Sartem 	rd->pid = pid;
29918c2aff7Sartem 	rd->stderr_v = stderr_v;
30018c2aff7Sartem 	rd->sent_kill = FALSE;
30118c2aff7Sartem 
30218c2aff7Sartem 	/* Add watch for exit of the program */
30318c2aff7Sartem 	rd->watch = g_child_watch_add(pid, run_exited, rd);
30418c2aff7Sartem 
30518c2aff7Sartem 	/* Add timeout if needed */
30618c2aff7Sartem 	if (r->timeout > 0)
30718c2aff7Sartem 		rd->timeout = g_timeout_add(r->timeout, run_timedout, rd);
30818c2aff7Sartem 	else
30918c2aff7Sartem 		rd->timeout = 0;
31018c2aff7Sartem 
3113ab06c27SMilan Jurik 	if (r->is_singleton) {
3123ab06c27SMilan Jurik 		singletons = g_list_prepend(singletons, rd);
3133ab06c27SMilan Jurik 	} else {
3143ab06c27SMilan Jurik 		/* Add to the hashtable */
3153ab06c27SMilan Jurik 		list = (GList *)g_hash_table_lookup(udi_hash, r->udi);
3163ab06c27SMilan Jurik 		list = g_list_prepend(list, rd);
31718c2aff7Sartem 
3183ab06c27SMilan Jurik 		/* The hash table will take care to not leak the dupped string */
3193ab06c27SMilan Jurik 		g_hash_table_insert(udi_hash, g_strdup(r->udi), list);
3203ab06c27SMilan Jurik 	}
32118c2aff7Sartem 
32218c2aff7Sartem 	/* send back PID if requested.. and only emit StartedProcessExited in this case */
32318c2aff7Sartem 	if (out_pid != NULL) {
32418c2aff7Sartem 		*out_pid = pid;
32518c2aff7Sartem 		rd->emit_pid_exited = TRUE;
32618c2aff7Sartem 	}
32718c2aff7Sartem 	return TRUE;
32818c2aff7Sartem }
32918c2aff7Sartem 
33018c2aff7Sartem static void
kill_rd(gpointer data,gpointer user_data)33118c2aff7Sartem kill_rd(gpointer data, gpointer user_data)
33218c2aff7Sartem {
33318c2aff7Sartem 	run_data *rd = (run_data *)data;
33418c2aff7Sartem 
33518c2aff7Sartem 	kill(rd->pid, SIGTERM);
33618c2aff7Sartem 	printf("Sent kill to %d\n", rd->pid);
33718c2aff7Sartem 	if (rd->timeout != 0) {
33818c2aff7Sartem 		/* Remove the timeout watch */
33918c2aff7Sartem 		g_source_remove(rd->timeout);
34018c2aff7Sartem 		rd->timeout = 0;
34118c2aff7Sartem 	}
34218c2aff7Sartem 
34318c2aff7Sartem 	/* So the exit watch will know it's killed  in case it runs */
34418c2aff7Sartem 	rd->sent_kill = TRUE;
34518c2aff7Sartem 
34618c2aff7Sartem 	if (rd->msg != NULL)
34718c2aff7Sartem 		send_reply(rd->con, rd->msg, HALD_RUN_KILLED, 0, NULL);
34818c2aff7Sartem }
34918c2aff7Sartem 
35018c2aff7Sartem static void
do_kill_udi(gchar * udi)35118c2aff7Sartem do_kill_udi(gchar *udi)
35218c2aff7Sartem {
35318c2aff7Sartem 	GList *list;
35418c2aff7Sartem 	list = (GList *)g_hash_table_lookup(udi_hash, udi);
35518c2aff7Sartem 	g_list_foreach(list, kill_rd, NULL);
35618c2aff7Sartem 	g_list_free(list);
35718c2aff7Sartem }
35818c2aff7Sartem 
35918c2aff7Sartem /* Kill all running request for a udi */
360*55fea89dSDan Cross void
run_kill_udi(gchar * udi)36118c2aff7Sartem run_kill_udi(gchar *udi)
36218c2aff7Sartem {
36318c2aff7Sartem 	do_kill_udi(udi);
36418c2aff7Sartem 	g_hash_table_remove(udi_hash, udi);
36518c2aff7Sartem }
36618c2aff7Sartem 
367*55fea89dSDan Cross static gboolean
hash_kill_udi(gpointer key,gpointer value,gpointer user_data)36818c2aff7Sartem hash_kill_udi(gpointer key, gpointer value, gpointer user_data) {
36918c2aff7Sartem 	do_kill_udi(key);
37018c2aff7Sartem 	return TRUE;
37118c2aff7Sartem }
37218c2aff7Sartem 
37318c2aff7Sartem /* Kill all running request*/
374*55fea89dSDan Cross void
run_kill_all()37518c2aff7Sartem run_kill_all()
37618c2aff7Sartem {
37718c2aff7Sartem 	g_hash_table_foreach_remove(udi_hash, hash_kill_udi, NULL);
3783ab06c27SMilan Jurik 	g_list_foreach(singletons, kill_rd, NULL);
37918c2aff7Sartem }
38018c2aff7Sartem 
38118c2aff7Sartem void
run_init()38218c2aff7Sartem run_init()
38318c2aff7Sartem {
38418c2aff7Sartem 	udi_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
38518c2aff7Sartem }
386