diff options
-rw-r--r-- | src/polkitbackend/polkitbackendjsauthority.c | 446 | ||||
-rw-r--r-- | test/data/etc/polkit-1/rules.d/10-testing.rules | 63 | ||||
-rw-r--r-- | test/polkitbackend/test-polkitbackendjsauthority.c | 40 |
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 (); |