From 064d7588d1272481b22e70ed2c3901e7673b23f7 Mon Sep 17 00:00:00 2001
From: Stef Walter <stefw@gnome.org>
Date: Mon, 20 Aug 2012 14:03:48 +0000
Subject: gcr: Cancel the prompt when prompter goes away
* If the prompter quits, cancel any prompting that's going on.
https://bugzilla.gnome.org/show_bug.cgi?id=684478
---
diff --git a/gcr/gcr-base.symbols b/gcr/gcr-base.symbols
index c794829..35a74a1 100644
--- a/gcr/gcr-base.symbols
+++ b/gcr/gcr-base.symbols
@@ -111,6 +111,7 @@ gcr_mock_prompter_is_prompting
gcr_mock_prompter_set_delay_msec
gcr_mock_prompter_start
gcr_mock_prompter_stop
+gcr_mock_prompter_disconnect
gcr_parsed_get_attributes
gcr_parsed_get_data
gcr_parsed_get_description
diff --git a/gcr/gcr-mock-prompter.c b/gcr/gcr-mock-prompter.c
index 564ed2d..31cbba2 100644
--- a/gcr/gcr-mock-prompter.c
+++ b/gcr/gcr-mock-prompter.c
@@ -105,7 +105,7 @@ typedef struct {
/* Owned by the prompter thread*/
GcrSystemPrompter *prompter;
- const gchar *bus_name;
+ GDBusConnection *connection;
GMainLoop *loop;
} ThreadData;
@@ -943,9 +943,9 @@ mock_prompter_thread (gpointer data)
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
NULL, NULL, &error);
if (error == NULL) {
+ thread_data->connection = connection;
gcr_system_prompter_register (GCR_SYSTEM_PROMPTER (thread_data->prompter),
connection);
- thread_data->bus_name = g_dbus_connection_get_unique_name (connection);
} else {
g_critical ("couldn't create connection: %s", error->message);
g_error_free (error);
@@ -978,10 +978,19 @@ mock_prompter_thread (gpointer data)
thread_data->prompter = NULL;
if (connection) {
- if (!g_dbus_connection_flush_sync (connection, NULL, &error)) {
- g_critical ("connection flush failed: %s", error->message);
- g_error_free (error);
+ thread_data->connection = NULL;
+
+ if (!g_dbus_connection_is_closed (connection)) {
+ if (!g_dbus_connection_flush_sync (connection, NULL, &error)) {
+ g_critical ("connection flush failed: %s", error->message);
+ g_error_free (error);
+ }
+ if (!g_dbus_connection_close_sync (connection, NULL, &error)) {
+ g_critical ("connection close failed: %s", error->message);
+ g_error_free (error);
+ }
}
+
g_object_unref (connection);
}
@@ -1030,7 +1039,22 @@ gcr_mock_prompter_start (void)
g_assert (running->prompter);
g_mutex_unlock (running->mutex);
- return running->bus_name;
+ return g_dbus_connection_get_unique_name (running->connection);
+}
+
+void
+gcr_mock_prompter_disconnect (void)
+{
+ GError *error = NULL;
+
+ g_assert (running != NULL);
+ g_assert (running->connection);
+
+ g_dbus_connection_close_sync (running->connection, NULL, &error);
+ if (error != NULL) {
+ g_critical ("disconnect connection close failed: %s", error->message);
+ g_error_free (error);
+ }
}
/**
diff --git a/gcr/gcr-mock-prompter.h b/gcr/gcr-mock-prompter.h
index 3e2cf26..f89f762 100644
--- a/gcr/gcr-mock-prompter.h
+++ b/gcr/gcr-mock-prompter.h
@@ -36,6 +36,8 @@ G_BEGIN_DECLS
const gchar * gcr_mock_prompter_start (void);
+void gcr_mock_prompter_disconnect (void);
+
void gcr_mock_prompter_stop (void);
gboolean gcr_mock_prompter_is_prompting (void);
diff --git a/gcr/gcr-system-prompt.c b/gcr/gcr-system-prompt.c
index 640aa77..b370b1c 100644
--- a/gcr/gcr-system-prompt.c
+++ b/gcr/gcr-system-prompt.c
@@ -143,8 +143,10 @@ static gint unique_prompt_id = 0;
typedef struct {
GSource *timeout;
+ GSource *waiting;
GMainContext *context;
GCancellable *cancellable;
+ guint watch_id;
} CallClosure;
static void
@@ -153,11 +155,45 @@ call_closure_free (gpointer data)
CallClosure *closure = data;
if (closure->timeout)
g_source_destroy (closure->timeout);
- g_clear_object (&closure->cancellable);
+ if (closure->waiting)
+ g_source_destroy (closure->waiting);
+ if (closure->watch_id)
+ g_bus_unwatch_name (closure->watch_id);
+ g_object_unref (closure->cancellable);
g_free (data);
}
static void
+on_propagate_cancelled (GCancellable *cancellable,
+ gpointer user_data)
+{
+ /* Propagate the cancelled signal */
+ GCancellable *cancel = G_CANCELLABLE (user_data);
+ g_cancellable_cancel (cancel);
+}
+
+static CallClosure *
+call_closure_new (GCancellable *cancellable)
+{
+ CallClosure *call;
+
+ /*
+ * We use our own cancellable object, since we cancel it it in
+ * situations other than when the caller cancels.
+ */
+
+ call = g_new0 (CallClosure, 1);
+ call->cancellable = g_cancellable_new ();
+
+ if (cancellable) {
+ g_cancellable_connect (cancellable, G_CALLBACK (on_propagate_cancelled),
+ g_object_ref (call->cancellable), g_object_unref);
+ }
+
+ return call;
+}
+
+static void
gcr_system_prompt_init (GcrSystemPrompt *self)
{
self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_SYSTEM_PROMPT,
@@ -742,6 +778,16 @@ register_prompt_object (GcrSystemPrompt *self,
}
static void
+on_prompter_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
+ CallClosure *call = g_simple_async_result_get_op_res_gpointer (async);
+ g_cancellable_cancel (call->cancellable);
+}
+
+static void
on_bus_connected (GObject *source,
GAsyncResult *result,
gpointer user_data)
@@ -763,6 +809,12 @@ on_bus_connected (GObject *source,
g_main_context_push_thread_default (closure->context);
+ closure->watch_id = g_bus_watch_name_on_connection (self->pv->connection,
+ self->pv->prompter_bus_name,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ NULL, on_prompter_vanished,
+ res, NULL);
+
register_prompt_object (self, &error);
g_main_context_pop_thread_default (closure->context);
@@ -835,6 +887,27 @@ on_call_timeout (gpointer user_data)
return FALSE; /* Don't call this function again */
}
+static gboolean
+on_call_cancelled (GCancellable *cancellable,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
+ CallClosure *call = g_simple_async_result_get_op_res_gpointer (async);
+ GcrSystemPrompt *self = GCR_SYSTEM_PROMPT (g_async_result_get_source_object (user_data));
+
+ g_source_destroy (call->waiting);
+ call->waiting = NULL;
+
+ g_simple_async_result_set_error (async, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+ _("The operation was cancelled"));
+
+ /* Tell the prompter we're no longer interested */
+ gcr_system_prompt_close_async (self, NULL, NULL, NULL);
+
+ g_object_unref (self);
+ return FALSE; /* Don't call this function again */
+}
+
void
perform_init_async (GcrSystemPrompt *self,
GSimpleAsyncResult *res)
@@ -896,7 +969,7 @@ gcr_system_prompt_real_init_async (GAsyncInitable *initable,
res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
gcr_system_prompt_real_init_async);
- closure = g_new0 (CallClosure, 1);
+ closure = call_closure_new (cancellable);
closure->context = g_main_context_get_thread_default ();
if (closure->context)
g_main_context_ref (closure->context);
@@ -1029,6 +1102,7 @@ on_perform_prompt_complete (GObject *source,
{
GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
GcrSystemPrompt *self = GCR_SYSTEM_PROMPT (g_async_result_get_source_object (user_data));
+ CallClosure *call = g_simple_async_result_get_op_res_gpointer (res);
GError *error = NULL;
GVariant *retval;
@@ -1037,6 +1111,11 @@ on_perform_prompt_complete (GObject *source,
self->pv->pending = NULL;
g_simple_async_result_take_error (res, error);
g_simple_async_result_complete (res);
+ } else {
+ g_assert (call->waiting == NULL);
+ call->waiting = g_cancellable_source_new (call->cancellable);
+ g_source_set_callback (call->waiting, (GSourceFunc)on_call_cancelled, res, NULL);
+ g_source_attach (call->waiting, call->context);
}
if (retval)
@@ -1069,8 +1148,7 @@ perform_prompt_async (GcrSystemPrompt *self,
}
res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, source_tag);
- closure = g_new0 (CallClosure, 1);
- closure->cancellable = cancellable ? g_object_ref (cancellable) : cancellable;
+ closure = call_closure_new (cancellable);
g_simple_async_result_set_op_res_gpointer (res, closure, call_closure_free);
if (self->pv->closed) {
@@ -1088,6 +1166,12 @@ perform_prompt_async (GcrSystemPrompt *self,
else
sent = gcr_secret_exchange_begin (exchange);
+ closure->watch_id = g_bus_watch_name_on_connection (self->pv->connection,
+ self->pv->prompter_bus_name,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ NULL, on_prompter_vanished,
+ res, NULL);
+
builder = build_dirty_properties (self);
/* Reregister the prompt object in the current GMainContext */
@@ -1470,8 +1554,7 @@ gcr_system_prompt_close_async (GcrSystemPrompt *self,
res = g_simple_async_result_new (NULL, callback, user_data,
gcr_system_prompt_close_async);
- closure = g_new0 (CallClosure, 1);
- closure->cancellable = cancellable ? g_object_ref (cancellable) : g_cancellable_new ();
+ closure = call_closure_new (cancellable);
closure->context = g_main_context_get_thread_default ();
if (closure->context != NULL)
g_main_context_ref (closure->context);
diff --git a/gcr/tests/test-system-prompt.c b/gcr/tests/test-system-prompt.c
index 0d48124..b5cae2c 100644
--- a/gcr/tests/test-system-prompt.c
+++ b/gcr/tests/test-system-prompt.c
@@ -641,6 +641,89 @@ test_after_close_dismisses (Test *test,
egg_assert_not_object (prompt);
}
+typedef struct {
+ GAsyncResult *result1;
+ GAsyncResult *result2;
+} ResultPair;
+
+static void
+on_result_pair_one (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ResultPair *pair = user_data;
+ g_assert (pair->result1 == NULL);
+ pair->result1 = g_object_ref (result);
+ if (pair->result1 && pair->result2)
+ egg_test_wait_stop ();
+}
+
+static void
+on_result_pair_two (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ResultPair *pair = user_data;
+ g_assert (pair->result2 == NULL);
+ pair->result2 = g_object_ref (result);
+ if (pair->result1 && pair->result2)
+ egg_test_wait_stop ();
+}
+
+static void
+test_watch_cancels (Test *test,
+ gconstpointer unused)
+{
+ GDBusConnection *connection;
+ GcrPrompt *prompt;
+ GcrPrompt *prompt2;
+ GError *error = NULL;
+ const gchar *password;
+ ResultPair pair = { NULL, NULL };
+
+ gcr_mock_prompter_set_delay_msec (3000);
+ gcr_mock_prompter_expect_password_ok ("booo", NULL);
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ g_assert_no_error (error);
+
+ /* This should happen immediately */
+ prompt = gcr_system_prompt_open_for_prompter (test->prompter_name, 0, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GCR_IS_SYSTEM_PROMPT (prompt));
+
+ /* Show a password prompt */
+ gcr_prompt_password_async (prompt, NULL, on_result_pair_one, &pair);
+
+ /* This prompt should wait, block */
+ gcr_system_prompt_open_for_prompter_async (test->prompter_name, 0, NULL,
+ on_result_pair_two, &pair);
+
+ /* Wait a bit before stopping, so outgoing request is done */
+ g_usleep (G_TIME_SPAN_SECOND / 4);
+
+ /* Kill the mock prompter */
+ gcr_mock_prompter_disconnect ();
+
+ /* Both the above operations should cancel */
+ egg_test_wait ();
+
+ prompt2 = gcr_system_prompt_open_finish (pair.result2, &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_clear_error (&error);
+ g_assert (prompt2 == NULL);
+
+ password = gcr_prompt_password_finish (prompt, pair.result1, &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_clear_error (&error);
+ g_assert (password == NULL);
+
+ g_object_unref (prompt);
+ g_object_unref (pair.result1);
+ g_object_unref (pair.result2);
+ g_object_unref (connection);
+}
+
int
main (int argc, char **argv)
{
@@ -667,6 +750,7 @@ main (int argc, char **argv)
g_test_add ("/gcr/system-prompt/close-cancels", Test, NULL, setup, test_close_cancels, teardown);
g_test_add ("/gcr/system-prompt/after-close-dismisses", Test, NULL, setup, test_after_close_dismisses, teardown);
g_test_add ("/gcr/system-prompt/close-from-prompter", Test, NULL, setup, test_close_from_prompter, teardown);
+ g_test_add ("/gcr/system-prompt/watch-cancels", Test, NULL, setup, test_watch_cancels, teardown);
return egg_tests_run_with_loop ();
}
--
cgit v0.9.0.2