diff options
author | David Zeuthen <david@fubar.dk> | 2006-01-21 02:45:27 +0000 |
---|---|---|
committer | David Zeuthen <david@fubar.dk> | 2006-01-21 02:45:27 +0000 |
commit | 40ebd03de7f2de5f1fb9f702d9d026a3ff50a448 (patch) | |
tree | aa496cee05edd229ab1bccf660359e95432e37a1 /hald-runner/runner.c | |
parent | 46223569abd6066675bf98ef4b7e25789feb22ba (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.c | 335 |
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); +} |