12912Sartem /***************************************************************************
22912Sartem * CVSID: $Id$
32912Sartem *
42912Sartem * runner.c - Process running code
52912Sartem *
62912Sartem * Copyright (C) 2006 Sjoerd Simons, <sjoerd@luon.net>
72912Sartem *
82912Sartem * Licensed under the Academic Free License version 2.1
92912Sartem *
102912Sartem * This program is free software; you can redistribute it and/or modify
112912Sartem * it under the terms of the GNU General Public License as published by
122912Sartem * the Free Software Foundation; either version 2 of the License, or
132912Sartem * (at your option) any later version.
142912Sartem *
152912Sartem * This program is distributed in the hope that it will be useful,
162912Sartem * but WITHOUT ANY WARRANTY; without even the implied warranty of
172912Sartem * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
182912Sartem * GNU General Public License for more details.
192912Sartem *
202912Sartem * You should have received a copy of the GNU General Public License
212912Sartem * along with this program; if not, write to the Free Software
222912Sartem * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
232912Sartem *
242912Sartem **************************************************************************/
252912Sartem #include <stdio.h>
262912Sartem #include <unistd.h>
272912Sartem #include <stdlib.h>
282912Sartem #include <sys/types.h>
292912Sartem #include <sys/stat.h>
302912Sartem #include <sys/wait.h>
312912Sartem #include <signal.h>
322912Sartem #include <string.h>
332912Sartem
342912Sartem #define DBUS_API_SUBJECT_TO_CHANGE
352912Sartem #include <dbus/dbus-glib-lowlevel.h>
362912Sartem
372912Sartem #include <glib.h>
382912Sartem #include "utils.h"
392912Sartem #include "runner.h"
402912Sartem
412912Sartem /* Successful run of the program */
422912Sartem #define HALD_RUN_SUCCESS 0x0
432912Sartem /* Process was killed because of running too long */
442912Sartem #define HALD_RUN_TIMEOUT 0x1
452912Sartem /* Failed to start for some reason */
462912Sartem #define HALD_RUN_FAILED 0x2
472912Sartem /* Killed on purpose, e.g. hal_util_kill_device_helpers */
482912Sartem #define HALD_RUN_KILLED 0x4
492912Sartem
502912Sartem GHashTable *udi_hash = NULL;
512912Sartem
522912Sartem typedef struct {
532912Sartem run_request *r;
542912Sartem DBusMessage *msg;
552912Sartem DBusConnection *con;
562912Sartem GPid pid;
572912Sartem gint stderr_v;
582912Sartem guint watch;
592912Sartem guint timeout;
602912Sartem gboolean sent_kill;
612912Sartem gboolean emit_pid_exited;
622912Sartem } run_data;
632912Sartem
642912Sartem static void
del_run_data(run_data * rd)652912Sartem del_run_data(run_data *rd)
662912Sartem {
672912Sartem if (rd == NULL)
682912Sartem return;
692912Sartem
702912Sartem del_run_request(rd->r);
712912Sartem if (rd->msg)
722912Sartem dbus_message_unref(rd->msg);
732912Sartem
742912Sartem g_spawn_close_pid(rd->pid);
752912Sartem
762912Sartem if (rd->stderr_v >= 0)
772912Sartem close(rd->stderr_v);
782912Sartem
792912Sartem if (rd->timeout != 0)
802912Sartem g_source_remove(rd->timeout);
812912Sartem
822912Sartem g_free(rd);
832912Sartem }
842912Sartem
852912Sartem run_request *
new_run_request(void)862912Sartem new_run_request(void)
872912Sartem {
882912Sartem run_request *result;
892912Sartem result = g_new0(run_request, 1);
902912Sartem g_assert(result != NULL);
912912Sartem return result;
922912Sartem }
932912Sartem
942912Sartem void
del_run_request(run_request * r)952912Sartem del_run_request(run_request *r)
962912Sartem {
972912Sartem if (r == NULL)
982912Sartem return;
992912Sartem g_free(r->udi);
1002912Sartem free_string_array(r->environment);
1012912Sartem free_string_array(r->argv);
1022912Sartem g_free(r->input);
1032912Sartem g_free(r);
1042912Sartem }
1052912Sartem
1062912Sartem static void
send_reply(DBusConnection * con,DBusMessage * msg,guint32 exit_type,gint32 return_code,gchar ** error)1072912Sartem send_reply(DBusConnection *con, DBusMessage *msg, guint32 exit_type, gint32 return_code, gchar **error)
1082912Sartem {
1092912Sartem DBusMessage *reply;
1102912Sartem DBusMessageIter iter;
1112912Sartem int i;
1122912Sartem
1132912Sartem if (con == NULL || msg == NULL)
1142912Sartem return;
1152912Sartem
1162912Sartem reply = dbus_message_new_method_return(msg);
1172912Sartem g_assert(reply != NULL);
1182912Sartem
1192912Sartem dbus_message_iter_init_append(reply, &iter);
1202912Sartem dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &exit_type);
1212912Sartem dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &return_code);
1222912Sartem if (error != NULL) for (i = 0; error[i] != NULL; i++) {
1232912Sartem dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &error[i]);
1242912Sartem }
1252912Sartem
1262912Sartem dbus_connection_send(con, reply, NULL);
1272912Sartem dbus_message_unref(reply);
1282912Sartem }
1292912Sartem
1302912Sartem static void
remove_from_hash_table(run_data * rd)1312912Sartem remove_from_hash_table(run_data *rd)
1322912Sartem {
1332912Sartem GList *list;
1342912Sartem
1352912Sartem /* Remove to the hashtable */
1362912Sartem list = (GList *)g_hash_table_lookup(udi_hash, rd->r->udi);
1372912Sartem list = g_list_remove(list, rd);
1382912Sartem /* The hash table will take care to not leak the dupped string */
1392912Sartem g_hash_table_insert(udi_hash, g_strdup(rd->r->udi), list);
1402912Sartem }
1412912Sartem
1422912Sartem static void
run_exited(GPid pid,gint status,gpointer data)1432912Sartem run_exited(GPid pid, gint status, gpointer data)
1442912Sartem {
1452912Sartem run_data *rd = (run_data *)data;
1462912Sartem char **error = NULL;
1472912Sartem
1482912Sartem printf("%s exited\n", rd->r->argv[0]);
1492912Sartem rd->watch = 0;
1502912Sartem if (rd->sent_kill == TRUE) {
1512912Sartem /* We send it a kill, so ignore */
1522912Sartem del_run_data(rd);
1532912Sartem return;
1542912Sartem }
1552912Sartem /* Check if it was a normal exit */
1562912Sartem if (!WIFEXITED(status)) {
1572912Sartem /* No not normal termination ? crash ? */
1582912Sartem send_reply(rd->con, rd->msg, HALD_RUN_FAILED, 0, NULL);
1592912Sartem goto out;
1602912Sartem }
1612912Sartem /* normal exit */
1622912Sartem if (rd->stderr_v >= 0) {
1632912Sartem /* Need to read stderr */
1642912Sartem error = get_string_array_from_fd(rd->stderr_v);
1652912Sartem close(rd->stderr_v);
1662912Sartem rd->stderr_v = -1;
1672912Sartem }
1682912Sartem if (rd->msg != NULL)
1692912Sartem send_reply(rd->con, rd->msg, HALD_RUN_SUCCESS, WEXITSTATUS(status), error);
1702912Sartem free_string_array(error);
1712912Sartem
1722912Sartem out:
1732912Sartem remove_from_hash_table(rd);
1742912Sartem
1752912Sartem /* emit a signal that this PID exited */
1762912Sartem if(rd->con != NULL && rd->emit_pid_exited) {
1772912Sartem DBusMessage *signal;
1783168Sartem dbus_int64_t pid64;
1792912Sartem signal = dbus_message_new_signal ("/org/freedesktop/HalRunner",
1802912Sartem "org.freedesktop.HalRunner",
1812912Sartem "StartedProcessExited");
1823168Sartem pid64 = rd->pid;
1832912Sartem dbus_message_append_args (signal,
1843168Sartem DBUS_TYPE_INT64, &pid64,
1852912Sartem DBUS_TYPE_INVALID);
1862912Sartem dbus_connection_send(rd->con, signal, NULL);
1872912Sartem }
1882912Sartem
1892912Sartem del_run_data(rd);
1902912Sartem }
1912912Sartem
1922912Sartem static gboolean
run_timedout(gpointer data)1932912Sartem run_timedout(gpointer data) {
1942912Sartem run_data *rd = (run_data *)data;
1952912Sartem /* Time is up, kill the process, send reply that it was killed!
1962912Sartem * Don't wait for exit, because it could hang in state D
1972912Sartem */
1982912Sartem kill(rd->pid, SIGTERM);
1992912Sartem /* Ensure the timeout is not removed in the delete */
2002912Sartem rd->timeout = 0;
2012912Sartem /* So the exit watch will know it's killed in case it runs*/
2022912Sartem rd->sent_kill = TRUE;
2032912Sartem
2042912Sartem send_reply(rd->con, rd->msg, HALD_RUN_TIMEOUT, 0, NULL);
2052912Sartem remove_from_hash_table(rd);
2062912Sartem return FALSE;
2072912Sartem }
2082912Sartem
2092912Sartem static gboolean
find_program(char ** argv)2102912Sartem find_program(char **argv)
2112912Sartem {
2122912Sartem /* Search for the program in the dirs where it's allowed to be */
2132912Sartem char *program;
2142912Sartem char *path = NULL;
2152912Sartem
2162912Sartem if (argv[0] == NULL)
2172912Sartem return FALSE;
2182912Sartem
2192912Sartem program = g_path_get_basename(argv[0]);
2202912Sartem
2212912Sartem /* first search $PATH to make e.g. run-hald.sh work */
2222912Sartem path = g_find_program_in_path (program);
2232912Sartem g_free(program);
2242912Sartem if (path == NULL)
2252912Sartem return FALSE;
2262912Sartem else {
2272912Sartem /* Replace program in argv[0] with the full path */
2282912Sartem g_free(argv[0]);
2292912Sartem argv[0] = path;
2302912Sartem }
2312912Sartem return TRUE;
2322912Sartem }
2332912Sartem
2342912Sartem /* Run the given request and reply it's result on msg */
2352912Sartem gboolean
run_request_run(run_request * r,DBusConnection * con,DBusMessage * msg,GPid * out_pid)2362912Sartem run_request_run (run_request *r, DBusConnection *con, DBusMessage *msg, GPid *out_pid)
2372912Sartem {
2382912Sartem GPid pid;
2392912Sartem GError *error = NULL;
2402912Sartem gint *stdin_p = NULL;
2412912Sartem gint *stderr_p = NULL;
2422912Sartem gint stdin_v;
2432912Sartem gint stderr_v = -1;
2442912Sartem run_data *rd = NULL;
2452912Sartem gboolean program_exists = FALSE;
2462912Sartem char *program_dir = NULL;
2472912Sartem GList *list;
2482912Sartem
2492912Sartem printf("Run started %s (%d) (%d) \n!", r->argv[0], r->timeout,
2502912Sartem r->error_on_stderr);
2512912Sartem if (r->input != NULL) {
2522912Sartem stdin_p = &stdin_v;
2532912Sartem }
2542912Sartem if (r->error_on_stderr) {
2552912Sartem stderr_p = &stderr_v;
2562912Sartem }
2572912Sartem
2582912Sartem program_exists = find_program(r->argv);
2592912Sartem
2603020Sartem if (program_exists) {
2612912Sartem program_dir = g_path_get_dirname (r->argv[0]);
2623020Sartem printf(" full path is '%s', program_dir is '%s'\n", r->argv[0], program_dir);
2633020Sartem }
2642912Sartem
2652912Sartem if (!program_exists ||
2662912Sartem !g_spawn_async_with_pipes(program_dir, r->argv, r->environment,
2672912Sartem G_SPAWN_DO_NOT_REAP_CHILD,
2682912Sartem NULL, NULL, &pid,
2692912Sartem stdin_p, NULL, stderr_p, &error)) {
2702912Sartem g_free (program_dir);
2712912Sartem del_run_request(r);
2722912Sartem if (con && msg)
2732912Sartem send_reply(con, msg, HALD_RUN_FAILED, 0, NULL);
2742912Sartem return FALSE;
2752912Sartem }
2762912Sartem g_free (program_dir);
2772912Sartem
2782912Sartem if (r->input) {
279*3502Sartem if (write(stdin_v, r->input, strlen(r->input)) != (ssize_t) strlen(r->input))
2802912Sartem printf("Warning: Error while wite r->input (%s) to stdin_v.\n", r->input);
2812912Sartem close(stdin_v);
2822912Sartem }
2832912Sartem
2842912Sartem rd = g_new0(run_data,1);
2852912Sartem g_assert(rd != NULL);
2862912Sartem rd->r = r;
2872912Sartem rd->msg = msg;
2882912Sartem if (msg != NULL)
2892912Sartem dbus_message_ref(msg);
2902912Sartem
2912912Sartem rd->con = con;
2922912Sartem rd->pid = pid;
2932912Sartem rd->stderr_v = stderr_v;
2942912Sartem rd->sent_kill = FALSE;
2952912Sartem
2962912Sartem /* Add watch for exit of the program */
2972912Sartem rd->watch = g_child_watch_add(pid, run_exited, rd);
2982912Sartem
2992912Sartem /* Add timeout if needed */
3002912Sartem if (r->timeout > 0)
3012912Sartem rd->timeout = g_timeout_add(r->timeout, run_timedout, rd);
3022912Sartem else
3032912Sartem rd->timeout = 0;
3042912Sartem
3052912Sartem /* Add to the hashtable */
3062912Sartem list = (GList *)g_hash_table_lookup(udi_hash, r->udi);
3072912Sartem list = g_list_prepend(list, rd);
3082912Sartem
3092912Sartem /* The hash table will take care to not leak the dupped string */
3102912Sartem g_hash_table_insert(udi_hash, g_strdup(r->udi), list);
3112912Sartem
3122912Sartem /* send back PID if requested.. and only emit StartedProcessExited in this case */
3132912Sartem if (out_pid != NULL) {
3142912Sartem *out_pid = pid;
3152912Sartem rd->emit_pid_exited = TRUE;
3162912Sartem }
3172912Sartem return TRUE;
3182912Sartem }
3192912Sartem
3202912Sartem static void
kill_rd(gpointer data,gpointer user_data)3212912Sartem kill_rd(gpointer data, gpointer user_data)
3222912Sartem {
3232912Sartem run_data *rd = (run_data *)data;
3242912Sartem
3252912Sartem kill(rd->pid, SIGTERM);
3262912Sartem printf("Sent kill to %d\n", rd->pid);
3272912Sartem if (rd->timeout != 0) {
3282912Sartem /* Remove the timeout watch */
3292912Sartem g_source_remove(rd->timeout);
3302912Sartem rd->timeout = 0;
3312912Sartem }
3322912Sartem
3332912Sartem /* So the exit watch will know it's killed in case it runs */
3342912Sartem rd->sent_kill = TRUE;
3352912Sartem
3362912Sartem if (rd->msg != NULL)
3372912Sartem send_reply(rd->con, rd->msg, HALD_RUN_KILLED, 0, NULL);
3382912Sartem }
3392912Sartem
3402912Sartem static void
do_kill_udi(gchar * udi)3412912Sartem do_kill_udi(gchar *udi)
3422912Sartem {
3432912Sartem GList *list;
3442912Sartem list = (GList *)g_hash_table_lookup(udi_hash, udi);
3452912Sartem g_list_foreach(list, kill_rd, NULL);
3462912Sartem g_list_free(list);
3472912Sartem }
3482912Sartem
3492912Sartem /* Kill all running request for a udi */
3502912Sartem void
run_kill_udi(gchar * udi)3512912Sartem run_kill_udi(gchar *udi)
3522912Sartem {
3532912Sartem do_kill_udi(udi);
3542912Sartem g_hash_table_remove(udi_hash, udi);
3552912Sartem }
3562912Sartem
3572912Sartem static gboolean
hash_kill_udi(gpointer key,gpointer value,gpointer user_data)3582912Sartem hash_kill_udi(gpointer key, gpointer value, gpointer user_data) {
3592912Sartem do_kill_udi(key);
3602912Sartem return TRUE;
3612912Sartem }
3622912Sartem
3632912Sartem /* Kill all running request*/
3642912Sartem void
run_kill_all()3652912Sartem run_kill_all()
3662912Sartem {
3672912Sartem g_hash_table_foreach_remove(udi_hash, hash_kill_udi, NULL);
3682912Sartem }
3692912Sartem
3702912Sartem void
run_init()3712912Sartem run_init()
3722912Sartem {
3732912Sartem udi_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
3742912Sartem }
375