summaryrefslogtreecommitdiff
path: root/hald-runner/runner.c
diff options
context:
space:
mode:
authorDavid Zeuthen <david@fubar.dk>2006-01-21 02:45:27 +0000
committerDavid Zeuthen <david@fubar.dk>2006-01-21 02:45:27 +0000
commit40ebd03de7f2de5f1fb9f702d9d026a3ff50a448 (patch)
treeaa496cee05edd229ab1bccf660359e95432e37a1 /hald-runner/runner.c
parent46223569abd6066675bf98ef4b7e25789feb22ba (diff)
Great patch from Sjoerd Simons <sjoerd@luon.net>: As most people probably
know by now, various people don't really like that hal running as root. We'd much rather see only a small process running as root and the main hal process running unpriviledged. Which is exactly what this patch does :) How does it work? Just before drops it's root privs. a small program is startup which will remain running as root and does the real execution of the addons/probes/callouts on hals behalf. Communication between hald and hald-runner is done via a p2p dbus connection. Resulting in a process tree like this: hal /usr/sbin/hald root \_ /usr/lib/hal/hald-runner root \_ /usr/lib/hal/hald-addon-acpi root \_ /usr/lib/hal/hald-addon-storage root \_ /usr/lib/hal/hald-addon-storage The patch consists out of two parts. First the implementation of hald-runner, which is about 700 lines of code. And then a part transforming the hald code from the current spawning code in utils to an interface that can talk to the runner. Add Sjoerd Simons <sjoerd@luon.net>. Revise my own email address. Add hald-runner Add hald-runner add hald_runner.[ch] New and changed files with slight changes. See the descriptive text above and discussion at http://lists.freedesktop.org/archives/hal/2006-January/004327.html for details. Changed function hald_runner_start_runner() in hald/hald_runner.c to print out runner path and improve error handling when runner is not found. Also removed dbus_server_unref (server) in handle_connection() in same file. Remove --retain-privileges as this is no longer needed Remove --retain-privileges and use --with-runner pointing to ../hald-runner/hald-runner
Diffstat (limited to 'hald-runner/runner.c')
-rw-r--r--hald-runner/runner.c335
1 files changed, 335 insertions, 0 deletions
diff --git a/hald-runner/runner.c b/hald-runner/runner.c
new file mode 100644
index 00000000..46c5d0bd
--- /dev/null
+++ b/hald-runner/runner.c
@@ -0,0 +1,335 @@
+/***************************************************************************
+ * CVSID: $Id$
+ *
+ * runner.c - Process running code
+ *
+ * Copyright (C) 2006 Sjoerd Simons, <sjoerd@luon.net>
+ *
+ * Licensed under the Academic Free License version 2.1
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ **************************************************************************/
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <string.h>
+
+#define DBUS_API_SUBJECT_TO_CHANGE
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <glib.h>
+#include "utils.h"
+#include "runner.h"
+
+/* Successful run of the program */
+#define HALD_RUN_SUCCESS 0x0
+/* Process was killed because of running too long */
+#define HALD_RUN_TIMEOUT 0x1
+/* Failed to start for some reason */
+#define HALD_RUN_FAILED 0x2
+/* Killed on purpose, e.g. hal_util_kill_device_helpers */
+#define HALD_RUN_KILLED 0x4
+
+GHashTable *udi_hash = NULL;
+
+typedef struct {
+ run_request *r;
+ DBusMessage *msg;
+ DBusConnection *con;
+ GPid pid;
+ gint stderr_v;
+ guint watch;
+ guint timeout;
+ gboolean sent_kill;
+} run_data;
+
+static void
+del_run_data(run_data *rd) {
+ if (rd == NULL)
+ return;
+
+ del_run_request(rd->r);
+ if (rd->msg) {
+ dbus_message_unref(rd->msg);
+ }
+ g_spawn_close_pid(rd->pid);
+
+ if (rd->stderr_v >= 0) {
+ close(rd->stderr_v);
+ }
+
+ if (rd->timeout != 0) {
+ g_source_remove(rd->timeout);
+ }
+ g_free(rd);
+}
+
+run_request *
+new_run_request(void) {
+ run_request *result;
+ result = g_new0(run_request, 1);
+ g_assert(result != NULL);
+ return result;
+}
+
+void
+del_run_request(run_request *r) {
+ if (r == NULL)
+ return;
+ g_free(r->udi);
+ free_string_array(r->environment);
+ free_string_array(r->argv);
+ g_free(r->input);
+ g_free(r);
+}
+
+static void
+send_reply(DBusConnection *con, DBusMessage *msg,
+ guint32 exit_type, gint32 return_code, gchar **error) {
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ int i;
+
+ if (con == NULL || msg == NULL)
+ return;
+
+ reply = dbus_message_new_method_return(msg);
+ g_assert(reply != NULL);
+
+ dbus_message_iter_init_append(reply, &iter);
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &exit_type);
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &return_code);
+ if (error != NULL) for (i = 0; error[i] != NULL; i++) {
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &error[i]);
+ }
+
+ dbus_connection_send(con, reply, NULL);
+ dbus_message_unref(reply);
+}
+
+static void
+remove_from_hash_table(run_data *rd) {
+ GList *list;
+ /* Remove to the hashtable */
+ list = (GList *)g_hash_table_lookup(udi_hash, rd->r->udi);
+ list = g_list_remove(list, rd);
+ /* The hash table will take care to not leak the dupped string */
+ g_hash_table_insert(udi_hash, g_strdup(rd->r->udi), list);
+}
+
+static void
+run_exited(GPid pid, gint status, gpointer data) {
+ run_data *rd = (run_data *)data;
+ char **error = NULL;
+
+ printf("%s exited\n", rd->r->argv[0]);
+ rd->watch = 0;
+ if (rd->sent_kill == TRUE) {
+ /* We send it a kill, so ignore */
+ del_run_data(rd);
+ return;
+ }
+ /* Check if it was a normal exit */
+ if (!WIFEXITED(status)) {
+ /* No not normal termination ? crash ? */
+ send_reply(rd->con, rd->msg, HALD_RUN_FAILED, 0, NULL);
+ remove_from_hash_table(rd);
+ del_run_data(rd);
+ return;
+ }
+ /* normal exit */
+ if (rd->stderr_v >= 0) {
+ /* Need to read stderr */
+ error = get_string_array_from_fd(rd->stderr_v);
+ rd->stderr_v = -1;
+ }
+ if (rd->msg != NULL) {
+ send_reply(rd->con, rd->msg, HALD_RUN_SUCCESS, WEXITSTATUS(status), error);
+ }
+ free_string_array(error);
+
+ remove_from_hash_table(rd);
+ del_run_data(rd);
+}
+
+static gboolean
+run_timedout(gpointer data) {
+ run_data *rd = (run_data *)data;
+ /* Time is up, kill the process, send reply that it was killed!
+ * Don't wait for exit, because it could hang in state D
+ */
+ kill(rd->pid, SIGTERM);
+ /* Ensure the timeout is not removed in the delete */
+ rd->timeout = 0;
+ /* So the exit watch will know it's killed in case it runs*/
+ rd->sent_kill = TRUE;
+
+ send_reply(rd->con, rd->msg, HALD_RUN_TIMEOUT, 0, NULL);
+ remove_from_hash_table(rd);
+ return FALSE;
+}
+
+static
+gboolean find_program(char **argv) {
+ /* Search for the program in the dirs where it's allowed to be */
+ char *dirs[] = { PACKAGE_LIBEXEC_DIR, PACKAGE_SCRIPT_DIR, NULL };
+ char *program;
+ char *path = NULL;
+ struct stat buf;
+ int i;
+
+ if (argv[0] == NULL)
+ return FALSE;
+ program = g_path_get_basename(argv[0]);
+ for (i = 0; dirs[i] != NULL; i++) {
+ path = g_build_filename(dirs[i], program, NULL);
+ if (stat(path, &buf) == 0) {
+ break;
+ }
+ g_free(path);
+ path = NULL;
+ }
+ g_free(program);
+ if (path == NULL)
+ return FALSE;
+ else {
+ /* Replace program in argv[0] with the full path */
+ g_free(argv[0]);
+ argv[0] = path;
+ }
+ return TRUE;
+}
+
+
+/* Run the given request and reply it's result on msg */
+gboolean
+run_request_run(run_request *r, DBusConnection *con, DBusMessage *msg) {
+ GPid pid;
+ GError *error = NULL;
+ gint *stdin_p = NULL;
+ gint *stderr_p = NULL;
+ gint stdin_v;
+ gint stderr_v = -1;
+ run_data *rd = NULL;
+ GList *list;
+
+ printf("Run started %s (%d) (%d) \n!", r->argv[0], r->timeout,
+ r->error_on_stderr);
+ if (r->input != NULL) {
+ stdin_p = &stdin_v;
+ }
+ if (r->error_on_stderr) {
+ stderr_p = &stderr_v;
+ }
+
+ if (!find_program(r->argv) ||
+ !g_spawn_async_with_pipes("/", r->argv, r->environment,
+ G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, NULL, &pid,
+ stdin_p, NULL, stderr_p, &error)) {
+ del_run_request(r);
+ if (con && msg) {
+ send_reply(con, msg, HALD_RUN_FAILED, 0, NULL);
+ }
+ return FALSE;
+ }
+
+ if (r->input) {
+ write(stdin_v, r->input, strlen(r->input));
+ close(stdin_v);
+ }
+
+ rd = g_new0(run_data,1);
+ g_assert(rd != NULL);
+ rd->r = r;
+ rd->msg = msg;
+ if (msg != NULL) {
+ dbus_message_ref(msg);
+ }
+ rd->con = con;
+ rd->pid = pid;
+ rd->stderr_v = stderr_v;
+ rd->sent_kill = FALSE;
+
+ /* Add watch for exit of the program */
+ rd->watch = g_child_watch_add(pid, run_exited, rd);
+ /* Add timeout if needed */
+ if (r->timeout > 0) {
+ rd->timeout = g_timeout_add(r->timeout, run_timedout, rd);
+ } else {
+ rd->timeout = 0;
+ }
+ /* Add to the hashtable */
+ list = (GList *)g_hash_table_lookup(udi_hash, r->udi);
+ list = g_list_prepend(list, rd);
+ /* The hash table will take care to not leak the dupped string */
+ g_hash_table_insert(udi_hash, g_strdup(r->udi), list);
+ return TRUE;
+}
+
+static void
+kill_rd(gpointer data, gpointer user_data) {
+ run_data *rd = (run_data *)data;
+
+ kill(rd->pid, SIGTERM);
+ printf("Sent kill to %d\n", rd->pid);
+ if (rd->timeout != 0) {
+ /* Remove the timeout watch */
+ g_source_remove(rd->timeout);
+ rd->timeout = 0;
+ }
+ /* So the exit watch will know it's killed in case it runs*/
+ rd->sent_kill = TRUE;
+
+ if (rd->msg != NULL) {
+ send_reply(rd->con, rd->msg, HALD_RUN_KILLED, 0, NULL);
+ }
+}
+
+static void
+do_kill_udi(gchar *udi) {
+ GList *list;
+ list = (GList *)g_hash_table_lookup(udi_hash, udi);
+ g_list_foreach(list, kill_rd, NULL);
+ g_list_free(list);
+}
+
+/* Kill all running request for a udi */
+void
+run_kill_udi(gchar *udi) {
+ do_kill_udi(udi);
+ g_hash_table_remove(udi_hash, udi);
+}
+
+static void
+hash_kill_udi(gpointer key, gpointer value, gpointer user_data) {
+ do_kill_udi(key);
+}
+
+/* Kill all running request*/
+void
+run_kill_all() {
+ g_hash_table_foreach_remove(udi_hash, hash_kill_udi, NULL);
+}
+
+void
+run_init() {
+ udi_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+}