From 29edfc1169d3c00aedbd2bf830fda9823908b964 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Thu, 10 Feb 2022 19:21:24 +0000 Subject: [PATCH 1/6] =?UTF-8?q?gdebugcontroller:=20Drop=20dup=5Fdefault()?= =?UTF-8?q?=20method=20as=20it=E2=80=99s=20broken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If `GDebugControllerDBus` remains as the only, or default, implementation of `GDebugController`, `dup_default()` cannot work. `GDebugControllerDBus` requires a `GDBusConnection` at construction time, which the `GIOModule` construction code can’t provide it. Either we use a default D-Bus connection (but which one? and how would it be changed by the user later if it was the wrong one?), or delegate singleton handling of the `GDebugController` to the user. The latter approach seems more flexible. Signed-off-by: Philip Withnall Helps: #1190 --- docs/reference/gio/gio-sections-common.txt | 1 - gio/gdebugcontroller.c | 17 ----------------- gio/gdebugcontroller.h | 3 --- 3 files changed, 21 deletions(-) diff --git a/docs/reference/gio/gio-sections-common.txt b/docs/reference/gio/gio-sections-common.txt index 4842deaf7..0c73afa6f 100644 --- a/docs/reference/gio/gio-sections-common.txt +++ b/docs/reference/gio/gio-sections-common.txt @@ -4215,7 +4215,6 @@ GDBusObjectManagerServerPrivate GDebugController GDebugControllerInterface G_DEBUG_CONTROLLER_EXTENSION_POINT_NAME -g_debug_controller_dup_default g_debug_controller_get_debug_enabled g_debug_controller_set_debug_enabled diff --git a/gio/gdebugcontroller.c b/gio/gdebugcontroller.c index 155c30445..13808d500 100644 --- a/gio/gdebugcontroller.c +++ b/gio/gdebugcontroller.c @@ -50,23 +50,6 @@ G_DEFINE_INTERFACE_WITH_CODE (GDebugController, g_debug_controller, G_TYPE_OBJECT, g_type_interface_add_prerequisite (g_define_type_id, G_TYPE_INITABLE)) -/** - * g_debug_controller_dup_default: - * - * Gets a reference to the default #GDebugController for the system. - * - * Returns: (not nullable) (transfer full): a new reference to the default #GDebugController - * - * Since: 2.72 - */ -GDebugController * -g_debug_controller_dup_default (void) -{ - return g_object_ref (_g_io_module_get_default (G_DEBUG_CONTROLLER_EXTENSION_POINT_NAME, - "GIO_USE_DEBUG_CONTROLLER", - NULL)); -} - static void g_debug_controller_default_init (GDebugControllerInterface *iface) { diff --git a/gio/gdebugcontroller.h b/gio/gdebugcontroller.h index 59b1ede2b..ca3a2d29d 100644 --- a/gio/gdebugcontroller.h +++ b/gio/gdebugcontroller.h @@ -68,9 +68,6 @@ struct _GDebugControllerInterface { GTypeInterface g_iface; }; -GLIB_AVAILABLE_IN_2_72 -GDebugController *g_debug_controller_dup_default (void); - GLIB_AVAILABLE_IN_2_72 gboolean g_debug_controller_get_debug_enabled (GDebugController *self); GLIB_AVAILABLE_IN_2_72 From a03920152f3445ce0c02f22e900ea2e6b6c0d2bc Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Thu, 10 Feb 2022 19:23:21 +0000 Subject: [PATCH 2/6] gdebugcontrollerdbus: Fix a typo in a D-Bus interface name This was preventing `PropertiesChanged` signals from working. Signed-off-by: Philip Withnall Helps: #1190 --- gio/gdebugcontrollerdbus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gio/gdebugcontrollerdbus.c b/gio/gdebugcontrollerdbus.c index 9085620b4..d58b03c76 100644 --- a/gio/gdebugcontrollerdbus.c +++ b/gio/gdebugcontrollerdbus.c @@ -131,7 +131,7 @@ set_debug_enabled (GDebugControllerDBus *self, g_dbus_connection_emit_signal (priv->connection, NULL, "/org/gtk/Debugging", - "org.gtk.DBus.Properties", + "org.freedesktop.DBus.Properties", "PropertiesChanged", g_variant_new ("(sa{sv}as)", "org.gtk.Debugging", From 35f6c65b3524f39eb8c8631d2e5aa0d61e72fc78 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Thu, 10 Feb 2022 19:23:49 +0000 Subject: [PATCH 3/6] giomodule: Ensure `GDebugControllerDBus` is registered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Or it’ll never work as a `GIOModule`, as the implementation won’t be found. Signed-off-by: Philip Withnall Helps: #1190 --- gio/giomodule.c | 1 + 1 file changed, 1 insertion(+) diff --git a/gio/giomodule.c b/gio/giomodule.c index e727b83bc..38ea7bc48 100644 --- a/gio/giomodule.c +++ b/gio/giomodule.c @@ -1343,6 +1343,7 @@ _g_io_modules_ensure_loaded (void) #endif #ifdef G_OS_UNIX g_type_ensure (_g_unix_volume_monitor_get_type ()); + g_type_ensure (g_debug_controller_dbus_get_type ()); g_type_ensure (g_fdo_notification_backend_get_type ()); g_type_ensure (g_gtk_notification_backend_get_type ()); g_type_ensure (g_portal_notification_backend_get_type ()); From 6d5953ee4835464a37cb7488543b9535edb6122e Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Thu, 10 Feb 2022 19:24:17 +0000 Subject: [PATCH 4/6] gdebugcontroller: Add some more documentation Signed-off-by: Philip Withnall Helps: #1190 --- gio/gdebugcontroller.c | 4 ++ gio/gdebugcontrollerdbus.c | 99 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/gio/gdebugcontroller.c b/gio/gdebugcontroller.c index 13808d500..c61561621 100644 --- a/gio/gdebugcontroller.c +++ b/gio/gdebugcontroller.c @@ -44,6 +44,10 @@ * default. Application code may connect to the #GObject::notify signal for it * to control other parts of its debug infrastructure as necessary. * + * If your application or service is using the default GLib log writer function, + * creating one of the built-in implementations of #GDebugController should be + * all that’s needed to dynamically enable or disable debug output. + * * Since: 2.72 */ diff --git a/gio/gdebugcontrollerdbus.c b/gio/gdebugcontrollerdbus.c index d58b03c76..e64390654 100644 --- a/gio/gdebugcontrollerdbus.c +++ b/gio/gdebugcontrollerdbus.c @@ -42,6 +42,105 @@ * #GDebugControllerDBus:connection once it’s initialized. The object will be * unregistered when the last reference to the #GDebugControllerDBus is dropped. * + * This D-Bus object can be used by remote processes to enable or disable debug + * output in this process. Remote processes calling + * `org.gtk.Debugging.SetDebugEnabled()` will affect the value of + * #GDebugController:debug-enabled and, by default, g_log_get_debug_enabled(). + * default. + * + * By default, all processes will be able to call `SetDebugEnabled()`. If this + * process is privileged, or might expose sensitive information in its debug + * output, you may want to restrict the ability to enable debug output to + * privileged users or processes. + * + * One option is to install a D-Bus security policy which restricts access to + * `SetDebugEnabled()`, installing something like the following in + * `$datadir/dbus-1/system.d/`: + * |[ + * + * + * + * + * + * + * + * + * + * + * ]| + * + * This will prevent the `SetDebugEnabled()` method from being called by all + * except root. It will not prevent the `DebugEnabled` property from being read, + * as it’s accessed through the `org.freedesktop.DBus.Properties` interface. + * + * Another option is to use polkit to allow or deny requests on a case-by-case + * basis, allowing for the possibility of dynamic authorisation. To do this, + * connect to the #GDebugControllerDBus::authorize signal and query polkit in + * it: + * |[ + * g_autoptr(GError) child_error = NULL; + * g_autoptr(GDBusConnection) connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL); + * gulong debug_controller_authorize_id = 0; + * + * // Set up the debug controller. + * debug_controller = G_DEBUG_CONTROLLER (g_debug_controller_dbus_new (priv->connection, NULL, &child_error)); + * if (debug_controller == NULL) + * { + * g_error ("Could not register debug controller on bus: %s"), + * child_error->message); + * } + * + * debug_controller_authorize_id = g_signal_connect (debug_controller, + * "authorize", + * G_CALLBACK (debug_controller_authorize_cb), + * self); + * + * static gboolean + * debug_controller_authorize_cb (GDebugControllerDBus *debug_controller, + * GDBusMethodInvocation *invocation, + * gpointer user_data) + * { + * g_autoptr(PolkitAuthority) authority = NULL; + * g_autoptr(PolkitSubject) subject = NULL; + * g_autoptr(PolkitAuthorizationResult) auth_result = NULL; + * g_autoptr(GError) local_error = NULL; + * GDBusMessage *message; + * GDBusMessageFlags message_flags; + * PolkitCheckAuthorizationFlags flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE; + * + * message = g_dbus_method_invocation_get_message (invocation); + * message_flags = g_dbus_message_get_flags (message); + * + * authority = polkit_authority_get_sync (NULL, &local_error); + * if (authority == NULL) + * { + * g_warning ("Failed to get polkit authority: %s", local_error->message); + * return FALSE; + * } + * + * if (message_flags & G_DBUS_MESSAGE_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION) + * flags |= POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; + * + * subject = polkit_system_bus_name_new (g_dbus_method_invocation_get_sender (invocation)); + * + * auth_result = polkit_authority_check_authorization_sync (authority, + * subject, + * "com.example.MyService.set-debug-enabled", + * NULL, + * flags, + * NULL, + * &local_error); + * if (auth_result == NULL) + * { + * g_warning ("Failed to get check polkit authorization: %s", local_error->message); + * return FALSE; + * } + * + * return polkit_authorization_result_get_is_authorized (auth_result); + * } + * ]| + * * Since: 2.72 */ From 1b3e6bab533537eb7129986f72788ac11b23ae0b Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Fri, 11 Feb 2022 11:36:28 +0000 Subject: [PATCH 5/6] gdebugcontrollerdbus: Add stop() method This allows the controller to explicitly be removed from the bus, in a way that allows the caller to synchronise with it and know that all other references to the controller should have been dropped (i.e. after this method returns, there should be no in-flight D-Bus calls still holding a reference to the object). This is needed to be able to guarantee finalisation of the controller in unit tests (and comparable real-world situations). Signed-off-by: Philip Withnall Helps: #1190 --- docs/reference/gio/gio-sections-common.txt | 1 + gio/gdebugcontrollerdbus.c | 94 ++++++++++++++++++---- gio/gdebugcontrollerdbus.h | 3 + 3 files changed, 84 insertions(+), 14 deletions(-) diff --git a/docs/reference/gio/gio-sections-common.txt b/docs/reference/gio/gio-sections-common.txt index 0c73afa6f..4e88a597e 100644 --- a/docs/reference/gio/gio-sections-common.txt +++ b/docs/reference/gio/gio-sections-common.txt @@ -4230,6 +4230,7 @@ G_DEBUG_CONTROLLER_GET_INTERFACE GDebugControllerDBus GDebugControllerDBus g_debug_controller_dbus_new +g_debug_controller_dbus_stop g_debug_controller_dbus_get_type G_TYPE_DEBUG_CONTROLLER_DBUS diff --git a/gio/gdebugcontrollerdbus.c b/gio/gdebugcontrollerdbus.c index e64390654..e3dcdbde2 100644 --- a/gio/gdebugcontrollerdbus.c +++ b/gio/gdebugcontrollerdbus.c @@ -183,8 +183,10 @@ typedef struct { GObject parent_instance; + GCancellable *cancellable; /* (owned) */ GDBusConnection *connection; /* (owned) */ guint object_id; + guint n_pending_authorize_tasks; gboolean debug_enabled; } GDebugControllerDBusPrivate; @@ -202,8 +204,11 @@ G_DEFINE_TYPE_WITH_CODE (GDebugControllerDBus, g_debug_controller_dbus, G_TYPE_O 30)) static void -g_debug_controller_dbus_init (GDebugControllerDBus *dbus) +g_debug_controller_dbus_init (GDebugControllerDBus *self) { + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); + + priv->cancellable = g_cancellable_new (); } static void @@ -212,6 +217,9 @@ set_debug_enabled (GDebugControllerDBus *self, { GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); + if (g_cancellable_is_cancelled (priv->cancellable)) + return; + if (debug_enabled != priv->debug_enabled) { GVariantBuilder builder; @@ -288,6 +296,7 @@ authorize_cb (GObject *object, gpointer user_data) { GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (object); + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); GTask *task = G_TASK (result); GDBusMethodInvocation *invocation = g_task_get_task_data (task); GVariant *parameters = g_dbus_method_invocation_get_parameters (invocation); @@ -301,14 +310,18 @@ authorize_cb (GObject *object, GError *local_error = g_error_new (G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, _("Not authorized to change debug settings")); g_dbus_method_invocation_take_error (invocation, g_steal_pointer (&local_error)); - return; + } + else + { + /* Update the property value. */ + g_variant_get (parameters, "(b)", &enabled); + set_debug_enabled (self, enabled); + + g_dbus_method_invocation_return_value (invocation, NULL); } - /* Update the property value. */ - g_variant_get (parameters, "(b)", &enabled); - set_debug_enabled (self, enabled); - - g_dbus_method_invocation_return_value (invocation, NULL); + g_assert (priv->n_pending_authorize_tasks > 0); + priv->n_pending_authorize_tasks--; } /* Called in the #GMainContext which was default when the #GDebugControllerDBus @@ -324,6 +337,7 @@ dbus_method_call (GDBusConnection *connection, gpointer user_data) { GDebugControllerDBus *self = user_data; + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); GDebugControllerDBusClass *klass = G_DEBUG_CONTROLLER_DBUS_GET_CLASS (self); /* Only on the org.gtk.Debugging interface */ @@ -331,10 +345,13 @@ dbus_method_call (GDBusConnection *connection, { GTask *task = NULL; - task = g_task_new (self, NULL, authorize_cb, NULL); + task = g_task_new (self, priv->cancellable, authorize_cb, NULL); g_task_set_source_tag (task, dbus_method_call); g_task_set_task_data (task, g_object_ref (invocation), (GDestroyNotify) g_object_unref); + g_assert (priv->n_pending_authorize_tasks < G_MAXUINT); + priv->n_pending_authorize_tasks++; + /* Check the calling peer is authorised to change the debug mode. So that * the signal handler can block on checking polkit authorisation (which * definitely involves D-Bus calls, and might involve user interaction), @@ -448,13 +465,10 @@ g_debug_controller_dbus_dispose (GObject *object) GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (object); GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); - if (priv->object_id != 0) - { - g_dbus_connection_unregister_object (priv->connection, priv->object_id); - priv->object_id = 0; - } - + g_debug_controller_dbus_stop (self); + g_assert (priv->n_pending_authorize_tasks == 0); g_clear_object (&priv->connection); + g_clear_object (&priv->cancellable); G_OBJECT_CLASS (g_debug_controller_dbus_parent_class)->dispose (object); } @@ -587,3 +601,55 @@ g_debug_controller_dbus_new (GDBusConnection *connection, "connection", connection, NULL); } + +/** + * g_debug_controller_dbus_stop: + * @self: a #GDebugControllerDBus + * + * Stop the debug controller, unregistering its object from the bus. + * + * Any pending method calls to the object will complete successfully, but new + * ones will return an error. This method will block until all pending + * #GDebugControllerDBus::authorize signals have been handled. This is expected + * to not take long, as it will just be waiting for threads to join. If any + * #GDebugControllerDBus::authorize signal handlers are still executing in other + * threads, this will block until after they have returned. + * + * This method will be called automatically when the final reference to the + * #GDebugControllerDBus is dropped. You may want to call it explicitly to know + * when the controller has been fully removed from the bus, or to break + * reference count cycles. + * + * Calling this method from within a #GDebugControllerDBus::authorize signal + * handler will cause a deadlock and must not be done. + * + * Since: 2.72 + */ +void +g_debug_controller_dbus_stop (GDebugControllerDBus *self) +{ + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); + + g_cancellable_cancel (priv->cancellable); + + if (priv->object_id != 0) + { + g_dbus_connection_unregister_object (priv->connection, priv->object_id); + priv->object_id = 0; + } + + /* Wait for any pending authorize tasks to finish. These will just be waiting + * for threads to join at this point, as the D-Bus object has been + * unregistered and the cancellable cancelled. + * + * FIXME: There is still a narrow race here where (we think) the worker thread + * briefly holds a reference to the #GTask after the task thread function has + * returned. + * + * The loop will also never terminate if g_debug_controller_dbus_stop() is + * called from within an ::authorize callback. + * + * See discussion in https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2486 */ + while (priv->n_pending_authorize_tasks > 0) + g_thread_yield (); +} diff --git a/gio/gdebugcontrollerdbus.h b/gio/gdebugcontrollerdbus.h index 2d41c25cf..5e54bbfa1 100644 --- a/gio/gdebugcontrollerdbus.h +++ b/gio/gdebugcontrollerdbus.h @@ -61,6 +61,9 @@ GDebugControllerDBus *g_debug_controller_dbus_new (GDBusConnection *connection, GCancellable *cancellable, GError **error); +GLIB_AVAILABLE_IN_2_72 +void g_debug_controller_dbus_stop (GDebugControllerDBus *self); + G_END_DECLS #endif /* __G_DEBUG_CONTROLLER_DBUS_H__ */ From b7d3e3f16edb2e6dc5a8ecbac837a258928987ea Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Thu, 10 Feb 2022 19:24:42 +0000 Subject: [PATCH 6/6] tests: Add tests for GDebugController Signed-off-by: Philip Withnall Fixes: #1190 --- gio/tests/debugcontroller.c | 396 ++++++++++++++++++++++++++++++++++++ gio/tests/meson.build | 1 + 2 files changed, 397 insertions(+) create mode 100644 gio/tests/debugcontroller.c diff --git a/gio/tests/debugcontroller.c b/gio/tests/debugcontroller.c new file mode 100644 index 000000000..c20acd659 --- /dev/null +++ b/gio/tests/debugcontroller.c @@ -0,0 +1,396 @@ +/* GLib testing framework examples and tests + * + * Copyright © 2022 Endless OS Foundation, LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * Author: Philip Withnall + */ + +#include +#include + + +static void +test_dbus_basic (void) +{ + GTestDBus *bus; + GDBusConnection *connection = NULL, *connection2 = NULL; + GDebugControllerDBus *controller = NULL; + gboolean old_value; + gboolean debug_enabled; + GError *local_error = NULL; + + g_test_summary ("Smoketest for construction and setting of a #GDebugControllerDBus."); + + /* Set up a test session bus and connection. */ + bus = g_test_dbus_new (G_TEST_DBUS_NONE); + g_test_dbus_up (bus); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error); + g_assert_no_error (local_error); + + /* Create a controller for this process. */ + controller = g_debug_controller_dbus_new (connection, NULL, &local_error); + g_assert_no_error (local_error); + g_assert_nonnull (controller); + g_assert_true (G_IS_DEBUG_CONTROLLER_DBUS (controller)); + + /* Try enabling and disabling debug output from within the process. */ + old_value = g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller)); + + g_debug_controller_set_debug_enabled (G_DEBUG_CONTROLLER (controller), TRUE); + g_assert_true (g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller))); + + g_debug_controller_set_debug_enabled (G_DEBUG_CONTROLLER (controller), FALSE); + g_assert_false (g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller))); + + /* Reset the debug state and check using g_object_get(), to exercise that. */ + g_debug_controller_set_debug_enabled (G_DEBUG_CONTROLLER (controller), old_value); + + g_object_get (G_OBJECT (controller), + "debug-enabled", &debug_enabled, + "connection", &connection2, + NULL); + g_assert_true (debug_enabled == old_value); + g_assert_true (connection2 == connection); + g_clear_object (&connection2); + + g_debug_controller_dbus_stop (controller); + while (g_main_context_iteration (NULL, FALSE)); + g_assert_finalize_object (controller); + g_clear_object (&connection); + + g_test_dbus_down (bus); + g_clear_object (&bus); +} + +static void +test_dbus_duplicate (void) +{ + GTestDBus *bus; + GDBusConnection *connection = NULL; + GDebugControllerDBus *controller1 = NULL, *controller2 = NULL; + GError *local_error = NULL; + + g_test_summary ("Test that creating a second #GDebugControllerDBus on the same D-Bus connection fails."); + + /* Set up a test session bus and connection. */ + bus = g_test_dbus_new (G_TEST_DBUS_NONE); + g_test_dbus_up (bus); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error); + g_assert_no_error (local_error); + + /* Create a controller for this process. */ + controller1 = g_debug_controller_dbus_new (connection, NULL, &local_error); + g_assert_no_error (local_error); + g_assert_nonnull (controller1); + + /* And try creating a second one. */ + controller2 = g_debug_controller_dbus_new (connection, NULL, &local_error); + g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS); + g_assert_null (controller2); + g_clear_error (&local_error); + + g_debug_controller_dbus_stop (controller1); + while (g_main_context_iteration (NULL, FALSE)); + g_assert_finalize_object (controller1); + g_clear_object (&connection); + + g_test_dbus_down (bus); + g_clear_object (&bus); +} + +static void +async_result_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GAsyncResult **result_out = user_data; + + g_assert_null (*result_out); + *result_out = g_object_ref (result); + + g_main_context_wakeup (g_main_context_get_thread_default ()); +} + +static gboolean +authorize_false_cb (GDebugControllerDBus *debug_controller, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + return FALSE; +} + +static gboolean +authorize_true_cb (GDebugControllerDBus *debug_controller, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + return TRUE; +} + +static void +notify_debug_enabled_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + guint *notify_count_out = user_data; + + *notify_count_out = *notify_count_out + 1; +} + +static void +properties_changed_cb (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + guint *properties_changed_count_out = user_data; + + *properties_changed_count_out = *properties_changed_count_out + 1; + g_main_context_wakeup (g_main_context_get_thread_default ()); +} + +static void +test_dbus_properties (void) +{ + GTestDBus *bus; + GDBusConnection *controller_connection = NULL; + GDBusConnection *remote_connection = NULL; + GDebugControllerDBus *controller = NULL; + gboolean old_value; + GAsyncResult *result = NULL; + GVariant *reply = NULL; + GVariant *debug_enabled_variant = NULL; + gboolean debug_enabled; + GError *local_error = NULL; + gulong handler_id; + gulong notify_id; + guint notify_count = 0; + guint properties_changed_id; + guint properties_changed_count = 0; + + g_test_summary ("Test getting and setting properties on a #GDebugControllerDBus."); + + /* Set up a test session bus and connection. Set up a separate second + * connection to simulate a remote peer. */ + bus = g_test_dbus_new (G_TEST_DBUS_NONE); + g_test_dbus_up (bus); + + controller_connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error); + g_assert_no_error (local_error); + + remote_connection = g_dbus_connection_new_for_address_sync (g_test_dbus_get_bus_address (bus), + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, + NULL, + NULL, + &local_error); + g_assert_no_error (local_error); + + /* Create a controller for this process. */ + controller = g_debug_controller_dbus_new (controller_connection, NULL, &local_error); + g_assert_no_error (local_error); + g_assert_nonnull (controller); + g_assert_true (G_IS_DEBUG_CONTROLLER_DBUS (controller)); + + old_value = g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller)); + notify_id = g_signal_connect (controller, "notify::debug-enabled", G_CALLBACK (notify_debug_enabled_cb), ¬ify_count); + + properties_changed_id = g_dbus_connection_signal_subscribe (remote_connection, + g_dbus_connection_get_unique_name (controller_connection), + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + "/org/gtk/Debugging", + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + properties_changed_cb, + &properties_changed_count, + NULL); + + /* Get the debug status remotely. */ + g_dbus_connection_call (remote_connection, + g_dbus_connection_get_unique_name (controller_connection), + "/org/gtk/Debugging", + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", "org.gtk.Debugging", "DebugEnabled"), + G_VARIANT_TYPE ("(v)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + async_result_cb, + &result); + g_assert_no_error (local_error); + + while (result == NULL) + g_main_context_iteration (NULL, TRUE); + + reply = g_dbus_connection_call_finish (remote_connection, result, &local_error); + g_assert_no_error (local_error); + g_clear_object (&result); + + g_variant_get (reply, "(v)", &debug_enabled_variant); + debug_enabled = g_variant_get_boolean (debug_enabled_variant); + g_assert_true (debug_enabled == old_value); + g_assert_cmpuint (notify_count, ==, 0); + g_assert_cmpuint (properties_changed_count, ==, 0); + + g_clear_pointer (&debug_enabled_variant, g_variant_unref); + g_clear_pointer (&reply, g_variant_unref); + + /* Set the debug status remotely. The first attempt should fail due to no + * authorisation handler being connected. The second should fail due to the + * now-connected handler returning %FALSE. The third attempt should + * succeed. */ + g_dbus_connection_call (remote_connection, + g_dbus_connection_get_unique_name (controller_connection), + "/org/gtk/Debugging", + "org.gtk.Debugging", + "SetDebugEnabled", + g_variant_new ("(b)", !old_value), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + async_result_cb, + &result); + + while (result == NULL) + g_main_context_iteration (NULL, TRUE); + + reply = g_dbus_connection_call_finish (remote_connection, result, &local_error); + g_assert_error (local_error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED); + g_clear_object (&result); + g_clear_error (&local_error); + + g_assert_true (g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller)) == old_value); + g_assert_cmpuint (notify_count, ==, 0); + g_assert_cmpuint (properties_changed_count, ==, 0); + + g_clear_pointer (&debug_enabled_variant, g_variant_unref); + g_clear_pointer (&reply, g_variant_unref); + + /* Attach an authorisation handler and try again. */ + handler_id = g_signal_connect (controller, "authorize", G_CALLBACK (authorize_false_cb), NULL); + + g_dbus_connection_call (remote_connection, + g_dbus_connection_get_unique_name (controller_connection), + "/org/gtk/Debugging", + "org.gtk.Debugging", + "SetDebugEnabled", + g_variant_new ("(b)", !old_value), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + async_result_cb, + &result); + + while (result == NULL) + g_main_context_iteration (NULL, TRUE); + + reply = g_dbus_connection_call_finish (remote_connection, result, &local_error); + g_assert_error (local_error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED); + g_clear_object (&result); + g_clear_error (&local_error); + + g_assert_true (g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller)) == old_value); + g_assert_cmpuint (notify_count, ==, 0); + g_assert_cmpuint (properties_changed_count, ==, 0); + + g_clear_pointer (&debug_enabled_variant, g_variant_unref); + g_clear_pointer (&reply, g_variant_unref); + + g_signal_handler_disconnect (controller, handler_id); + handler_id = 0; + + /* Attach another signal handler which will grant access, and try again. */ + handler_id = g_signal_connect (controller, "authorize", G_CALLBACK (authorize_true_cb), NULL); + + g_dbus_connection_call (remote_connection, + g_dbus_connection_get_unique_name (controller_connection), + "/org/gtk/Debugging", + "org.gtk.Debugging", + "SetDebugEnabled", + g_variant_new ("(b)", !old_value), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + async_result_cb, + &result); + + while (result == NULL) + g_main_context_iteration (NULL, TRUE); + + reply = g_dbus_connection_call_finish (remote_connection, result, &local_error); + g_assert_no_error (local_error); + g_clear_object (&result); + + g_assert_true (g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller)) == !old_value); + g_assert_cmpuint (notify_count, ==, 1); + g_assert_cmpuint (properties_changed_count, ==, 1); + + g_clear_pointer (&debug_enabled_variant, g_variant_unref); + g_clear_pointer (&reply, g_variant_unref); + + g_signal_handler_disconnect (controller, handler_id); + handler_id = 0; + + /* Set the debug status locally. */ + g_debug_controller_set_debug_enabled (G_DEBUG_CONTROLLER (controller), old_value); + g_assert_true (g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller)) == old_value); + g_assert_cmpuint (notify_count, ==, 2); + + while (properties_changed_count != 2) + g_main_context_iteration (NULL, TRUE); + + g_assert_cmpuint (properties_changed_count, ==, 2); + + g_signal_handler_disconnect (controller, notify_id); + notify_id = 0; + + g_dbus_connection_signal_unsubscribe (remote_connection, properties_changed_id); + properties_changed_id = 0; + + g_debug_controller_dbus_stop (controller); + while (g_main_context_iteration (NULL, FALSE)); + g_assert_finalize_object (controller); + g_clear_object (&controller_connection); + g_clear_object (&remote_connection); + + g_test_dbus_down (bus); + g_clear_object (&bus); +} + +int +main (int argc, + char *argv[]) +{ + setlocale (LC_ALL, ""); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/debug-controller/dbus/basic", test_dbus_basic); + g_test_add_func ("/debug-controller/dbus/duplicate", test_dbus_duplicate); + g_test_add_func ("/debug-controller/dbus/properties", test_dbus_properties); + + return g_test_run (); +} diff --git a/gio/tests/meson.build b/gio/tests/meson.build index 017749c1c..43bc9d76f 100644 --- a/gio/tests/meson.build +++ b/gio/tests/meson.build @@ -57,6 +57,7 @@ gio_tests = { }, 'data-input-stream' : {}, 'data-output-stream' : {}, + 'debugcontroller' : {}, 'defaultvalue' : {'extra_sources' : [giotypefuncs_inc]}, 'fileattributematcher' : {}, 'filter-streams' : {},