summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Zeuthen <davidz@redhat.com>2012-05-21 14:38:49 -0400
committerDavid Zeuthen <davidz@redhat.com>2012-05-21 14:38:49 -0400
commit28ce5634df0a109d15f2b307e56fbfac92d7c876 (patch)
tree5c9c78d2d03011181d26bdec0bf7dcae8a00d296
parente7f01d6a37b0d922e9fe005e38fe9a958eb18e7f (diff)
downloadpolkit-28ce5634df0a109d15f2b307e56fbfac92d7c876.tar.gz
Add test-cases and 10 second timeout for polkit.spawn()
Signed-off-by: David Zeuthen <davidz@redhat.com>
-rw-r--r--src/polkitbackend/polkitbackendjsauthority.c446
-rw-r--r--test/data/etc/polkit-1/rules.d/10-testing.rules63
-rw-r--r--test/polkitbackend/test-polkitbackendjsauthority.c40
3 files changed, 538 insertions, 11 deletions
diff --git a/src/polkitbackend/polkitbackendjsauthority.c b/src/polkitbackend/polkitbackendjsauthority.c
index 7798d45..a7bf50b 100644
--- a/src/polkitbackend/polkitbackendjsauthority.c
+++ b/src/polkitbackend/polkitbackendjsauthority.c
@@ -68,6 +68,18 @@ struct _PolkitBackendJsAuthorityPrivate
GList *scripts;
};
+static void utils_spawn (const gchar *const *argv,
+ guint timeout_seconds,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean utils_spawn_finish (GAsyncResult *res,
+ gint *out_exit_status,
+ gchar **out_standard_output,
+ gchar **out_standard_error,
+ GError **error);
+
static void on_dir_monitor_changed (GFileMonitor *monitor,
GFile *file,
GFile *other_file,
@@ -1130,6 +1142,22 @@ get_signal_name (gint signal_number)
return "UNKNOWN_SIGNAL";
}
+typedef struct
+{
+ GMainLoop *loop;
+ GAsyncResult *res;
+} SpawnData;
+
+static void
+spawn_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SpawnData *data = user_data;
+ data->res = g_object_ref (res);
+ g_main_loop_quit (data->loop);
+}
+
static JSBool
js_polkit_spawn (JSContext *cx,
uintN js_argc,
@@ -1145,6 +1173,9 @@ js_polkit_spawn (JSContext *cx,
JSString *ret_jsstr;
jsuint array_len;
gchar **argv = NULL;
+ GMainContext *context = NULL;
+ GMainLoop *loop = NULL;
+ SpawnData data = {0};
guint n;
if (!JS_ConvertArguments (cx, js_argc, JS_ARGV (cx, vp), "o", &array_object))
@@ -1172,19 +1203,30 @@ js_polkit_spawn (JSContext *cx,
JS_free (cx, s);
}
- /* TODO: set a timeout */
- if (!g_spawn_sync (NULL, /* working dir */
- argv,
- NULL, /* envp */
- G_SPAWN_SEARCH_PATH,
- NULL, NULL, /* child_setup, user_data */
- &standard_output,
- &standard_error,
- &exit_status,
- &error))
+ context = g_main_context_new ();
+ loop = g_main_loop_new (context, FALSE);
+
+ g_main_context_push_thread_default (context);
+
+ data.loop = loop;
+ utils_spawn ((const gchar *const *) argv,
+ 10, /* timeout_seconds */
+ NULL, /* cancellable */
+ spawn_cb,
+ &data);
+
+ g_main_loop_run (loop);
+
+ g_main_context_pop_thread_default (context);
+
+ if (!utils_spawn_finish (data.res,
+ &exit_status,
+ &standard_output,
+ &standard_error,
+ &error))
{
JS_ReportError (cx,
- "Failed to spawn helper: %s (%s, %d)",
+ "Error spawning helper: %s (%s, %d)",
error->message, g_quark_to_string (error->domain), error->code);
g_clear_error (&error);
goto out;
@@ -1223,6 +1265,11 @@ js_polkit_spawn (JSContext *cx,
g_strfreev (argv);
g_free (standard_output);
g_free (standard_error);
+ g_clear_object (&data.res);
+ if (loop != NULL)
+ g_main_loop_unref (loop);
+ if (context != NULL)
+ g_main_context_unref (context);
return ret;
}
@@ -1266,3 +1313,380 @@ js_polkit_user_is_in_netgroup (JSContext *cx,
return ret;
}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GSimpleAsyncResult *simple; /* borrowed reference */
+ GMainContext *main_context; /* may be NULL */
+
+ GCancellable *cancellable; /* may be NULL */
+ gulong cancellable_handler_id;
+
+ GPid child_pid;
+ gint child_stdout_fd;
+ gint child_stderr_fd;
+
+ GIOChannel *child_stdout_channel;
+ GIOChannel *child_stderr_channel;
+
+ GSource *child_watch_source;
+ GSource *child_stdout_source;
+ GSource *child_stderr_source;
+
+ guint timeout_seconds;
+ gboolean timed_out;
+ GSource *timeout_source;
+
+ GString *child_stdout;
+ GString *child_stderr;
+
+ gint exit_status;
+} UtilsSpawnData;
+
+static void
+utils_child_watch_from_release_cb (GPid pid,
+ gint status,
+ gpointer user_data)
+{
+}
+
+static void
+utils_spawn_data_free (UtilsSpawnData *data)
+{
+ if (data->timeout_source != NULL)
+ {
+ g_source_destroy (data->timeout_source);
+ data->timeout_source = NULL;
+ }
+
+ /* Nuke the child, if necessary */
+ if (data->child_watch_source != NULL)
+ {
+ g_source_destroy (data->child_watch_source);
+ data->child_watch_source = NULL;
+ }
+
+ if (data->child_pid != 0)
+ {
+ GSource *source;
+ kill (data->child_pid, SIGTERM);
+ /* OK, we need to reap for the child ourselves - we don't want
+ * to use waitpid() because that might block the calling
+ * thread (the child might handle SIGTERM and use several
+ * seconds for cleanup/rollback).
+ *
+ * So we use GChildWatch instead.
+ *
+ * Avoid taking a references to ourselves. but note that we need
+ * to pass the GSource so we can nuke it once handled.
+ */
+ source = g_child_watch_source_new (data->child_pid);
+ g_source_set_callback (source,
+ (GSourceFunc) utils_child_watch_from_release_cb,
+ source,
+ (GDestroyNotify) g_source_destroy);
+ g_source_attach (source, data->main_context);
+ g_source_unref (source);
+ data->child_pid = 0;
+ }
+
+ if (data->child_stdout != NULL)
+ {
+ g_string_free (data->child_stdout, TRUE);
+ data->child_stdout = NULL;
+ }
+
+ if (data->child_stderr != NULL)
+ {
+ g_string_free (data->child_stderr, TRUE);
+ data->child_stderr = NULL;
+ }
+
+ if (data->child_stdout_channel != NULL)
+ {
+ g_io_channel_unref (data->child_stdout_channel);
+ data->child_stdout_channel = NULL;
+ }
+ if (data->child_stderr_channel != NULL)
+ {
+ g_io_channel_unref (data->child_stderr_channel);
+ data->child_stderr_channel = NULL;
+ }
+
+ if (data->child_stdout_source != NULL)
+ {
+ g_source_destroy (data->child_stdout_source);
+ data->child_stdout_source = NULL;
+ }
+ if (data->child_stderr_source != NULL)
+ {
+ g_source_destroy (data->child_stderr_source);
+ data->child_stderr_source = NULL;
+ }
+
+ if (data->child_stdout_fd != -1)
+ {
+ g_warn_if_fail (close (data->child_stdout_fd) == 0);
+ data->child_stdout_fd = -1;
+ }
+ if (data->child_stderr_fd != -1)
+ {
+ g_warn_if_fail (close (data->child_stderr_fd) == 0);
+ data->child_stderr_fd = -1;
+ }
+
+ if (data->cancellable_handler_id > 0)
+ {
+ g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id);
+ data->cancellable_handler_id = 0;
+ }
+
+ if (data->main_context != NULL)
+ g_main_context_unref (data->main_context);
+
+ if (data->cancellable != NULL)
+ g_object_unref (data->cancellable);
+
+ g_slice_free (UtilsSpawnData, data);
+}
+
+/* called in the thread where @cancellable was cancelled */
+static void
+utils_on_cancelled (GCancellable *cancellable,
+ gpointer user_data)
+{
+ UtilsSpawnData *data = user_data;
+ GError *error;
+
+ error = NULL;
+ g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error));
+ g_simple_async_result_take_error (data->simple, error);
+ g_simple_async_result_complete_in_idle (data->simple);
+ g_object_unref (data->simple);
+}
+
+static gboolean
+utils_read_child_stderr (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ UtilsSpawnData *data = user_data;
+ gchar buf[1024];
+ gsize bytes_read;
+
+ g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
+ g_string_append_len (data->child_stderr, buf, bytes_read);
+ return TRUE;
+}
+
+static gboolean
+utils_read_child_stdout (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ UtilsSpawnData *data = user_data;
+ gchar buf[1024];
+ gsize bytes_read;
+
+ g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
+ g_string_append_len (data->child_stdout, buf, bytes_read);
+ return TRUE;
+}
+
+static void
+utils_child_watch_cb (GPid pid,
+ gint status,
+ gpointer user_data)
+{
+ UtilsSpawnData *data = user_data;
+ gchar *buf;
+ gsize buf_size;
+
+ if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
+ {
+ g_string_append_len (data->child_stdout, buf, buf_size);
+ g_free (buf);
+ }
+ if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
+ {
+ g_string_append_len (data->child_stderr, buf, buf_size);
+ g_free (buf);
+ }
+
+ data->exit_status = status;
+
+ /* ok, child watch is history, make sure we don't free it in spawn_data_free() */
+ data->child_pid = 0;
+ data->child_watch_source = NULL;
+
+ /* we're done */
+ g_simple_async_result_complete_in_idle (data->simple);
+ g_object_unref (data->simple);
+}
+
+static gboolean
+utils_timeout_cb (gpointer user_data)
+{
+ UtilsSpawnData *data = user_data;
+
+ data->timed_out = TRUE;
+
+ /* ok, timeout is history, make sure we don't free it in spawn_data_free() */
+ data->timeout_source = NULL;
+
+ /* we're done */
+ g_simple_async_result_complete_in_idle (data->simple);
+ g_object_unref (data->simple);
+
+ return FALSE; /* remove source */
+}
+
+static void
+utils_spawn (const gchar *const *argv,
+ guint timeout_seconds,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ UtilsSpawnData *data;
+ GError *error;
+
+ data = g_slice_new0 (UtilsSpawnData);
+ data->timeout_seconds = timeout_seconds;
+ data->simple = g_simple_async_result_new (NULL,
+ callback,
+ user_data,
+ utils_spawn);
+ data->main_context = g_main_context_get_thread_default ();
+ if (data->main_context != NULL)
+ g_main_context_ref (data->main_context);
+
+ data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL;
+
+ data->child_stdout = g_string_new (NULL);
+ data->child_stderr = g_string_new (NULL);
+ data->child_stdout_fd = -1;
+ data->child_stderr_fd = -1;
+
+ /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */
+ g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free);
+
+ error = NULL;
+ if (data->cancellable != NULL)
+ {
+ /* could already be cancelled */
+ error = NULL;
+ if (g_cancellable_set_error_if_cancelled (data->cancellable, &error))
+ {
+ g_simple_async_result_take_error (data->simple, error);
+ g_simple_async_result_complete_in_idle (data->simple);
+ g_object_unref (data->simple);
+ goto out;
+ }
+
+ data->cancellable_handler_id = g_cancellable_connect (data->cancellable,
+ G_CALLBACK (utils_on_cancelled),
+ data,
+ NULL);
+ }
+
+ error = NULL;
+ if (!g_spawn_async_with_pipes (NULL, /* working directory */
+ (gchar **) argv,
+ NULL, /* envp */
+ G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, /* child_setup */
+ NULL, /* child_setup's user_data */
+ &(data->child_pid),
+ NULL, /* gint *stdin_fd */
+ &(data->child_stdout_fd),
+ &(data->child_stderr_fd),
+ &error))
+ {
+ g_prefix_error (&error, "Error spawning: ");
+ g_simple_async_result_take_error (data->simple, error);
+ g_simple_async_result_complete_in_idle (data->simple);
+ g_object_unref (data->simple);
+ goto out;
+ }
+
+ if (timeout_seconds > 0)
+ {
+ data->timeout_source = g_timeout_source_new_seconds (timeout_seconds);
+ g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL);
+ g_source_attach (data->timeout_source, data->main_context);
+ g_source_unref (data->timeout_source);
+ }
+
+ data->child_watch_source = g_child_watch_source_new (data->child_pid);
+ g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL);
+ g_source_attach (data->child_watch_source, data->main_context);
+ g_source_unref (data->child_watch_source);
+
+ data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd);
+ g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL);
+ data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN);
+ g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL);
+ g_source_attach (data->child_stdout_source, data->main_context);
+ g_source_unref (data->child_stdout_source);
+
+ data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd);
+ g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL);
+ data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN);
+ g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL);
+ g_source_attach (data->child_stderr_source, data->main_context);
+ g_source_unref (data->child_stderr_source);
+
+ out:
+ ;
+}
+
+gboolean
+utils_spawn_finish (GAsyncResult *res,
+ gint *out_exit_status,
+ gchar **out_standard_output,
+ gchar **out_standard_error,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ UtilsSpawnData *data;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == utils_spawn);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ goto out;
+
+ data = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (data->timed_out)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_TIMED_OUT,
+ "Timed out after %d seconds",
+ data->timeout_seconds);
+ goto out;
+ }
+
+ if (out_exit_status != NULL)
+ *out_exit_status = data->exit_status;
+
+ if (out_standard_output != NULL)
+ *out_standard_output = g_strdup (data->child_stdout->str);
+
+ if (out_standard_error != NULL)
+ *out_standard_error = g_strdup (data->child_stderr->str);
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
diff --git a/test/data/etc/polkit-1/rules.d/10-testing.rules b/test/data/etc/polkit-1/rules.d/10-testing.rules
index 0cad62c..4a35e48 100644
--- a/test/data/etc/polkit-1/rules.d/10-testing.rules
+++ b/test/data/etc/polkit-1/rules.d/10-testing.rules
@@ -71,3 +71,66 @@ polkit.addRule(function(action, subject, details) {
return "no";
}
});
+
+// ---------------------------------------------------------------------
+// spawning
+
+polkit.addRule(function(action, subject, details) {
+ if (action == "net.company.spawning.non_existing_helper") {
+ try {
+ polkit.spawn(["/path/to/non/existing/helper"]);
+ return "no";
+ } catch (error) {
+ return "yes";
+ }
+ }
+});
+
+polkit.addRule(function(action, subject, details) {
+ if (action == "net.company.spawning.successful_helper") {
+ try {
+ polkit.spawn(["/bin/true"]);
+ return "yes";
+ } catch (error) {
+ return "no";
+ }
+ }
+});
+
+polkit.addRule(function(action, subject, details) {
+ if (action == "net.company.spawning.failing_helper") {
+ try {
+ polkit.spawn(["/bin/false"]);
+ return "no";
+ } catch (error) {
+ return "yes";
+ }
+ }
+});
+
+polkit.addRule(function(action, subject, details) {
+ if (action == "net.company.spawning.helper_with_output") {
+ try {
+ var out = polkit.spawn(["echo", "-n", "-e", "Hello\nWorld"]);
+ if (out == "Hello\nWorld")
+ return "yes";
+ else
+ return "no";
+ } catch (error) {
+ return "no";
+ }
+ }
+});
+
+polkit.addRule(function(action, subject, details) {
+ if (action == "net.company.spawning.helper_timeout") {
+ try {
+ polkit.spawn(["sleep", "20"]);
+ return "no";
+ } catch (error) {
+ if (error == "Error: Error spawning helper: Timed out after 10 seconds (g-io-error-quark, 24)")
+ return "yes";
+ return "no";
+ }
+ }
+});
diff --git a/test/polkitbackend/test-polkitbackendjsauthority.c b/test/polkitbackend/test-polkitbackendjsauthority.c
index f81c7fb..948cbc1 100644
--- a/test/polkitbackend/test-polkitbackendjsauthority.c
+++ b/test/polkitbackend/test-polkitbackendjsauthority.c
@@ -23,6 +23,7 @@
#include "glib.h"
+#include <locale.h>
#include <polkit/polkit.h>
#include <polkitbackend/polkitbackendjsauthority.h>
#include <polkittesthelper.h>
@@ -246,6 +247,43 @@ static const RulesTestCase rules_test_cases[] = {
POLKIT_IMPLICIT_AUTHORIZATION_NOT_AUTHORIZED,
NULL
},
+
+ /* spawning */
+ {
+ "spawning_non_existing_helper",
+ "net.company.spawning.non_existing_helper",
+ "unix-user:root",
+ POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED,
+ NULL
+ },
+ {
+ "spawning_successful_helper",
+ "net.company.spawning.successful_helper",
+ "unix-user:root",
+ POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED,
+ NULL
+ },
+ {
+ "spawning_failing_helper",
+ "net.company.spawning.failing_helper",
+ "unix-user:root",
+ POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED,
+ NULL
+ },
+ {
+ "spawning_helper_with_output",
+ "net.company.spawning.helper_with_output",
+ "unix-user:root",
+ POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED,
+ NULL
+ },
+ {
+ "spawning_helper_timeout",
+ "net.company.spawning.helper_timeout",
+ "unix-user:root",
+ POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED,
+ NULL
+ },
};
/* ---------------------------------------------------------------------------------------------------- */
@@ -310,6 +348,8 @@ main (int argc, char *argv[])
{
GIOExtensionPoint *ep;
+ setlocale (LC_ALL, "");
+
g_type_init ();
g_test_init (&argc, &argv, NULL);
//polkit_test_redirect_logs ();