mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-11-04 10:08:56 +01:00 
			
		
		
		
	Merge branch '1190-debugging-docs' into 'main'
gdebugcontroller: Add documentation and tests Closes #1190 See merge request GNOME/glib!2486
This commit is contained in:
		@@ -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
 | 
			
		||||
<SUBSECTION Standard>
 | 
			
		||||
@@ -4231,6 +4230,7 @@ G_DEBUG_CONTROLLER_GET_INTERFACE
 | 
			
		||||
<TITLE>GDebugControllerDBus</TITLE>
 | 
			
		||||
GDebugControllerDBus
 | 
			
		||||
g_debug_controller_dbus_new
 | 
			
		||||
g_debug_controller_dbus_stop
 | 
			
		||||
<SUBSECTION Standard>
 | 
			
		||||
g_debug_controller_dbus_get_type
 | 
			
		||||
G_TYPE_DEBUG_CONTROLLER_DBUS
 | 
			
		||||
 
 | 
			
		||||
@@ -44,29 +44,16 @@
 | 
			
		||||
 * 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
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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/`:
 | 
			
		||||
 * |[<!-- language="XML" -->
 | 
			
		||||
 * <?xml version="1.0"?> <!--*-nxml-*-->
 | 
			
		||||
 * <!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 | 
			
		||||
 *      "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
 | 
			
		||||
 * <busconfig>
 | 
			
		||||
 *   <policy user="root">
 | 
			
		||||
 *     <allow send_destination="com.example.MyService" send_interface="org.gtk.Debugging"/>
 | 
			
		||||
 *   </policy>
 | 
			
		||||
 *   <policy context="default">
 | 
			
		||||
 *     <deny send_destination="com.example.MyService" send_interface="org.gtk.Debugging"/>
 | 
			
		||||
 *   </policy>
 | 
			
		||||
 * </busconfig>
 | 
			
		||||
 * ]|
 | 
			
		||||
 *
 | 
			
		||||
 * 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:
 | 
			
		||||
 * |[<!-- language="C" -->
 | 
			
		||||
 *   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
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@@ -84,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;
 | 
			
		||||
@@ -103,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
 | 
			
		||||
@@ -113,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;
 | 
			
		||||
@@ -131,7 +238,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",
 | 
			
		||||
@@ -189,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);
 | 
			
		||||
@@ -202,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
 | 
			
		||||
@@ -225,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 */
 | 
			
		||||
@@ -232,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),
 | 
			
		||||
@@ -349,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);
 | 
			
		||||
}
 | 
			
		||||
@@ -488,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 ();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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__ */
 | 
			
		||||
 
 | 
			
		||||
@@ -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 ());
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										396
									
								
								gio/tests/debugcontroller.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										396
									
								
								gio/tests/debugcontroller.c
									
									
									
									
									
										Normal file
									
								
							@@ -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 <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: LGPL-2.1-or-later
 | 
			
		||||
 * Author: Philip Withnall <pwithnall@endlessos.org>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <gio/gio.h>
 | 
			
		||||
#include <locale.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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 ();
 | 
			
		||||
}
 | 
			
		||||
@@ -57,6 +57,7 @@ gio_tests = {
 | 
			
		||||
  },
 | 
			
		||||
  'data-input-stream' : {},
 | 
			
		||||
  'data-output-stream' : {},
 | 
			
		||||
  'debugcontroller' : {},
 | 
			
		||||
  'defaultvalue' : {'extra_sources' : [giotypefuncs_inc]},
 | 
			
		||||
  'fileattributematcher' : {},
 | 
			
		||||
  'filter-streams' : {},
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user