Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright 2025 GNOME Foundation, Inc.
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 Public
18 : : * License along with this library; if not, write to the Free Software
19 : : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 : : *
21 : : * Authors:
22 : : * - Philip Withnall <pwithnall@gnome.org>
23 : : */
24 : :
25 : : #include "config.h"
26 : :
27 : : #include <glib.h>
28 : : #include <glib/gi18n-lib.h>
29 : : #include <gio/gio.h>
30 : : #include <libmalcontent-timer/extension-agent-object.h>
31 : : #include <libmalcontent-timer/timer-store.h>
32 : :
33 : : #include "extension-agent-iface.h"
34 : : #include "extension-agent-request-iface.h"
35 : : #include "enums.h"
36 : : #include "operation-counter-private.h"
37 : : #include "properties-iface.h"
38 : :
39 : :
40 : : static void mct_extension_agent_object_constructed (GObject *object);
41 : : static void mct_extension_agent_object_dispose (GObject *object);
42 : : static void mct_extension_agent_object_get_property (GObject *object,
43 : : unsigned int property_id,
44 : : GValue *value,
45 : : GParamSpec *pspec);
46 : : static void mct_extension_agent_object_set_property (GObject *object,
47 : : unsigned int property_id,
48 : : const GValue *value,
49 : : GParamSpec *pspec);
50 : :
51 : : static char **mct_extension_agent_object_request_enumerate (GDBusConnection *connection,
52 : : const char *sender,
53 : : const char *object_path,
54 : : void *user_data);
55 : : static GDBusInterfaceInfo **mct_extension_agent_object_request_introspect (GDBusConnection *connection,
56 : : const char *sender,
57 : : const char *object_path,
58 : : const char *node,
59 : : void *user_data);
60 : : static const GDBusInterfaceVTable *mct_extension_agent_object_request_dispatch (GDBusConnection *connection,
61 : : const char *sender,
62 : : const char *object_path,
63 : : const char *interface_name,
64 : : const char *node,
65 : : void **out_user_data,
66 : : void *user_data);
67 : :
68 : : static void mct_extension_agent_object_method_call (GDBusConnection *connection,
69 : : const char *sender,
70 : : const char *object_path,
71 : : const char *interface_name,
72 : : const char *method_name,
73 : : GVariant *parameters,
74 : : GDBusMethodInvocation *invocation,
75 : : void *user_data);
76 : : static void mct_extension_agent_object_properties_get (MctExtensionAgentObject *self,
77 : : GDBusConnection *connection,
78 : : const char *sender,
79 : : GVariant *parameters,
80 : : GDBusMethodInvocation *invocation);
81 : : static void mct_extension_agent_object_properties_set (MctExtensionAgentObject *self,
82 : : GDBusConnection *connection,
83 : : const char *sender,
84 : : GVariant *parameters,
85 : : GDBusMethodInvocation *invocation);
86 : : static void mct_extension_agent_object_properties_get_all (MctExtensionAgentObject *self,
87 : : GDBusConnection *connection,
88 : : const char *sender,
89 : : GVariant *parameters,
90 : : GDBusMethodInvocation *invocation);
91 : :
92 : : static void mct_extension_agent_object_request_extension (MctExtensionAgentObject *self,
93 : : GDBusConnection *connection,
94 : : const char *sender,
95 : : GVariant *parameters,
96 : : GDBusMethodInvocation *invocation);
97 : :
98 : : /* These errors do go over the bus, and are registered in mct_extension_agent_object_class_init(). */
99 [ + + ]: 40 : G_DEFINE_QUARK (MctExtensionAgentObjectError, mct_extension_agent_object_error)
100 : :
101 : : static const char *extension_agent_object_errors[] =
102 : : {
103 : : "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.Failed",
104 : : "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.IdentifyingUser",
105 : : "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.Cancelled",
106 : : "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.InvalidQuery",
107 : : };
108 : : static const GDBusErrorEntry extension_agent_object_error_map[] =
109 : : {
110 : : { MCT_EXTENSION_AGENT_OBJECT_ERROR_FAILED, "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.Failed" },
111 : : { MCT_EXTENSION_AGENT_OBJECT_ERROR_IDENTIFYING_USER, "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.IdentifyingUser" },
112 : : { MCT_EXTENSION_AGENT_OBJECT_ERROR_CANCELLED, "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.Cancelled" },
113 : : { MCT_EXTENSION_AGENT_OBJECT_ERROR_INVALID_QUERY, "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.InvalidQuery" },
114 : : };
115 : : G_STATIC_ASSERT (G_N_ELEMENTS (extension_agent_object_error_map) == MCT_EXTENSION_AGENT_OBJECT_N_ERRORS);
116 : : G_STATIC_ASSERT (G_N_ELEMENTS (extension_agent_object_error_map) == G_N_ELEMENTS (extension_agent_object_errors));
117 : :
118 : : /* Small struct which tracks a pending request, and which maps directly to a
119 : : * /Request D-Bus object on the bus. While this struct exists (and is registered
120 : : * in MctExtensionAgentObject.requests) it’s exported on the bus. */
121 : : typedef struct
122 : : {
123 : : char *object_path; /* full path of this Request object on the bus */
124 : : const char *object_subpath; /* subpath of the Request object, i.e. `Request123` */
125 : : char *owner; /* unique bus name of peer who originally called RequestExtension() */
126 : : GCancellable *cancellable; /* (owned) */
127 : : unsigned int owner_name_watch_id;
128 : : } MctExtensionAgentRequest;
129 : :
130 : : static void
131 : 27 : mct_extension_agent_request_free (MctExtensionAgentRequest *request)
132 : : {
133 : 27 : g_free (request->object_path);
134 : 27 : g_free (request->owner);
135 [ + - ]: 27 : g_clear_object (&request->cancellable);
136 [ + - ]: 27 : if (request->owner_name_watch_id != 0)
137 : 27 : g_bus_unwatch_name (request->owner_name_watch_id);
138 : 27 : request->owner_name_watch_id = 0;
139 : 27 : g_free (request);
140 : 27 : }
141 : :
142 [ - + ]: 72 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (MctExtensionAgentRequest, mct_extension_agent_request_free)
143 : :
144 : : static void mct_extension_agent_request_method_call (GDBusConnection *connection,
145 : : const char *sender,
146 : : const char *object_path,
147 : : const char *interface_name,
148 : : const char *method_name,
149 : : GVariant *parameters,
150 : : GDBusMethodInvocation *invocation,
151 : : void *user_data);
152 : : static void mct_extension_agent_request_properties_get (MctExtensionAgentObject *self,
153 : : MctExtensionAgentRequest *request,
154 : : GDBusConnection *connection,
155 : : const char *sender,
156 : : GVariant *parameters,
157 : : GDBusMethodInvocation *invocation);
158 : : static void mct_extension_agent_request_properties_set (MctExtensionAgentObject *self,
159 : : MctExtensionAgentRequest *request,
160 : : GDBusConnection *connection,
161 : : const char *sender,
162 : : GVariant *parameters,
163 : : GDBusMethodInvocation *invocation);
164 : : static void mct_extension_agent_request_properties_get_all (MctExtensionAgentObject *self,
165 : : MctExtensionAgentRequest *request,
166 : : GDBusConnection *connection,
167 : : const char *sender,
168 : : GVariant *parameters,
169 : : GDBusMethodInvocation *invocation);
170 : : static void mct_extension_agent_request_close (MctExtensionAgentObject *self,
171 : : MctExtensionAgentRequest *request,
172 : : GDBusConnection *connection,
173 : : const char *sender,
174 : : GVariant *parameters,
175 : : GDBusMethodInvocation *invocation);
176 : :
177 : : /**
178 : : * MctExtensionAgentObject:
179 : : *
180 : : * An implementation of the `org.freedesktop.MalcontentTimer1.ExtensionAgent`
181 : : * D-Bus interface, allowing a trusted component on the system (the
182 : : * `malcontent-timerd` daemon) to ask for a decision about whether to allow a
183 : : * screen time limit extension request from a child user.
184 : : *
185 : : * This will expose all the necessary objects on the bus for `malcontent-timerd`
186 : : * to interact with them, but delegates the logic of whether to allow each
187 : : * extension request to the
188 : : * [vfunc@Malcontent.ExtensionAgentObject.request_extension_async] virtual
189 : : * method, which must be implemented by a subclass. This allows for different
190 : : * implementations of the agent to share the common D-Bus framework but use
191 : : * different UIs for asking the parent whether to allow the extension.
192 : : *
193 : : * Since: 0.14.0
194 : : */
195 : : typedef struct
196 : : {
197 : : GDBusConnection *connection; /* (owned) */
198 : : char *object_path; /* (owned) */
199 : : unsigned int request_subtree_id;
200 : :
201 : : /* Used to cancel any pending operations when the object is unregistered. */
202 : : GCancellable *cancellable; /* (owned) */
203 : : unsigned long cancelled_id;
204 : :
205 : : unsigned int n_pending_operations;
206 : :
207 : : unsigned int request_counter;
208 : : /* Map from D-Bus object path to MctExtensionAgentRequest struct: */
209 : : GHashTable *requests; /* (element-type utf8 MctExtensionAgentRequest) (owned) */
210 : : } MctExtensionAgentObjectPrivate;
211 : :
212 : : typedef enum
213 : : {
214 : : PROP_CONNECTION = 1,
215 : : PROP_OBJECT_PATH,
216 : : PROP_BUSY,
217 : : } MctExtensionAgentObjectProperty;
218 : :
219 : : static GParamSpec *props[PROP_BUSY + 1] = { NULL, };
220 : :
221 [ + + + - : 1611 : G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (MctExtensionAgentObject, mct_extension_agent_object, G_TYPE_OBJECT)
+ + ]
222 : :
223 : : static void
224 : 2 : mct_extension_agent_object_class_init (MctExtensionAgentObjectClass *klass)
225 : : {
226 : 2 : GObjectClass *object_class = (GObjectClass *) klass;
227 : :
228 : 2 : object_class->constructed = mct_extension_agent_object_constructed;
229 : 2 : object_class->dispose = mct_extension_agent_object_dispose;
230 : 2 : object_class->get_property = mct_extension_agent_object_get_property;
231 : 2 : object_class->set_property = mct_extension_agent_object_set_property;
232 : :
233 : : /**
234 : : * MctExtensionAgentObject:connection:
235 : : *
236 : : * D-Bus connection to export objects on.
237 : : *
238 : : * Since: 0.14.0
239 : : */
240 : 2 : props[PROP_CONNECTION] =
241 : 2 : g_param_spec_object ("connection", NULL, NULL,
242 : : G_TYPE_DBUS_CONNECTION,
243 : : G_PARAM_READWRITE |
244 : : G_PARAM_CONSTRUCT_ONLY |
245 : : G_PARAM_STATIC_STRINGS);
246 : :
247 : : /**
248 : : * MctExtensionAgentObject:object-path:
249 : : *
250 : : * Object path to root all exported objects at. If this does not end in a
251 : : * slash, one will be added.
252 : : *
253 : : * Since: 0.14.0
254 : : */
255 : 2 : props[PROP_OBJECT_PATH] =
256 : 2 : g_param_spec_string ("object-path", NULL, NULL,
257 : : "/",
258 : : G_PARAM_READWRITE |
259 : : G_PARAM_CONSTRUCT_ONLY |
260 : : G_PARAM_STATIC_STRINGS);
261 : :
262 : : /**
263 : : * MctExtensionAgentObject:busy:
264 : : *
265 : : * True if the D-Bus API is busy.
266 : : *
267 : : * For example, if there are any outstanding method calls which haven’t been
268 : : * replied to yet.
269 : : *
270 : : * Since: 0.14.0
271 : : */
272 : 2 : props[PROP_BUSY] =
273 : 2 : g_param_spec_boolean ("busy", NULL, NULL,
274 : : FALSE,
275 : : G_PARAM_READABLE |
276 : : G_PARAM_STATIC_STRINGS);
277 : :
278 : 2 : g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
279 : :
280 : : /* Error domain registration for D-Bus. We do this here, rather than in a
281 : : * #GOnce section in mct_extension_agent_object_error_quark(), to avoid spreading the
282 : : * D-Bus code outside this file. */
283 [ + + ]: 10 : for (size_t i = 0; i < G_N_ELEMENTS (extension_agent_object_error_map); i++)
284 : 8 : g_dbus_error_register_error (MCT_EXTENSION_AGENT_OBJECT_ERROR,
285 : 8 : extension_agent_object_error_map[i].error_code,
286 : 8 : extension_agent_object_error_map[i].dbus_error_name);
287 : 2 : }
288 : :
289 : : static void cancelled_cb (GCancellable *cancellable,
290 : : void *user_data);
291 : :
292 : : static void
293 : 36 : mct_extension_agent_object_init (MctExtensionAgentObject *self)
294 : : {
295 : 36 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
296 : :
297 : 36 : priv->cancellable = g_cancellable_new ();
298 : 36 : priv->cancelled_id = g_cancellable_connect (priv->cancellable,
299 : : G_CALLBACK (cancelled_cb), self, NULL);
300 : 36 : priv->requests = g_hash_table_new_full (g_str_hash, g_str_equal,
301 : : NULL, (GDestroyNotify) mct_extension_agent_request_free);
302 : 36 : }
303 : :
304 : : static void
305 : 36 : cancelled_cb (GCancellable *cancellable,
306 : : void *user_data)
307 : : {
308 : 36 : MctExtensionAgentObject *self = MCT_EXTENSION_AGENT_OBJECT (user_data);
309 : 36 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
310 : : GHashTableIter iter;
311 : : void *value;
312 : :
313 : : /* Propagate cancellation to all our pending requests. */
314 : 36 : g_hash_table_iter_init (&iter, priv->requests);
315 [ - + ]: 36 : while (g_hash_table_iter_next (&iter, NULL, &value))
316 : : {
317 : 0 : MctExtensionAgentRequest *request = value;
318 : :
319 : 0 : g_cancellable_cancel (request->cancellable);
320 : : }
321 : 36 : }
322 : :
323 : : static void
324 : 36 : mct_extension_agent_object_constructed (GObject *object)
325 : : {
326 : 36 : MctExtensionAgentObject *self = MCT_EXTENSION_AGENT_OBJECT (object);
327 : 36 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
328 : :
329 : : /* Chain up. */
330 : 36 : G_OBJECT_CLASS (mct_extension_agent_object_parent_class)->constructed (object);
331 : :
332 : : /* Check our construct properties. */
333 : 36 : g_assert (G_IS_DBUS_CONNECTION (priv->connection));
334 : 36 : g_assert (g_variant_is_object_path (priv->object_path));
335 : 36 : }
336 : :
337 : : static void
338 : 34 : mct_extension_agent_object_dispose (GObject *object)
339 : : {
340 : 34 : MctExtensionAgentObject *self = MCT_EXTENSION_AGENT_OBJECT (object);
341 : 34 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
342 : :
343 : 34 : g_assert (priv->request_subtree_id == 0);
344 : 34 : g_assert (priv->n_pending_operations == 0);
345 : 34 : g_assert (g_hash_table_size (priv->requests) == 0);
346 : :
347 [ + - ]: 34 : g_clear_pointer (&priv->requests, g_hash_table_unref);
348 : :
349 [ + - ]: 34 : g_clear_object (&priv->connection);
350 [ + - ]: 34 : g_clear_pointer (&priv->object_path, g_free);
351 : :
352 [ + - ]: 34 : if (priv->cancelled_id != 0)
353 : : {
354 : 34 : g_cancellable_disconnect (priv->cancellable, priv->cancelled_id);
355 : 34 : priv->cancelled_id = 0;
356 : : }
357 [ + - ]: 34 : g_clear_object (&priv->cancellable);
358 : :
359 : : /* Chain up to the parent class */
360 : 34 : G_OBJECT_CLASS (mct_extension_agent_object_parent_class)->dispose (object);
361 : 34 : }
362 : :
363 : : static void
364 : 3 : mct_extension_agent_object_get_property (GObject *object,
365 : : unsigned int property_id,
366 : : GValue *value,
367 : : GParamSpec *pspec)
368 : : {
369 : 3 : MctExtensionAgentObject *self = MCT_EXTENSION_AGENT_OBJECT (object);
370 : 3 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
371 : :
372 [ + + + - ]: 3 : switch ((MctExtensionAgentObjectProperty) property_id)
373 : : {
374 : 1 : case PROP_CONNECTION:
375 : 1 : g_value_set_object (value, priv->connection);
376 : 1 : break;
377 : 1 : case PROP_OBJECT_PATH:
378 : 1 : g_value_set_string (value, priv->object_path);
379 : 1 : break;
380 : 1 : case PROP_BUSY:
381 : 1 : g_value_set_boolean (value, mct_extension_agent_object_get_busy (self));
382 : 1 : break;
383 : 0 : default:
384 : : g_assert_not_reached ();
385 : : }
386 : 3 : }
387 : :
388 : : static void
389 : 72 : mct_extension_agent_object_set_property (GObject *object,
390 : : unsigned int property_id,
391 : : const GValue *value,
392 : : GParamSpec *pspec)
393 : : {
394 : 72 : MctExtensionAgentObject *self = MCT_EXTENSION_AGENT_OBJECT (object);
395 : 72 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
396 : :
397 [ + + - ]: 72 : switch ((MctExtensionAgentObjectProperty) property_id)
398 : : {
399 : 36 : case PROP_CONNECTION:
400 : : /* Construct only. */
401 : 36 : g_assert (priv->connection == NULL);
402 : 36 : priv->connection = g_value_dup_object (value);
403 : 36 : break;
404 : 36 : case PROP_OBJECT_PATH:
405 : : /* Construct only. */
406 : 36 : g_assert (priv->object_path == NULL);
407 : 36 : g_assert (g_variant_is_object_path (g_value_get_string (value)));
408 : 36 : priv->object_path = g_value_dup_string (value);
409 : 36 : break;
410 : 0 : case PROP_BUSY:
411 : : /* Read only. Fall through. */
412 : : G_GNUC_FALLTHROUGH;
413 : : default:
414 : : g_assert_not_reached ();
415 : : }
416 : 72 : }
417 : :
418 : : /**
419 : : * mct_extension_agent_object_register:
420 : : * @self: an extension agent object
421 : : * @error: return location for a [type@GLib.Error]
422 : : *
423 : : * Register the agent service objects on D-Bus using the connection details
424 : : * given in [property@Malcontent.ExtensionAgentObject.connection] and
425 : : * [property@Malcontent.ExtensionAgentObject.object-path].
426 : : *
427 : : * Use [method@Malcontent.ExtensionAgentObject.unregister] to unregister them.
428 : : * Calls to these two functions must be well paired.
429 : : *
430 : : * Returns: true on success, false otherwise
431 : : * Since: 0.14.0
432 : : */
433 : : gboolean
434 : 36 : mct_extension_agent_object_register (MctExtensionAgentObject *self,
435 : : GError **error)
436 : : {
437 : 36 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
438 : :
439 : 36 : g_return_val_if_fail (MCT_IS_EXTENSION_AGENT_OBJECT (self), FALSE);
440 : 36 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
441 : :
442 : 36 : const GDBusSubtreeVTable subtree_vtable =
443 : : {
444 : : mct_extension_agent_object_request_enumerate,
445 : : mct_extension_agent_object_request_introspect,
446 : : mct_extension_agent_object_request_dispatch,
447 : : { NULL, } /* padding */
448 : : };
449 : :
450 : 36 : unsigned int id = g_dbus_connection_register_subtree (priv->connection,
451 : 36 : priv->object_path,
452 : : &subtree_vtable,
453 : : G_DBUS_SUBTREE_FLAGS_NONE,
454 : : g_object_ref (self),
455 : : g_object_unref,
456 : : error);
457 : :
458 [ - + ]: 36 : if (id == 0)
459 : 0 : return FALSE;
460 : :
461 : 36 : priv->request_subtree_id = id;
462 : :
463 : : /* This has potentially changed. */
464 : 36 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUSY]);
465 : :
466 : 36 : return TRUE;
467 : : }
468 : :
469 : : /**
470 : : * mct_extension_agent_object_unregister:
471 : : * @self: an extension agent object
472 : : *
473 : : * Unregister objects from D-Bus which were previously registered using
474 : : * [method@Malcontent.ExtensionAgentObject.register].
475 : : *
476 : : * Calls to these two functions must be well paired.
477 : : *
478 : : * Since: 0.14.0
479 : : */
480 : : void
481 : 36 : mct_extension_agent_object_unregister (MctExtensionAgentObject *self)
482 : : {
483 : 36 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
484 : :
485 : 36 : g_return_if_fail (MCT_IS_EXTENSION_AGENT_OBJECT (self));
486 : :
487 : 36 : g_cancellable_cancel (priv->cancellable);
488 : :
489 : 36 : g_dbus_connection_unregister_subtree (priv->connection, priv->request_subtree_id);
490 : 36 : priv->request_subtree_id = 0;
491 : :
492 : : /* This has potentially changed. */
493 : 36 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUSY]);
494 : : }
495 : :
496 : : static char **
497 : 88 : mct_extension_agent_object_request_enumerate (GDBusConnection *connection,
498 : : const char *sender,
499 : : const char *object_path,
500 : : void *user_data)
501 : : {
502 : 88 : MctExtensionAgentObject *self = MCT_EXTENSION_AGENT_OBJECT (user_data);
503 : 88 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
504 : 88 : g_autoptr(GPtrArray) paths = NULL; /* (element-type utf8) */
505 : : GHashTableIter iter;
506 : : void *key;
507 : :
508 : : /* Don’t implement any permissions checks here, as they should be specific to
509 : : * the APIs being called and objects being accessed. Just output a list of
510 : : * paths to request objects. */
511 : 88 : paths = g_ptr_array_new_null_terminated (g_hash_table_size (priv->requests), g_free, TRUE);
512 : :
513 : 88 : g_hash_table_iter_init (&iter, priv->requests);
514 [ + + ]: 158 : while (g_hash_table_iter_next (&iter, &key, NULL))
515 : : {
516 : 70 : const char *request_object_subpath = key;
517 : 70 : g_ptr_array_add (paths, g_strdup (request_object_subpath));
518 : : }
519 : :
520 : 88 : return (char **) g_ptr_array_free (g_steal_pointer (&paths), FALSE);
521 : : }
522 : :
523 : : /* @object_subpath should be something like `Request1` or `Request513` */
524 : : static MctExtensionAgentRequest *
525 : 137 : object_subpath_to_request (MctExtensionAgentObject *self,
526 : : const char *object_subpath)
527 : : {
528 : 137 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
529 : :
530 : 137 : g_return_val_if_fail (object_subpath != NULL, NULL);
531 : :
532 : 137 : return g_hash_table_lookup (priv->requests, object_subpath);
533 : : }
534 : :
535 : : static GDBusInterfaceInfo **
536 : 106 : mct_extension_agent_object_request_introspect (GDBusConnection *connection,
537 : : const char *sender,
538 : : const char *object_path,
539 : : const char *node,
540 : : void *user_data)
541 : : {
542 : 106 : MctExtensionAgentObject *self = MCT_EXTENSION_AGENT_OBJECT (user_data);
543 : 106 : g_autofree GDBusInterfaceInfo **interfaces = NULL;
544 : :
545 : : /* Don’t implement any permissions checks here, as they should be specific to
546 : : * the APIs being called and objects being accessed. */
547 : :
548 [ + + ]: 106 : if (node == NULL)
549 : : {
550 : : /* The root node implements ExtensionAgent only. */
551 : 47 : interfaces = g_new0 (GDBusInterfaceInfo *, 3);
552 : 47 : interfaces[0] = (GDBusInterfaceInfo *) &org_freedesktop_malcontent_timer1_extension_agent_interface;
553 : 47 : interfaces[1] = (GDBusInterfaceInfo *) &org_freedesktop_dbus_properties_interface;
554 : 47 : interfaces[2] = NULL;
555 : : }
556 [ + - ]: 59 : else if (object_subpath_to_request (self, node) != NULL)
557 : : {
558 : : /* Request objects */
559 : 59 : interfaces = g_new0 (GDBusInterfaceInfo *, 3);
560 : 59 : interfaces[0] = (GDBusInterfaceInfo *) &org_freedesktop_malcontent_timer1_extension_agent_request_interface;
561 : 59 : interfaces[1] = (GDBusInterfaceInfo *) &org_freedesktop_dbus_properties_interface;
562 : 59 : interfaces[2] = NULL;
563 : : }
564 : :
565 : 106 : return g_steal_pointer (&interfaces);
566 : : }
567 : :
568 : : static const GDBusInterfaceVTable *
569 : 86 : mct_extension_agent_object_request_dispatch (GDBusConnection *connection,
570 : : const char *sender,
571 : : const char *object_path,
572 : : const char *interface_name,
573 : : const char *node,
574 : : void **out_user_data,
575 : : void *user_data)
576 : : {
577 : 86 : MctExtensionAgentObject *self = MCT_EXTENSION_AGENT_OBJECT (user_data);
578 : : static const GDBusInterfaceVTable extension_agent_interface_vtable =
579 : : {
580 : : mct_extension_agent_object_method_call,
581 : : NULL, /* handled in mct_extension_agent_object_method_call() */
582 : : NULL, /* handled in mct_extension_agent_object_method_call() */
583 : : { NULL, } /* padding */
584 : : };
585 : : static const GDBusInterfaceVTable extension_agent_request_interface_vtable =
586 : : {
587 : : mct_extension_agent_request_method_call,
588 : : NULL, /* handled in mct_extension_agent_request_method_call() */
589 : : NULL, /* handled in mct_extension_agent_request_method_call() */
590 : : { NULL, } /* padding */
591 : : };
592 : :
593 : : /* Don’t implement any permissions checks here, as they should be specific to
594 : : * the APIs being called and objects being accessed. */
595 : :
596 : : /* Agent is implemented on the root of the tree. */
597 [ + + ]: 86 : if (node == NULL &&
598 [ + + ]: 47 : (g_str_equal (interface_name, "org.freedesktop.MalcontentTimer1.ExtensionAgent") ||
599 [ + - ]: 11 : g_str_equal (interface_name, "org.freedesktop.DBus.Properties")))
600 : : {
601 : 47 : *out_user_data = user_data;
602 : 47 : return &extension_agent_interface_vtable;
603 : : }
604 [ - + ]: 39 : else if (node == NULL)
605 : : {
606 : 0 : return NULL;
607 : : }
608 : :
609 : : /* We only handle the Request interface on other objects. */
610 [ + + ]: 39 : if (!g_str_equal (interface_name, "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request") &&
611 [ - + ]: 12 : !g_str_equal (interface_name, "org.freedesktop.DBus.Properties"))
612 : 0 : return NULL;
613 : :
614 : : /* Find the request. */
615 : 39 : MctExtensionAgentRequest *request = object_subpath_to_request (self, node);
616 : :
617 [ - + ]: 39 : if (request == NULL)
618 : 0 : return NULL;
619 : :
620 : 39 : *out_user_data = user_data;
621 : 39 : return &extension_agent_request_interface_vtable;
622 : : }
623 : :
624 : : static gboolean
625 : 23 : validate_dbus_interface_name (GDBusMethodInvocation *invocation,
626 : : const char *interface_name)
627 : : {
628 [ + + ]: 23 : if (!g_dbus_is_interface_name (interface_name))
629 : : {
630 : 6 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
631 : : G_DBUS_ERROR_UNKNOWN_INTERFACE,
632 : : _("Invalid interface name ‘%s’."),
633 : : interface_name);
634 : 6 : return FALSE;
635 : : }
636 : :
637 : 17 : return TRUE;
638 : : }
639 : :
640 : : typedef void (*ExtensionAgentMethodCallFunc) (MctExtensionAgentObject *self,
641 : : GDBusConnection *connection,
642 : : const char *sender,
643 : : GVariant *parameters,
644 : : GDBusMethodInvocation *invocation);
645 : :
646 : : static const struct
647 : : {
648 : : const char *interface_name;
649 : : const char *method_name;
650 : : ExtensionAgentMethodCallFunc func;
651 : : }
652 : : extension_agent_methods[] =
653 : : {
654 : : /* Handle properties. */
655 : : { "org.freedesktop.DBus.Properties", "Get",
656 : : mct_extension_agent_object_properties_get },
657 : : { "org.freedesktop.DBus.Properties", "Set",
658 : : mct_extension_agent_object_properties_set },
659 : : { "org.freedesktop.DBus.Properties", "GetAll",
660 : : mct_extension_agent_object_properties_get_all },
661 : :
662 : : /* Extension agent methods. */
663 : : { "org.freedesktop.MalcontentTimer1.ExtensionAgent", "RequestExtension",
664 : : mct_extension_agent_object_request_extension },
665 : : };
666 : :
667 : : static void
668 : 47 : mct_extension_agent_object_method_call (GDBusConnection *connection,
669 : : const char *sender,
670 : : const char *object_path,
671 : : const char *interface_name,
672 : : const char *method_name,
673 : : GVariant *parameters,
674 : : GDBusMethodInvocation *invocation,
675 : : void *user_data)
676 : : {
677 : 47 : MctExtensionAgentObject *self = MCT_EXTENSION_AGENT_OBJECT (user_data);
678 : 47 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
679 : :
680 : : /* Check we’ve implemented all the methods. Unfortunately this can’t be a
681 : : * compile time check because the method array is declared in a separate
682 : : * compilation unit. */
683 : 47 : size_t n_extension_agent_interface_methods = 0;
684 [ + + ]: 94 : for (size_t i = 0; org_freedesktop_malcontent_timer1_extension_agent_interface.methods[i] != NULL; i++)
685 : 47 : n_extension_agent_interface_methods++;
686 : :
687 : 47 : g_assert (G_N_ELEMENTS (extension_agent_methods) ==
688 : : n_extension_agent_interface_methods +
689 : : 3 /* o.fdo.DBus.Properties */);
690 : :
691 : : /* Remove the service prefix from the path. */
692 : 47 : g_assert (g_str_equal (object_path, priv->object_path));
693 : :
694 : : /* Work out which method to call. */
695 [ + - ]: 165 : for (size_t i = 0; i < G_N_ELEMENTS (extension_agent_methods); i++)
696 : : {
697 [ + + ]: 165 : if (g_str_equal (extension_agent_methods[i].interface_name, interface_name) &&
698 [ + + ]: 57 : g_str_equal (extension_agent_methods[i].method_name, method_name))
699 : : {
700 : 47 : extension_agent_methods[i].func (self, connection, sender, parameters, invocation);
701 : 47 : return;
702 : : }
703 : : }
704 : :
705 : : /* Make sure we actually called a method implementation. GIO guarantees that
706 : : * this function is only called with methods we’ve declared in the interface
707 : : * info, so this should never fail. */
708 : : g_assert_not_reached ();
709 : : }
710 : :
711 : : static void
712 : 4 : mct_extension_agent_object_properties_get (MctExtensionAgentObject *self,
713 : : GDBusConnection *connection,
714 : : const char *sender,
715 : : GVariant *parameters,
716 : : GDBusMethodInvocation *invocation)
717 : : {
718 : : const char *interface_name, *property_name;
719 : 4 : g_variant_get (parameters, "(&s&s)", &interface_name, &property_name);
720 : :
721 : : /* D-Bus property names can be anything. */
722 [ + + ]: 4 : if (!validate_dbus_interface_name (invocation, interface_name))
723 : 1 : return;
724 : :
725 : : /* No properties exposed. */
726 : 3 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
727 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
728 : : _("Unknown property ‘%s.%s’."),
729 : : interface_name, property_name);
730 : : }
731 : :
732 : : static void
733 : 4 : mct_extension_agent_object_properties_set (MctExtensionAgentObject *self,
734 : : GDBusConnection *connection,
735 : : const char *sender,
736 : : GVariant *parameters,
737 : : GDBusMethodInvocation *invocation)
738 : : {
739 : : const char *interface_name, *property_name;
740 : 4 : g_variant_get (parameters, "(&s&sv)", &interface_name, &property_name, NULL);
741 : :
742 : : /* D-Bus property names can be anything. */
743 [ + + ]: 4 : if (!validate_dbus_interface_name (invocation, interface_name))
744 : 1 : return;
745 : :
746 : : /* No properties exposed. */
747 : 3 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
748 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
749 : : _("Unknown property ‘%s.%s’."),
750 : : interface_name, property_name);
751 : : }
752 : :
753 : : static void
754 : 3 : mct_extension_agent_object_properties_get_all (MctExtensionAgentObject *self,
755 : : GDBusConnection *connection,
756 : : const char *sender,
757 : : GVariant *parameters,
758 : : GDBusMethodInvocation *invocation)
759 : : {
760 : : const char *interface_name;
761 : 3 : g_variant_get (parameters, "(&s)", &interface_name);
762 : :
763 [ + + ]: 3 : if (!validate_dbus_interface_name (invocation, interface_name))
764 : 1 : return;
765 : :
766 : : /* Try the interface. */
767 [ + + ]: 2 : if (g_str_equal (interface_name, "org.freedesktop.MalcontentTimer1.ExtensionAgent"))
768 : 1 : g_dbus_method_invocation_return_value (invocation,
769 : : g_variant_new_parsed ("(@a{sv} {},)"));
770 : : else
771 : 1 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
772 : : G_DBUS_ERROR_UNKNOWN_INTERFACE,
773 : : _("Unknown interface ‘%s’."),
774 : : interface_name);
775 : : }
776 : :
777 : : static gboolean
778 : 36 : validate_record_type_and_identifier (GDBusMethodInvocation *invocation,
779 : : const char *record_type,
780 : : const char *identifier)
781 : : {
782 : 36 : g_autoptr(GError) local_error = NULL;
783 : :
784 [ + + + + ]: 71 : if (!mct_timer_store_record_type_validate_string (record_type, &local_error) ||
785 : 35 : !mct_timer_store_record_type_validate_identifier (mct_timer_store_record_type_from_string (record_type), identifier, &local_error))
786 : : {
787 : 3 : g_dbus_method_invocation_return_error (invocation, MCT_EXTENSION_AGENT_OBJECT_ERROR,
788 : : MCT_EXTENSION_AGENT_OBJECT_ERROR_INVALID_QUERY,
789 : : _("Invalid query parameters: %s"),
790 : 3 : local_error->message);
791 : 3 : return FALSE;
792 : : }
793 : :
794 : 33 : return TRUE;
795 : : }
796 : :
797 : : /* https://www.freedesktop.org/software/polkit/docs/latest/eggdbus-interface-org.freedesktop.PolicyKit1.Authority.html#eggdbus-struct-Subject */
798 : : static gboolean
799 : 33 : validate_subject (GDBusMethodInvocation *invocation,
800 : : GVariant *subject)
801 : : {
802 : : const char *subject_kind;
803 : 33 : g_autoptr(GVariant) subject_details = NULL;
804 : :
805 : : /* For the moment we only support unix-process subjects, using the pidfd details. */
806 : 33 : g_assert (g_variant_is_of_type (subject, G_VARIANT_TYPE ("(sa{sv})")));
807 : :
808 : 33 : g_variant_get (subject, "(&s@a{sv})", &subject_kind, &subject_details);
809 : :
810 [ + + ]: 33 : if (g_str_equal (subject_kind, "unix-process"))
811 : : {
812 [ + + + + ]: 61 : if (!g_variant_lookup (subject_details, "pidfd", "h", NULL) ||
813 : 29 : !g_variant_lookup (subject_details, "uid", "i", NULL))
814 : : {
815 : 5 : g_dbus_method_invocation_return_error (invocation, MCT_EXTENSION_AGENT_OBJECT_ERROR,
816 : : MCT_EXTENSION_AGENT_OBJECT_ERROR_INVALID_QUERY,
817 : : _("Invalid query parameters: %s"),
818 : 5 : _("Missing subject UID"));
819 : 5 : return FALSE;
820 : : }
821 : :
822 : 27 : return TRUE;
823 : : }
824 : : else
825 : : {
826 : 1 : g_dbus_method_invocation_return_error (invocation, MCT_EXTENSION_AGENT_OBJECT_ERROR,
827 : : MCT_EXTENSION_AGENT_OBJECT_ERROR_INVALID_QUERY,
828 : : _("Invalid query parameters: %s"),
829 : 1 : _("Unsupported subject kind"));
830 : 1 : return FALSE;
831 : : }
832 : : }
833 : :
834 : : typedef struct
835 : : {
836 : : char *request_owner; /* (owned) */
837 : : char *request_object_path; /* (owned) */
838 : : MctOperationCounter operation_counter;
839 : : } RequestExtensionData;
840 : :
841 : : static void
842 : 27 : request_extension_data_free (RequestExtensionData *data)
843 : : {
844 [ + - ]: 27 : g_clear_pointer (&data->request_owner, g_free);
845 [ + - ]: 27 : g_clear_pointer (&data->request_object_path, g_free);
846 : 27 : mct_operation_counter_release_and_clear (&data->operation_counter);
847 : 27 : g_free (data);
848 : 27 : }
849 : :
850 [ + + ]: 126 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (RequestExtensionData, request_extension_data_free)
851 : :
852 : : static void request_sender_vanished_cb (GDBusConnection *connection,
853 : : const char *name,
854 : : void *user_data);
855 : : static void request_extension_vfunc_cb (GObject *object,
856 : : GAsyncResult *result,
857 : : void *user_data);
858 : :
859 : : static void
860 : 36 : mct_extension_agent_object_request_extension (MctExtensionAgentObject *self,
861 : : GDBusConnection *connection,
862 : : const char *sender,
863 : : GVariant *parameters,
864 : : GDBusMethodInvocation *invocation)
865 : : {
866 : 36 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
867 [ + + ]: 36 : g_autoptr(GError) local_error = NULL;
868 [ + + ]: 36 : g_autofree char *record_type = NULL;
869 [ + + ]: 36 : g_autofree char *identifier = NULL;
870 : : uint64_t duration_secs;
871 [ + + ]: 36 : g_autoptr(GVariant) subject = NULL;
872 : : GUnixFDList *subject_fd_list;
873 [ + + ]: 36 : g_autoptr(GVariant) extra_data = NULL;
874 [ + + ]: 36 : g_autoptr(RequestExtensionData) data = NULL;
875 : : MctExtensionAgentObjectClass *klass;
876 [ + + ]: 36 : g_autoptr(MctExtensionAgentRequest) request_owned = NULL;
877 : : MctExtensionAgentRequest *request;
878 : :
879 : : /* Validate the parameters. */
880 : 36 : g_variant_get (parameters, "(sst@r@a{sv})",
881 : : &record_type, &identifier, &duration_secs, &subject, &extra_data);
882 : :
883 [ + + ]: 36 : if (!validate_record_type_and_identifier (invocation, record_type, identifier))
884 : 3 : return;
885 [ + + ]: 33 : if (!validate_subject (invocation, subject))
886 : 6 : return;
887 : :
888 : : /* Only malcontent-timerd is allowed to call the agent, but we enforce that
889 : : * using a D-Bus access control policy, rather than in code. */
890 : :
891 : 27 : subject_fd_list = g_dbus_message_get_unix_fd_list (g_dbus_method_invocation_get_message (invocation));
892 : :
893 : 27 : data = g_new0 (RequestExtensionData, 1);
894 : 27 : mct_operation_counter_init_and_hold (&data->operation_counter,
895 : : &priv->n_pending_operations,
896 : 27 : G_OBJECT (self), props[PROP_BUSY]);
897 : :
898 : : /* Create an ExtensionAgent.Request object and immediately return it, so the
899 : : * client can track the request. We don’t need to explicitly register the
900 : : * object on the bus because once it’s in priv->requests, a call to
901 : : * mct_extension_agent_object_request_enumerate() will return it. */
902 [ - + ]: 27 : if (priv->request_counter == G_MAXUINT)
903 : : {
904 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_EXTENSION_AGENT_OBJECT_ERROR,
905 : : MCT_EXTENSION_AGENT_OBJECT_ERROR_FAILED,
906 : : _("Error requesting extension: %s"),
907 : 0 : _("Too many requests"));
908 : 0 : return;
909 : : }
910 : :
911 : 27 : request = request_owned = g_new0 (MctExtensionAgentRequest, 1);
912 : 54 : request->object_path = g_strdup_printf ("/org/freedesktop/MalcontentTimer1/ExtensionAgent/Request%u",
913 : 27 : ++priv->request_counter);
914 : 27 : request->object_subpath = request->object_path + strlen ("/org/freedesktop/MalcontentTimer1/ExtensionAgent/");
915 : 27 : request->cancellable = g_cancellable_new ();
916 : 27 : request->owner = g_strdup (g_dbus_method_invocation_get_sender (invocation));
917 : :
918 : 27 : data->request_owner = g_strdup (request->owner);
919 : 27 : data->request_object_path = g_strdup (request->object_path);
920 : 27 : g_hash_table_insert (priv->requests, (void *) request->object_subpath,
921 : : g_steal_pointer (&request_owned));
922 : :
923 : 27 : g_dbus_method_invocation_return_value (invocation,
924 : : g_variant_new ("(o)", request->object_path));
925 : :
926 : : /* This has potentially changed. */
927 : 27 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUSY]);
928 : :
929 : : /* Watch the sender so we can cancel the request if they disappear off the bus. */
930 : 27 : request->owner_name_watch_id = g_bus_watch_name_on_connection (priv->connection,
931 : : sender,
932 : : G_BUS_NAME_WATCHER_FLAGS_NONE,
933 : : NULL,
934 : : request_sender_vanished_cb,
935 : : self,
936 : : NULL);
937 : :
938 : : /* Now start the actual asynchronous extension request. It’ll be controlled
939 : : * and monitored from the request object. */
940 : 27 : klass = MCT_EXTENSION_AGENT_OBJECT_GET_CLASS (self);
941 : 27 : g_assert (klass->request_extension_async != NULL);
942 : 27 : klass->request_extension_async (self, record_type, identifier, duration_secs, extra_data, subject, subject_fd_list,
943 : : invocation, request->cancellable,
944 : 27 : request_extension_vfunc_cb, g_steal_pointer (&data));
945 : : }
946 : :
947 : : static void
948 : 27 : request_extension_vfunc_cb (GObject *object,
949 : : GAsyncResult *result,
950 : : void *user_data)
951 : : {
952 : 27 : MctExtensionAgentObject *self = MCT_EXTENSION_AGENT_OBJECT (object);
953 : 27 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
954 : 54 : g_autoptr(RequestExtensionData) data = g_steal_pointer (&user_data);
955 : : MctExtensionAgentObjectClass *klass;
956 : 27 : gboolean granted = FALSE;
957 : 27 : g_autoptr(GVariant) extra_data = NULL;
958 : 27 : g_autoptr(GError) local_error = NULL;
959 : :
960 : 27 : klass = MCT_EXTENSION_AGENT_OBJECT_GET_CLASS (self);
961 : 27 : g_assert (klass->request_extension_finish != NULL);
962 [ + + ]: 27 : if (!klass->request_extension_finish (self, result, &granted, &extra_data, &local_error))
963 : : {
964 : 7 : g_autofree char *error_name = NULL;
965 : :
966 : 7 : g_assert (local_error->domain == MCT_EXTENSION_AGENT_OBJECT_ERROR);
967 : :
968 : 7 : granted = FALSE;
969 [ - + ]: 7 : g_clear_pointer (&extra_data, g_variant_unref);
970 : :
971 : 7 : error_name = g_dbus_error_encode_gerror (local_error);
972 : :
973 : 7 : g_auto(GVariantBuilder) extra_data_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{sv}"));
974 [ + - ]: 7 : if (error_name != NULL)
975 : 7 : g_variant_builder_add (&extra_data_builder, "{sv}", "error-name", g_variant_new_string (error_name));
976 : 7 : extra_data = g_variant_ref_sink (g_variant_builder_end (&extra_data_builder));
977 : : }
978 : :
979 : : /* Emit the response signal on the request object, to notify the client about
980 : : * the decision. This call can only fail if the parameters are invalid, or the
981 : : * D-Bus connection has been closed, so we don’t bother with error reporting. */
982 : 27 : g_dbus_connection_emit_signal (priv->connection,
983 : 27 : data->request_owner,
984 : 27 : data->request_object_path,
985 : : "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
986 : : "Response",
987 : : g_variant_new ("(b@a{sv})",
988 : : granted,
989 [ - + ]: 27 : (extra_data != NULL) ? extra_data : g_variant_new_parsed ("@a{sv} {}")),
990 : : NULL);
991 : :
992 : : /* Keep the Request object about until the client calls Close() on it, but
993 : : * otherwise this async chain is complete. */
994 : 27 : }
995 : :
996 : : static void
997 : 1 : request_sender_vanished_cb (GDBusConnection *connection,
998 : : const char *name,
999 : : void *user_data)
1000 : : {
1001 : 1 : MctExtensionAgentObject *self = MCT_EXTENSION_AGENT_OBJECT (user_data);
1002 : 1 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
1003 : : GHashTableIter iter;
1004 : : void *value;
1005 : :
1006 : : /* Search for all the sender’s requests. */
1007 : 1 : g_hash_table_iter_init (&iter, priv->requests);
1008 [ + + ]: 2 : while (g_hash_table_iter_next (&iter, NULL, &value))
1009 : : {
1010 : 1 : MctExtensionAgentRequest *request = value;
1011 : :
1012 [ - + ]: 1 : if (!g_str_equal (request->owner, name))
1013 : 0 : continue;
1014 : :
1015 : : /* Cancel any pending async calls associated with the request. */
1016 : 1 : g_cancellable_cancel (request->cancellable);
1017 : :
1018 : : /* Remove the request. */
1019 : 1 : g_hash_table_iter_remove (&iter);
1020 : : }
1021 : :
1022 : : /* This has potentially changed. */
1023 : 1 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUSY]);
1024 : 1 : }
1025 : :
1026 : : typedef void (*ExtensionAgentRequestMethodCallFunc) (MctExtensionAgentObject *self,
1027 : : MctExtensionAgentRequest *request,
1028 : : GDBusConnection *connection,
1029 : : const char *sender,
1030 : : GVariant *parameters,
1031 : : GDBusMethodInvocation *invocation);
1032 : :
1033 : : static const struct
1034 : : {
1035 : : const char *interface_name;
1036 : : const char *method_name;
1037 : : ExtensionAgentRequestMethodCallFunc func;
1038 : : }
1039 : : extension_agent_request_methods[] =
1040 : : {
1041 : : /* Handle properties. */
1042 : : { "org.freedesktop.DBus.Properties", "Get",
1043 : : mct_extension_agent_request_properties_get },
1044 : : { "org.freedesktop.DBus.Properties", "Set",
1045 : : mct_extension_agent_request_properties_set },
1046 : : { "org.freedesktop.DBus.Properties", "GetAll",
1047 : : mct_extension_agent_request_properties_get_all },
1048 : :
1049 : : /* Request methods. */
1050 : : { "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request", "Close",
1051 : : mct_extension_agent_request_close },
1052 : : };
1053 : :
1054 : : static void
1055 : 39 : mct_extension_agent_request_method_call (GDBusConnection *connection,
1056 : : const char *sender,
1057 : : const char *object_path,
1058 : : const char *interface_name,
1059 : : const char *method_name,
1060 : : GVariant *parameters,
1061 : : GDBusMethodInvocation *invocation,
1062 : : void *user_data)
1063 : : {
1064 : 39 : MctExtensionAgentObject *self = MCT_EXTENSION_AGENT_OBJECT (user_data);
1065 : 39 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
1066 : : MctExtensionAgentRequest *request;
1067 : :
1068 : : /* Check we’ve implemented all the methods. Unfortunately this can’t be a
1069 : : * compile time check because the method array is declared in a separate
1070 : : * compilation unit. */
1071 : 39 : size_t n_request_interface_methods = 0;
1072 [ + + ]: 78 : for (size_t i = 0; org_freedesktop_malcontent_timer1_extension_agent_request_interface.methods[i] != NULL; i++)
1073 : 39 : n_request_interface_methods++;
1074 : :
1075 : 39 : g_assert (G_N_ELEMENTS (extension_agent_request_methods) ==
1076 : : n_request_interface_methods +
1077 : : 3 /* o.fdo.DBus.Properties */);
1078 : :
1079 : : /* Remove the service prefix from the path. */
1080 : 39 : g_assert (g_str_has_prefix (object_path, priv->object_path));
1081 : 39 : g_assert (object_path[strlen (priv->object_path)] == '/');
1082 : :
1083 : 39 : request = object_subpath_to_request (self,
1084 : 39 : object_path + strlen (priv->object_path) + 1);
1085 : 39 : g_assert (request != NULL);
1086 : :
1087 : : /* Check the @sender is the owner of @request. */
1088 [ + + ]: 39 : if (!g_str_equal (request->owner, sender))
1089 : : {
1090 : 1 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1091 : : G_DBUS_ERROR_UNKNOWN_OBJECT,
1092 : : _("Unknown object ‘%s’."), object_path);
1093 : 1 : return;
1094 : : }
1095 : :
1096 : : /* Work out which method to call. */
1097 [ + - ]: 128 : for (size_t i = 0; i < G_N_ELEMENTS (extension_agent_request_methods); i++)
1098 : : {
1099 [ + + ]: 128 : if (g_str_equal (extension_agent_request_methods[i].interface_name, interface_name) &&
1100 [ + + ]: 50 : g_str_equal (extension_agent_request_methods[i].method_name, method_name))
1101 : : {
1102 : 38 : extension_agent_request_methods[i].func (self, request, connection,
1103 : : sender, parameters, invocation);
1104 : 38 : return;
1105 : : }
1106 : : }
1107 : :
1108 : : /* Make sure we actually called a method implementation. GIO guarantees that
1109 : : * this function is only called with methods we’ve declared in the interface
1110 : : * info, so this should never fail. */
1111 : : g_assert_not_reached ();
1112 : : }
1113 : :
1114 : : static void
1115 : 4 : mct_extension_agent_request_properties_get (MctExtensionAgentObject *self,
1116 : : MctExtensionAgentRequest *request,
1117 : : GDBusConnection *connection,
1118 : : const char *sender,
1119 : : GVariant *parameters,
1120 : : GDBusMethodInvocation *invocation)
1121 : : {
1122 : : const char *interface_name, *property_name;
1123 : 4 : g_variant_get (parameters, "(&s&s)", &interface_name, &property_name);
1124 : :
1125 : : /* D-Bus property names can be anything. */
1126 [ + + ]: 4 : if (!validate_dbus_interface_name (invocation, interface_name))
1127 : 1 : return;
1128 : :
1129 : : /* No properties exposed. */
1130 : 3 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1131 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
1132 : : _("Unknown property ‘%s.%s’."),
1133 : : interface_name, property_name);
1134 : : }
1135 : :
1136 : : static void
1137 : 4 : mct_extension_agent_request_properties_set (MctExtensionAgentObject *self,
1138 : : MctExtensionAgentRequest *request,
1139 : : GDBusConnection *connection,
1140 : : const char *sender,
1141 : : GVariant *parameters,
1142 : : GDBusMethodInvocation *invocation)
1143 : : {
1144 : : const char *interface_name, *property_name;
1145 : 4 : g_variant_get (parameters, "(&s&sv)", &interface_name, &property_name, NULL);
1146 : :
1147 : : /* D-Bus property names can be anything. */
1148 [ + + ]: 4 : if (!validate_dbus_interface_name (invocation, interface_name))
1149 : 1 : return;
1150 : :
1151 : : /* No properties exposed. */
1152 : 3 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1153 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
1154 : : _("Unknown property ‘%s.%s’."),
1155 : : interface_name, property_name);
1156 : : }
1157 : :
1158 : : static void
1159 : 4 : mct_extension_agent_request_properties_get_all (MctExtensionAgentObject *self,
1160 : : MctExtensionAgentRequest *request,
1161 : : GDBusConnection *connection,
1162 : : const char *sender,
1163 : : GVariant *parameters,
1164 : : GDBusMethodInvocation *invocation)
1165 : : {
1166 : : const char *interface_name;
1167 : 4 : g_variant_get (parameters, "(&s)", &interface_name);
1168 : :
1169 [ + + ]: 4 : if (!validate_dbus_interface_name (invocation, interface_name))
1170 : 1 : return;
1171 : :
1172 : : /* Try the interface. */
1173 [ + + ]: 3 : if (g_str_equal (interface_name, "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request"))
1174 : 1 : g_dbus_method_invocation_return_value (invocation,
1175 : : g_variant_new_parsed ("(@a{sv} {},)"));
1176 : : else
1177 : 2 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1178 : : G_DBUS_ERROR_UNKNOWN_INTERFACE,
1179 : : _("Unknown interface ‘%s’."),
1180 : : interface_name);
1181 : : }
1182 : :
1183 : : static void
1184 : 26 : mct_extension_agent_request_close (MctExtensionAgentObject *self,
1185 : : MctExtensionAgentRequest *request,
1186 : : GDBusConnection *connection,
1187 : : const char *sender,
1188 : : GVariant *parameters,
1189 : : GDBusMethodInvocation *invocation)
1190 : : {
1191 : 26 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
1192 : :
1193 : : /* We don’t need to check whether the @sender has permission, as
1194 : : * mct_extension_agent_request_method_call() has already done that. */
1195 : :
1196 : : /* Cancel any pending async calls associated with the request. */
1197 : 26 : g_cancellable_cancel (request->cancellable);
1198 : :
1199 : : /* Remove the request. Note: @request may be potentially freed after this point. */
1200 : 26 : g_hash_table_remove (priv->requests, request->object_subpath);
1201 : :
1202 : 26 : g_dbus_method_invocation_return_value (invocation, NULL);
1203 : :
1204 : : /* This has potentially changed. */
1205 : 26 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUSY]);
1206 : 26 : }
1207 : :
1208 : : /**
1209 : : * mct_extension_agent_object_get_busy:
1210 : : * @self: an extension agent object
1211 : : *
1212 : : * Get the value of [property@Malcontent.ExtensionAgentObject.busy].
1213 : : *
1214 : : * Returns: true if the object is busy, false otherwise
1215 : : * Since: 0.14.0
1216 : : */
1217 : : gboolean
1218 : 47 : mct_extension_agent_object_get_busy (MctExtensionAgentObject *self)
1219 : : {
1220 : 47 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
1221 : :
1222 : 47 : g_return_val_if_fail (MCT_IS_EXTENSION_AGENT_OBJECT (self), FALSE);
1223 : :
1224 [ + + ]: 92 : return (priv->request_subtree_id != 0 &&
1225 [ + - + + ]: 45 : (priv->n_pending_operations > 0 || g_hash_table_size (priv->requests) > 0));
1226 : : }
1227 : :
1228 : : /**
1229 : : * mct_extension_agent_object_get_connection:
1230 : : * @self: an extension agent object
1231 : : *
1232 : : * Get the value of [property@Malcontent.ExtensionAgentObject.connection].
1233 : : *
1234 : : * Returns: D-Bus connection used by the agent object
1235 : : * Since: 0.14.0
1236 : : */
1237 : : GDBusConnection *
1238 : 28 : mct_extension_agent_object_get_connection (MctExtensionAgentObject *self)
1239 : : {
1240 : 28 : MctExtensionAgentObjectPrivate *priv = mct_extension_agent_object_get_instance_private (self);
1241 : :
1242 : 28 : g_return_val_if_fail (MCT_IS_EXTENSION_AGENT_OBJECT (self), NULL);
1243 : :
1244 : 28 : return priv->connection;
1245 : : }
|