/src/glib/gio/gdebugcontrollerdbus.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* GIO - GLib Input, Output and Streaming Library |
2 | | * |
3 | | * Copyright © 2021 Endless OS Foundation, LLC |
4 | | * |
5 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
6 | | * |
7 | | * This library is free software; you can redistribute it and/or |
8 | | * modify it under the terms of the GNU Lesser General Public |
9 | | * License as published by the Free Software Foundation; either |
10 | | * version 2.1 of the License, or (at your option) any later version. |
11 | | * |
12 | | * This library is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | | * Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General |
18 | | * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. |
19 | | * |
20 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
21 | | */ |
22 | | |
23 | | #include "config.h" |
24 | | |
25 | | #include <gio/gio.h> |
26 | | #include "gdebugcontroller.h" |
27 | | #include "gdebugcontrollerdbus.h" |
28 | | #include "giomodule-priv.h" |
29 | | #include "gi18n.h" |
30 | | #include "gio/gdbusprivate.h" |
31 | | #include "gio/gmarshal-internal.h" |
32 | | |
33 | | /** |
34 | | * SECTION:gdebugcontrollerdbus |
35 | | * @title: GDebugControllerDBus |
36 | | * @short_description: Debugging controller D-Bus implementation |
37 | | * @include: gio/gio.h |
38 | | * |
39 | | * #GDebugControllerDBus is an implementation of #GDebugController which exposes |
40 | | * debug settings as a D-Bus object. |
41 | | * |
42 | | * It is a #GInitable object, and will register an object at |
43 | | * `/org/gtk/Debugging` on the bus given as |
44 | | * #GDebugControllerDBus:connection once it’s initialized. The object will be |
45 | | * unregistered when the last reference to the #GDebugControllerDBus is dropped. |
46 | | * |
47 | | * This D-Bus object can be used by remote processes to enable or disable debug |
48 | | * output in this process. Remote processes calling |
49 | | * `org.gtk.Debugging.SetDebugEnabled()` will affect the value of |
50 | | * #GDebugController:debug-enabled and, by default, g_log_get_debug_enabled(). |
51 | | * default. |
52 | | * |
53 | | * By default, no processes are allowed to call `SetDebugEnabled()` unless a |
54 | | * #GDebugControllerDBus::authorize signal handler is installed. This is because |
55 | | * the process may be privileged, or might expose sensitive information in its |
56 | | * debug output. You may want to restrict the ability to enable debug output to |
57 | | * privileged users or processes. |
58 | | * |
59 | | * One option is to install a D-Bus security policy which restricts access to |
60 | | * `SetDebugEnabled()`, installing something like the following in |
61 | | * `$datadir/dbus-1/system.d/`: |
62 | | * |[<!-- language="XML" --> |
63 | | * <?xml version="1.0"?> <!--*-nxml-*--> |
64 | | * <!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" |
65 | | * "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> |
66 | | * <busconfig> |
67 | | * <policy user="root"> |
68 | | * <allow send_destination="com.example.MyService" send_interface="org.gtk.Debugging"/> |
69 | | * </policy> |
70 | | * <policy context="default"> |
71 | | * <deny send_destination="com.example.MyService" send_interface="org.gtk.Debugging"/> |
72 | | * </policy> |
73 | | * </busconfig> |
74 | | * ]| |
75 | | * |
76 | | * This will prevent the `SetDebugEnabled()` method from being called by all |
77 | | * except root. It will not prevent the `DebugEnabled` property from being read, |
78 | | * as it’s accessed through the `org.freedesktop.DBus.Properties` interface. |
79 | | * |
80 | | * Another option is to use polkit to allow or deny requests on a case-by-case |
81 | | * basis, allowing for the possibility of dynamic authorisation. To do this, |
82 | | * connect to the #GDebugControllerDBus::authorize signal and query polkit in |
83 | | * it: |
84 | | * |[<!-- language="C" --> |
85 | | * g_autoptr(GError) child_error = NULL; |
86 | | * g_autoptr(GDBusConnection) connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL); |
87 | | * gulong debug_controller_authorize_id = 0; |
88 | | * |
89 | | * // Set up the debug controller. |
90 | | * debug_controller = G_DEBUG_CONTROLLER (g_debug_controller_dbus_new (priv->connection, NULL, &child_error)); |
91 | | * if (debug_controller == NULL) |
92 | | * { |
93 | | * g_error ("Could not register debug controller on bus: %s"), |
94 | | * child_error->message); |
95 | | * } |
96 | | * |
97 | | * debug_controller_authorize_id = g_signal_connect (debug_controller, |
98 | | * "authorize", |
99 | | * G_CALLBACK (debug_controller_authorize_cb), |
100 | | * self); |
101 | | * |
102 | | * static gboolean |
103 | | * debug_controller_authorize_cb (GDebugControllerDBus *debug_controller, |
104 | | * GDBusMethodInvocation *invocation, |
105 | | * gpointer user_data) |
106 | | * { |
107 | | * g_autoptr(PolkitAuthority) authority = NULL; |
108 | | * g_autoptr(PolkitSubject) subject = NULL; |
109 | | * g_autoptr(PolkitAuthorizationResult) auth_result = NULL; |
110 | | * g_autoptr(GError) local_error = NULL; |
111 | | * GDBusMessage *message; |
112 | | * GDBusMessageFlags message_flags; |
113 | | * PolkitCheckAuthorizationFlags flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE; |
114 | | * |
115 | | * message = g_dbus_method_invocation_get_message (invocation); |
116 | | * message_flags = g_dbus_message_get_flags (message); |
117 | | * |
118 | | * authority = polkit_authority_get_sync (NULL, &local_error); |
119 | | * if (authority == NULL) |
120 | | * { |
121 | | * g_warning ("Failed to get polkit authority: %s", local_error->message); |
122 | | * return FALSE; |
123 | | * } |
124 | | * |
125 | | * if (message_flags & G_DBUS_MESSAGE_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION) |
126 | | * flags |= POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; |
127 | | * |
128 | | * subject = polkit_system_bus_name_new (g_dbus_method_invocation_get_sender (invocation)); |
129 | | * |
130 | | * auth_result = polkit_authority_check_authorization_sync (authority, |
131 | | * subject, |
132 | | * "com.example.MyService.set-debug-enabled", |
133 | | * NULL, |
134 | | * flags, |
135 | | * NULL, |
136 | | * &local_error); |
137 | | * if (auth_result == NULL) |
138 | | * { |
139 | | * g_warning ("Failed to get check polkit authorization: %s", local_error->message); |
140 | | * return FALSE; |
141 | | * } |
142 | | * |
143 | | * return polkit_authorization_result_get_is_authorized (auth_result); |
144 | | * } |
145 | | * ]| |
146 | | * |
147 | | * Since: 2.72 |
148 | | */ |
149 | | |
150 | | static const gchar org_gtk_Debugging_xml[] = |
151 | | "<node>" |
152 | | "<interface name='org.gtk.Debugging'>" |
153 | | "<property name='DebugEnabled' type='b' access='read'/>" |
154 | | "<method name='SetDebugEnabled'>" |
155 | | "<arg type='b' name='debug-enabled' direction='in'/>" |
156 | | "</method>" |
157 | | "</interface>" |
158 | | "</node>"; |
159 | | |
160 | | static GDBusInterfaceInfo *org_gtk_Debugging; |
161 | | |
162 | | #define G_DEBUG_CONTROLLER_DBUS_GET_INITABLE_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_INITABLE, GInitable)) |
163 | | |
164 | | static void g_debug_controller_dbus_iface_init (GDebugControllerInterface *iface); |
165 | | static void g_debug_controller_dbus_initable_iface_init (GInitableIface *iface); |
166 | | static gboolean g_debug_controller_dbus_authorize_default (GDebugControllerDBus *self, |
167 | | GDBusMethodInvocation *invocation); |
168 | | |
169 | | typedef enum |
170 | | { |
171 | | PROP_CONNECTION = 1, |
172 | | /* Overrides: */ |
173 | | PROP_DEBUG_ENABLED, |
174 | | } GDebugControllerDBusProperty; |
175 | | |
176 | | static GParamSpec *props[PROP_CONNECTION + 1] = { NULL, }; |
177 | | |
178 | | typedef enum |
179 | | { |
180 | | SIGNAL_AUTHORIZE, |
181 | | } GDebugControllerDBusSignal; |
182 | | |
183 | | static guint signals[SIGNAL_AUTHORIZE + 1] = {0}; |
184 | | |
185 | | typedef struct |
186 | | { |
187 | | GObject parent_instance; |
188 | | |
189 | | GCancellable *cancellable; /* (owned) */ |
190 | | GDBusConnection *connection; /* (owned) */ |
191 | | guint object_id; |
192 | | GPtrArray *pending_authorize_tasks; /* (element-type GWeakRef) (owned) (nullable) */ |
193 | | |
194 | | gboolean debug_enabled; |
195 | | } GDebugControllerDBusPrivate; |
196 | | |
197 | | G_DEFINE_TYPE_WITH_CODE (GDebugControllerDBus, g_debug_controller_dbus, G_TYPE_OBJECT, |
198 | | G_ADD_PRIVATE (GDebugControllerDBus) |
199 | | G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, |
200 | | g_debug_controller_dbus_initable_iface_init) |
201 | | G_IMPLEMENT_INTERFACE (G_TYPE_DEBUG_CONTROLLER, |
202 | | g_debug_controller_dbus_iface_init) |
203 | | _g_io_modules_ensure_extension_points_registered (); |
204 | | g_io_extension_point_implement (G_DEBUG_CONTROLLER_EXTENSION_POINT_NAME, |
205 | | g_define_type_id, |
206 | | "dbus", |
207 | | 30)) |
208 | | |
209 | | static void |
210 | | g_debug_controller_dbus_init (GDebugControllerDBus *self) |
211 | 0 | { |
212 | 0 | GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); |
213 | |
|
214 | 0 | priv->cancellable = g_cancellable_new (); |
215 | 0 | } |
216 | | |
217 | | static void |
218 | | set_debug_enabled (GDebugControllerDBus *self, |
219 | | gboolean debug_enabled) |
220 | 0 | { |
221 | 0 | GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); |
222 | |
|
223 | 0 | if (g_cancellable_is_cancelled (priv->cancellable)) |
224 | 0 | return; |
225 | | |
226 | 0 | if (debug_enabled != priv->debug_enabled) |
227 | 0 | { |
228 | 0 | GVariantBuilder builder; |
229 | |
|
230 | 0 | priv->debug_enabled = debug_enabled; |
231 | | |
232 | | /* Change the default log writer’s behaviour in GLib. */ |
233 | 0 | g_log_set_debug_enabled (debug_enabled); |
234 | | |
235 | | /* Notify internally and externally of the property change. */ |
236 | 0 | g_object_notify (G_OBJECT (self), "debug-enabled"); |
237 | |
|
238 | 0 | g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); |
239 | 0 | g_variant_builder_add (&builder, "{sv}", "DebugEnabled", g_variant_new_boolean (priv->debug_enabled)); |
240 | |
|
241 | 0 | g_dbus_connection_emit_signal (priv->connection, |
242 | 0 | NULL, |
243 | 0 | "/org/gtk/Debugging", |
244 | 0 | "org.freedesktop.DBus.Properties", |
245 | 0 | "PropertiesChanged", |
246 | 0 | g_variant_new ("(sa{sv}as)", |
247 | 0 | "org.gtk.Debugging", |
248 | 0 | &builder, |
249 | 0 | NULL), |
250 | 0 | NULL); |
251 | |
|
252 | 0 | g_debug ("Debug output %s", debug_enabled ? "enabled" : "disabled"); |
253 | 0 | } |
254 | 0 | } |
255 | | |
256 | | /* Called in the #GMainContext which was default when the #GDebugControllerDBus |
257 | | * was initialised. */ |
258 | | static GVariant * |
259 | | dbus_get_property (GDBusConnection *connection, |
260 | | const gchar *sender, |
261 | | const gchar *object_path, |
262 | | const gchar *interface_name, |
263 | | const gchar *property_name, |
264 | | GError **error, |
265 | | gpointer user_data) |
266 | 0 | { |
267 | 0 | GDebugControllerDBus *self = user_data; |
268 | 0 | GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); |
269 | |
|
270 | 0 | if (g_str_equal (property_name, "DebugEnabled")) |
271 | 0 | return g_variant_new_boolean (priv->debug_enabled); |
272 | | |
273 | 0 | g_assert_not_reached (); |
274 | | |
275 | 0 | return NULL; |
276 | 0 | } |
277 | | |
278 | | static GWeakRef * |
279 | | weak_ref_new (GObject *obj) |
280 | 0 | { |
281 | 0 | GWeakRef *weak_ref = g_new0 (GWeakRef, 1); |
282 | |
|
283 | 0 | g_weak_ref_init (weak_ref, obj); |
284 | |
|
285 | 0 | return g_steal_pointer (&weak_ref); |
286 | 0 | } |
287 | | |
288 | | static void |
289 | | weak_ref_free (GWeakRef *weak_ref) |
290 | 0 | { |
291 | 0 | g_weak_ref_clear (weak_ref); |
292 | 0 | g_free (weak_ref); |
293 | 0 | } |
294 | | |
295 | | /* Called in the #GMainContext which was default when the #GDebugControllerDBus |
296 | | * was initialised. */ |
297 | | static void |
298 | | garbage_collect_weak_refs (GDebugControllerDBus *self) |
299 | 0 | { |
300 | 0 | GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); |
301 | 0 | guint i; |
302 | |
|
303 | 0 | if (priv->pending_authorize_tasks == NULL) |
304 | 0 | return; |
305 | | |
306 | | /* Iterate in reverse order so that if we remove an element the hole won’t be |
307 | | * filled by an element we haven’t checked yet. */ |
308 | 0 | for (i = priv->pending_authorize_tasks->len; i > 0; i--) |
309 | 0 | { |
310 | 0 | GWeakRef *weak_ref = g_ptr_array_index (priv->pending_authorize_tasks, i - 1); |
311 | 0 | GObject *obj = g_weak_ref_get (weak_ref); |
312 | |
|
313 | 0 | if (obj == NULL) |
314 | 0 | g_ptr_array_remove_index_fast (priv->pending_authorize_tasks, i - 1); |
315 | 0 | else |
316 | 0 | g_object_unref (obj); |
317 | 0 | } |
318 | | |
319 | | /* Don’t need to keep the array around any more if it’s empty. */ |
320 | 0 | if (priv->pending_authorize_tasks->len == 0) |
321 | 0 | g_clear_pointer (&priv->pending_authorize_tasks, g_ptr_array_unref); |
322 | 0 | } |
323 | | |
324 | | /* Called in a worker thread. */ |
325 | | static void |
326 | | authorize_task_cb (GTask *task, |
327 | | gpointer source_object, |
328 | | gpointer task_data, |
329 | | GCancellable *cancellable) |
330 | 0 | { |
331 | 0 | GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (source_object); |
332 | 0 | GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (task_data); |
333 | 0 | gboolean authorized = TRUE; |
334 | |
|
335 | 0 | g_signal_emit (self, signals[SIGNAL_AUTHORIZE], 0, invocation, &authorized); |
336 | |
|
337 | 0 | g_task_return_boolean (task, authorized); |
338 | 0 | } |
339 | | |
340 | | /* Called in the #GMainContext which was default when the #GDebugControllerDBus |
341 | | * was initialised. */ |
342 | | static void |
343 | | authorize_cb (GObject *object, |
344 | | GAsyncResult *result, |
345 | | gpointer user_data) |
346 | 0 | { |
347 | 0 | GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (object); |
348 | 0 | GDebugControllerDBusPrivate *priv G_GNUC_UNUSED /* when compiling with G_DISABLE_ASSERT */; |
349 | 0 | GTask *task = G_TASK (result); |
350 | 0 | GDBusMethodInvocation *invocation = g_task_get_task_data (task); |
351 | 0 | GVariant *parameters = g_dbus_method_invocation_get_parameters (invocation); |
352 | 0 | gboolean enabled = FALSE; |
353 | 0 | gboolean authorized; |
354 | |
|
355 | 0 | priv = g_debug_controller_dbus_get_instance_private (self); |
356 | 0 | authorized = g_task_propagate_boolean (task, NULL); |
357 | |
|
358 | 0 | if (!authorized) |
359 | 0 | { |
360 | 0 | GError *local_error = g_error_new (G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, |
361 | 0 | _("Not authorized to change debug settings")); |
362 | 0 | g_dbus_method_invocation_take_error (invocation, g_steal_pointer (&local_error)); |
363 | 0 | } |
364 | 0 | else |
365 | 0 | { |
366 | | /* Update the property value. */ |
367 | 0 | g_variant_get (parameters, "(b)", &enabled); |
368 | 0 | set_debug_enabled (self, enabled); |
369 | |
|
370 | 0 | g_dbus_method_invocation_return_value (invocation, NULL); |
371 | 0 | } |
372 | | |
373 | | /* The GTask will stay alive for a bit longer as the worker thread is |
374 | | * potentially still in the process of dropping its reference to it. */ |
375 | 0 | g_assert (priv->pending_authorize_tasks != NULL && priv->pending_authorize_tasks->len > 0); |
376 | 0 | } |
377 | | |
378 | | /* Called in the #GMainContext which was default when the #GDebugControllerDBus |
379 | | * was initialised. */ |
380 | | static void |
381 | | dbus_method_call (GDBusConnection *connection, |
382 | | const gchar *sender, |
383 | | const gchar *object_path, |
384 | | const gchar *interface_name, |
385 | | const gchar *method_name, |
386 | | GVariant *parameters, |
387 | | GDBusMethodInvocation *invocation, |
388 | | gpointer user_data) |
389 | 0 | { |
390 | 0 | GDebugControllerDBus *self = user_data; |
391 | 0 | GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); |
392 | 0 | GDebugControllerDBusClass *klass = G_DEBUG_CONTROLLER_DBUS_GET_CLASS (self); |
393 | | |
394 | | /* Only on the org.gtk.Debugging interface */ |
395 | 0 | if (g_str_equal (method_name, "SetDebugEnabled")) |
396 | 0 | { |
397 | 0 | GTask *task = NULL; |
398 | |
|
399 | 0 | task = g_task_new (self, priv->cancellable, authorize_cb, NULL); |
400 | 0 | g_task_set_source_tag (task, dbus_method_call); |
401 | 0 | g_task_set_task_data (task, g_object_ref (invocation), (GDestroyNotify) g_object_unref); |
402 | | |
403 | | /* Track the pending #GTask with a weak ref as its final strong ref could |
404 | | * be dropped from this thread or an arbitrary #GTask worker thread. The |
405 | | * weak refs will be evaluated in g_debug_controller_dbus_stop(). */ |
406 | 0 | if (priv->pending_authorize_tasks == NULL) |
407 | 0 | priv->pending_authorize_tasks = g_ptr_array_new_with_free_func ((GDestroyNotify) weak_ref_free); |
408 | 0 | g_ptr_array_add (priv->pending_authorize_tasks, weak_ref_new (G_OBJECT (task))); |
409 | | |
410 | | /* Take the opportunity to clean up a bit. */ |
411 | 0 | garbage_collect_weak_refs (self); |
412 | | |
413 | | /* Check the calling peer is authorised to change the debug mode. So that |
414 | | * the signal handler can block on checking polkit authorisation (which |
415 | | * definitely involves D-Bus calls, and might involve user interaction), |
416 | | * emit the #GDebugControllerDBus::authorize signal in a worker thread, so |
417 | | * that handlers can synchronously block it. This is similar to how |
418 | | * #GDBusInterfaceSkeleton::g-authorize-method works. |
419 | | * |
420 | | * If no signal handlers are connected, don’t bother running the worker |
421 | | * thread, and just return a default value of %FALSE. Fail closed. */ |
422 | 0 | if (g_signal_has_handler_pending (self, signals[SIGNAL_AUTHORIZE], 0, FALSE) || |
423 | 0 | klass->authorize != g_debug_controller_dbus_authorize_default) |
424 | 0 | g_task_run_in_thread (task, authorize_task_cb); |
425 | 0 | else |
426 | 0 | g_task_return_boolean (task, FALSE); |
427 | |
|
428 | 0 | g_clear_object (&task); |
429 | 0 | } |
430 | 0 | else |
431 | 0 | g_assert_not_reached (); |
432 | 0 | } |
433 | | |
434 | | static gboolean |
435 | | g_debug_controller_dbus_initable_init (GInitable *initable, |
436 | | GCancellable *cancellable, |
437 | | GError **error) |
438 | 0 | { |
439 | 0 | GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (initable); |
440 | 0 | GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); |
441 | 0 | static const GDBusInterfaceVTable vtable = { |
442 | 0 | dbus_method_call, |
443 | 0 | dbus_get_property, |
444 | 0 | NULL /* set_property */, |
445 | 0 | { 0 } |
446 | 0 | }; |
447 | |
|
448 | 0 | if (org_gtk_Debugging == NULL) |
449 | 0 | { |
450 | 0 | GError *local_error = NULL; |
451 | 0 | GDBusNodeInfo *info; |
452 | |
|
453 | 0 | info = g_dbus_node_info_new_for_xml (org_gtk_Debugging_xml, &local_error); |
454 | 0 | if G_UNLIKELY (info == NULL) |
455 | 0 | g_error ("%s", local_error->message); |
456 | 0 | org_gtk_Debugging = g_dbus_node_info_lookup_interface (info, "org.gtk.Debugging"); |
457 | 0 | g_assert (org_gtk_Debugging != NULL); |
458 | 0 | g_dbus_interface_info_ref (org_gtk_Debugging); |
459 | 0 | g_dbus_node_info_unref (info); |
460 | 0 | } |
461 | | |
462 | 0 | priv->object_id = g_dbus_connection_register_object (priv->connection, |
463 | 0 | "/org/gtk/Debugging", |
464 | 0 | org_gtk_Debugging, |
465 | 0 | &vtable, self, NULL, error); |
466 | 0 | if (priv->object_id == 0) |
467 | 0 | return FALSE; |
468 | | |
469 | 0 | return TRUE; |
470 | 0 | } |
471 | | |
472 | | static void |
473 | | g_debug_controller_dbus_get_property (GObject *object, |
474 | | guint prop_id, |
475 | | GValue *value, |
476 | | GParamSpec *pspec) |
477 | 0 | { |
478 | 0 | GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (object); |
479 | 0 | GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); |
480 | |
|
481 | 0 | switch ((GDebugControllerDBusProperty) prop_id) |
482 | 0 | { |
483 | 0 | case PROP_CONNECTION: |
484 | 0 | g_value_set_object (value, priv->connection); |
485 | 0 | break; |
486 | 0 | case PROP_DEBUG_ENABLED: |
487 | 0 | g_value_set_boolean (value, priv->debug_enabled); |
488 | 0 | break; |
489 | 0 | default: |
490 | 0 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
491 | 0 | break; |
492 | 0 | } |
493 | 0 | } |
494 | | |
495 | | static void |
496 | | g_debug_controller_dbus_set_property (GObject *object, |
497 | | guint prop_id, |
498 | | const GValue *value, |
499 | | GParamSpec *pspec) |
500 | 0 | { |
501 | 0 | GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (object); |
502 | 0 | GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); |
503 | |
|
504 | 0 | switch ((GDebugControllerDBusProperty) prop_id) |
505 | 0 | { |
506 | 0 | case PROP_CONNECTION: |
507 | | /* Construct only */ |
508 | 0 | g_assert (priv->connection == NULL); |
509 | 0 | priv->connection = g_value_dup_object (value); |
510 | 0 | break; |
511 | 0 | case PROP_DEBUG_ENABLED: |
512 | 0 | set_debug_enabled (self, g_value_get_boolean (value)); |
513 | 0 | break; |
514 | 0 | default: |
515 | 0 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
516 | 0 | break; |
517 | 0 | } |
518 | 0 | } |
519 | | |
520 | | static void |
521 | | g_debug_controller_dbus_dispose (GObject *object) |
522 | 0 | { |
523 | 0 | GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (object); |
524 | 0 | GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); |
525 | |
|
526 | 0 | g_debug_controller_dbus_stop (self); |
527 | 0 | g_assert (priv->pending_authorize_tasks == NULL); |
528 | 0 | g_clear_object (&priv->connection); |
529 | 0 | g_clear_object (&priv->cancellable); |
530 | |
|
531 | 0 | G_OBJECT_CLASS (g_debug_controller_dbus_parent_class)->dispose (object); |
532 | 0 | } |
533 | | |
534 | | static gboolean |
535 | | g_debug_controller_dbus_authorize_default (GDebugControllerDBus *self, |
536 | | GDBusMethodInvocation *invocation) |
537 | 0 | { |
538 | 0 | return TRUE; |
539 | 0 | } |
540 | | |
541 | | static void |
542 | | g_debug_controller_dbus_class_init (GDebugControllerDBusClass *klass) |
543 | 0 | { |
544 | 0 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
545 | |
|
546 | 0 | gobject_class->get_property = g_debug_controller_dbus_get_property; |
547 | 0 | gobject_class->set_property = g_debug_controller_dbus_set_property; |
548 | 0 | gobject_class->dispose = g_debug_controller_dbus_dispose; |
549 | |
|
550 | 0 | klass->authorize = g_debug_controller_dbus_authorize_default; |
551 | | |
552 | | /** |
553 | | * GDebugControllerDBus:connection: |
554 | | * |
555 | | * The D-Bus connection to expose the debugging interface on. |
556 | | * |
557 | | * Typically this will be the same connection (to the system or session bus) |
558 | | * which the rest of the application or service’s D-Bus objects are registered |
559 | | * on. |
560 | | * |
561 | | * Since: 2.72 |
562 | | */ |
563 | 0 | props[PROP_CONNECTION] = |
564 | 0 | g_param_spec_object ("connection", "D-Bus Connection", |
565 | 0 | "The D-Bus connection to expose the debugging interface on.", |
566 | 0 | G_TYPE_DBUS_CONNECTION, |
567 | 0 | G_PARAM_READWRITE | |
568 | 0 | G_PARAM_CONSTRUCT_ONLY | |
569 | 0 | G_PARAM_STATIC_STRINGS); |
570 | |
|
571 | 0 | g_object_class_install_properties (gobject_class, G_N_ELEMENTS (props), props); |
572 | |
|
573 | 0 | g_object_class_override_property (gobject_class, PROP_DEBUG_ENABLED, "debug-enabled"); |
574 | | |
575 | | /** |
576 | | * GDebugControllerDBus::authorize: |
577 | | * @controller: The #GDebugControllerDBus emitting the signal. |
578 | | * @invocation: A #GDBusMethodInvocation. |
579 | | * |
580 | | * Emitted when a D-Bus peer is trying to change the debug settings and used |
581 | | * to determine if that is authorized. |
582 | | * |
583 | | * This signal is emitted in a dedicated worker thread, so handlers are |
584 | | * allowed to perform blocking I/O. This means that, for example, it is |
585 | | * appropriate to call `polkit_authority_check_authorization_sync()` to check |
586 | | * authorization using polkit. |
587 | | * |
588 | | * If %FALSE is returned then no further handlers are run and the request to |
589 | | * change the debug settings is rejected. |
590 | | * |
591 | | * Otherwise, if %TRUE is returned, signal emission continues. If no handlers |
592 | | * return %FALSE, then the debug settings are allowed to be changed. |
593 | | * |
594 | | * Signal handlers must not modify @invocation, or cause it to return a value. |
595 | | * |
596 | | * The default class handler just returns %TRUE. |
597 | | * |
598 | | * Returns: %TRUE if the call is authorized, %FALSE otherwise. |
599 | | * |
600 | | * Since: 2.72 |
601 | | */ |
602 | 0 | signals[SIGNAL_AUTHORIZE] = |
603 | 0 | g_signal_new ("authorize", |
604 | 0 | G_TYPE_DEBUG_CONTROLLER_DBUS, |
605 | 0 | G_SIGNAL_RUN_LAST, |
606 | 0 | G_STRUCT_OFFSET (GDebugControllerDBusClass, authorize), |
607 | 0 | _g_signal_accumulator_false_handled, |
608 | 0 | NULL, |
609 | 0 | _g_cclosure_marshal_BOOLEAN__OBJECT, |
610 | 0 | G_TYPE_BOOLEAN, |
611 | 0 | 1, |
612 | 0 | G_TYPE_DBUS_METHOD_INVOCATION); |
613 | 0 | g_signal_set_va_marshaller (signals[SIGNAL_AUTHORIZE], |
614 | 0 | G_TYPE_FROM_CLASS (klass), |
615 | 0 | _g_cclosure_marshal_BOOLEAN__OBJECTv); |
616 | 0 | } |
617 | | |
618 | | static void |
619 | | g_debug_controller_dbus_iface_init (GDebugControllerInterface *iface) |
620 | 0 | { |
621 | 0 | } |
622 | | |
623 | | static void |
624 | | g_debug_controller_dbus_initable_iface_init (GInitableIface *iface) |
625 | 0 | { |
626 | 0 | iface->init = g_debug_controller_dbus_initable_init; |
627 | 0 | } |
628 | | |
629 | | /** |
630 | | * g_debug_controller_dbus_new: |
631 | | * @connection: a #GDBusConnection to register the debug object on |
632 | | * @cancellable: (nullable): a #GCancellable, or %NULL |
633 | | * @error: return location for a #GError, or %NULL |
634 | | * |
635 | | * Create a new #GDebugControllerDBus and synchronously initialize it. |
636 | | * |
637 | | * Initializing the object will export the debug object on @connection. The |
638 | | * object will remain registered until the last reference to the |
639 | | * #GDebugControllerDBus is dropped. |
640 | | * |
641 | | * Initialization may fail if registering the object on @connection fails. |
642 | | * |
643 | | * Returns: (nullable) (transfer full): a new #GDebugControllerDBus, or %NULL |
644 | | * on failure |
645 | | * Since: 2.72 |
646 | | */ |
647 | | GDebugControllerDBus * |
648 | | g_debug_controller_dbus_new (GDBusConnection *connection, |
649 | | GCancellable *cancellable, |
650 | | GError **error) |
651 | 0 | { |
652 | 0 | g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); |
653 | 0 | g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); |
654 | 0 | g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
655 | | |
656 | 0 | return g_initable_new (G_TYPE_DEBUG_CONTROLLER_DBUS, |
657 | 0 | cancellable, |
658 | 0 | error, |
659 | 0 | "connection", connection, |
660 | 0 | NULL); |
661 | 0 | } |
662 | | |
663 | | /** |
664 | | * g_debug_controller_dbus_stop: |
665 | | * @self: a #GDebugControllerDBus |
666 | | * |
667 | | * Stop the debug controller, unregistering its object from the bus. |
668 | | * |
669 | | * Any pending method calls to the object will complete successfully, but new |
670 | | * ones will return an error. This method will block until all pending |
671 | | * #GDebugControllerDBus::authorize signals have been handled. This is expected |
672 | | * to not take long, as it will just be waiting for threads to join. If any |
673 | | * #GDebugControllerDBus::authorize signal handlers are still executing in other |
674 | | * threads, this will block until after they have returned. |
675 | | * |
676 | | * This method will be called automatically when the final reference to the |
677 | | * #GDebugControllerDBus is dropped. You may want to call it explicitly to know |
678 | | * when the controller has been fully removed from the bus, or to break |
679 | | * reference count cycles. |
680 | | * |
681 | | * Calling this method from within a #GDebugControllerDBus::authorize signal |
682 | | * handler will cause a deadlock and must not be done. |
683 | | * |
684 | | * Since: 2.72 |
685 | | */ |
686 | | void |
687 | | g_debug_controller_dbus_stop (GDebugControllerDBus *self) |
688 | 0 | { |
689 | 0 | GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); |
690 | |
|
691 | 0 | g_cancellable_cancel (priv->cancellable); |
692 | |
|
693 | 0 | if (priv->object_id != 0) |
694 | 0 | { |
695 | 0 | g_dbus_connection_unregister_object (priv->connection, priv->object_id); |
696 | 0 | priv->object_id = 0; |
697 | 0 | } |
698 | | |
699 | | /* Wait for any pending authorize tasks to finish. These will just be waiting |
700 | | * for threads to join at this point, as the D-Bus object has been |
701 | | * unregistered and the cancellable cancelled. |
702 | | * |
703 | | * The loop will never terminate if g_debug_controller_dbus_stop() is |
704 | | * called from within an ::authorize callback. |
705 | | * |
706 | | * See discussion in https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2486 */ |
707 | 0 | while (priv->pending_authorize_tasks != NULL) |
708 | 0 | { |
709 | 0 | garbage_collect_weak_refs (self); |
710 | 0 | g_thread_yield (); |
711 | 0 | } |
712 | 0 | } |