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 <libgsystemservice/peer-manager.h>
31 : : #include <libmalcontent/user-manager.h>
32 : : #include <libmalcontent/user.h>
33 : : #include <libmalcontent-timer/parent-timer-service.h>
34 : : #include <libmalcontent-timer/time-span.h>
35 : : #include <libmalcontent-timer/timer-store.h>
36 : :
37 : : #include "parent-iface.h"
38 : : #include "enums.h"
39 : : #include "operation-counter-private.h"
40 : :
41 : :
42 : : static void mct_parent_timer_service_constructed (GObject *object);
43 : : static void mct_parent_timer_service_dispose (GObject *object);
44 : : static void mct_parent_timer_service_get_property (GObject *object,
45 : : unsigned int property_id,
46 : : GValue *value,
47 : : GParamSpec *pspec);
48 : : static void mct_parent_timer_service_set_property (GObject *object,
49 : : unsigned int property_id,
50 : : const GValue *value,
51 : : GParamSpec *pspec);
52 : :
53 : : static void mct_parent_timer_service_method_call (GDBusConnection *connection,
54 : : const char *sender,
55 : : const char *object_path,
56 : : const char *interface_name,
57 : : const char *method_name,
58 : : GVariant *parameters,
59 : : GDBusMethodInvocation *invocation,
60 : : void *user_data);
61 : : static void mct_parent_timer_service_properties_get (MctParentTimerService *self,
62 : : GDBusConnection *connection,
63 : : const char *sender,
64 : : GVariant *parameters,
65 : : GDBusMethodInvocation *invocation);
66 : : static void mct_parent_timer_service_properties_set (MctParentTimerService *self,
67 : : GDBusConnection *connection,
68 : : const char *sender,
69 : : GVariant *parameters,
70 : : GDBusMethodInvocation *invocation);
71 : : static void mct_parent_timer_service_properties_get_all (MctParentTimerService *self,
72 : : GDBusConnection *connection,
73 : : const char *sender,
74 : : GVariant *parameters,
75 : : GDBusMethodInvocation *invocation);
76 : :
77 : : static void timer_store_estimated_end_times_changed_cb (MctTimerStore *timer_store,
78 : : const char *username,
79 : : void *user_data);
80 : : static void mct_parent_timer_service_query_usage (MctParentTimerService *self,
81 : : GDBusConnection *connection,
82 : : const char *sender,
83 : : GVariant *parameters,
84 : : GDBusMethodInvocation *invocation);
85 : :
86 : : /* These errors do go over the bus, and are registered in mct_parent_timer_service_class_init(). */
87 [ + + ]: 5 : G_DEFINE_QUARK (MctParentTimerServiceError, mct_parent_timer_service_error)
88 : :
89 : : static const char *parent_timer_service_errors[] =
90 : : {
91 : : "org.freedesktop.MalcontentTimer1.Parent.Error.InvalidQuery",
92 : : "org.freedesktop.MalcontentTimer1.Parent.Error.StorageError",
93 : : "org.freedesktop.MalcontentTimer1.Parent.Error.Busy",
94 : : "org.freedesktop.MalcontentTimer1.Parent.Error.IdentifyingUser",
95 : : "org.freedesktop.MalcontentTimer1.Parent.Error.PermissionDenied",
96 : : };
97 : : static const GDBusErrorEntry parent_timer_service_error_map[] =
98 : : {
99 : : { MCT_PARENT_TIMER_SERVICE_ERROR_INVALID_QUERY, "org.freedesktop.MalcontentTimer1.Parent.Error.InvalidQuery" },
100 : : { MCT_PARENT_TIMER_SERVICE_ERROR_STORAGE_ERROR, "org.freedesktop.MalcontentTimer1.Parent.Error.StorageError" },
101 : : { MCT_PARENT_TIMER_SERVICE_ERROR_BUSY, "org.freedesktop.MalcontentTimer1.Parent.Error.Busy" },
102 : : { MCT_PARENT_TIMER_SERVICE_ERROR_IDENTIFYING_USER, "org.freedesktop.MalcontentTimer1.Parent.Error.IdentifyingUser" },
103 : : { MCT_PARENT_TIMER_SERVICE_ERROR_PERMISSION_DENIED, "org.freedesktop.MalcontentTimer1.Parent.Error.PermissionDenied" },
104 : : };
105 : : G_STATIC_ASSERT (G_N_ELEMENTS (parent_timer_service_error_map) == MCT_PARENT_TIMER_SERVICE_N_ERRORS);
106 : : G_STATIC_ASSERT (G_N_ELEMENTS (parent_timer_service_error_map) == G_N_ELEMENTS (parent_timer_service_errors));
107 : :
108 : : /**
109 : : * MctParentTimerService:
110 : : *
111 : : * An implementation of the `org.freedesktop.MalcontentTimer1.Parent` D-Bus
112 : : * interface, allowing a trusted component in a parent user account’s session to
113 : : * record screen time and app usage periods for that account.
114 : : *
115 : : * This will expose all the necessary objects on the bus for peers to interact
116 : : * with them, and hooks them up to internal state management using
117 : : * [property@Malcontent.ParentTimerService:timer-store].
118 : : *
119 : : * Since: 0.14.0
120 : : */
121 : : struct _MctParentTimerService
122 : : {
123 : : GObject parent;
124 : :
125 : : GDBusConnection *connection; /* (owned) */
126 : : char *object_path; /* (owned) */
127 : : unsigned int object_id;
128 : :
129 : : /* Used to cancel any pending operations when the object is unregistered. */
130 : : GCancellable *cancellable; /* (owned) */
131 : :
132 : : MctTimerStore *timer_store; /* (owned) */
133 : : unsigned long timer_store_estimated_end_times_changed_id;
134 : : MctUserManager *user_manager; /* (owned) */
135 : : GssPeerManager *peer_manager; /* (owned) */
136 : : unsigned int n_pending_operations;
137 : : };
138 : :
139 : : typedef enum
140 : : {
141 : : PROP_CONNECTION = 1,
142 : : PROP_OBJECT_PATH,
143 : : PROP_TIMER_STORE,
144 : : PROP_USER_MANAGER,
145 : : PROP_PEER_MANAGER,
146 : : PROP_BUSY,
147 : : } MctParentTimerServiceProperty;
148 : :
149 : : static GParamSpec *props[PROP_BUSY + 1] = { NULL, };
150 : :
151 [ + + + - : 16 : G_DEFINE_TYPE (MctParentTimerService, mct_parent_timer_service, G_TYPE_OBJECT)
+ + ]
152 : :
153 : : static void
154 : 1 : mct_parent_timer_service_class_init (MctParentTimerServiceClass *klass)
155 : : {
156 : 1 : GObjectClass *object_class = (GObjectClass *) klass;
157 : :
158 : 1 : object_class->constructed = mct_parent_timer_service_constructed;
159 : 1 : object_class->dispose = mct_parent_timer_service_dispose;
160 : 1 : object_class->get_property = mct_parent_timer_service_get_property;
161 : 1 : object_class->set_property = mct_parent_timer_service_set_property;
162 : :
163 : : /**
164 : : * MctParentTimerService:connection:
165 : : *
166 : : * D-Bus connection to export objects on.
167 : : *
168 : : * Since: 0.14.0
169 : : */
170 : 1 : props[PROP_CONNECTION] =
171 : 1 : g_param_spec_object ("connection", NULL, NULL,
172 : : G_TYPE_DBUS_CONNECTION,
173 : : G_PARAM_READWRITE |
174 : : G_PARAM_CONSTRUCT_ONLY |
175 : : G_PARAM_STATIC_STRINGS);
176 : :
177 : : /**
178 : : * MctParentTimerService:object-path:
179 : : *
180 : : * Object path to root all exported objects at. If this does not end in a
181 : : * slash, one will be added.
182 : : *
183 : : * Since: 0.14.0
184 : : */
185 : 1 : props[PROP_OBJECT_PATH] =
186 : 1 : g_param_spec_string ("object-path", NULL, NULL,
187 : : "/",
188 : : G_PARAM_READWRITE |
189 : : G_PARAM_CONSTRUCT_ONLY |
190 : : G_PARAM_STATIC_STRINGS);
191 : :
192 : : /**
193 : : * MctParentTimerService:timer-store:
194 : : *
195 : : * Store for timer data.
196 : : *
197 : : * Since: 0.14.0
198 : : */
199 : 1 : props[PROP_TIMER_STORE] =
200 : 1 : g_param_spec_object ("timer-store", NULL, NULL,
201 : : MCT_TYPE_TIMER_STORE,
202 : : G_PARAM_READWRITE |
203 : : G_PARAM_CONSTRUCT_ONLY |
204 : : G_PARAM_STATIC_STRINGS);
205 : :
206 : : /**
207 : : * MctParentTimerService:user-manager:
208 : : *
209 : : * User manager for querying family relationships.
210 : : *
211 : : * Since: 0.14.0
212 : : */
213 : 1 : props[PROP_USER_MANAGER] =
214 : 1 : g_param_spec_object ("user-manager", NULL, NULL,
215 : : MCT_TYPE_USER_MANAGER,
216 : : G_PARAM_READWRITE |
217 : : G_PARAM_CONSTRUCT_ONLY |
218 : : G_PARAM_STATIC_STRINGS);
219 : :
220 : : /**
221 : : * MctParentTimerService:peer-manager:
222 : : *
223 : : * Peer manager to identify incoming method calls.
224 : : *
225 : : * Since: 0.14.0
226 : : */
227 : 1 : props[PROP_PEER_MANAGER] =
228 : 1 : g_param_spec_object ("peer-manager", NULL, NULL,
229 : : GSS_TYPE_PEER_MANAGER,
230 : : G_PARAM_READWRITE |
231 : : G_PARAM_CONSTRUCT_ONLY |
232 : : G_PARAM_STATIC_STRINGS);
233 : :
234 : : /**
235 : : * MctParentTimerService:busy:
236 : : *
237 : : * True if the D-Bus API is busy.
238 : : *
239 : : * For example, if there are any outstanding method calls which haven’t been
240 : : * replied to yet.
241 : : *
242 : : * Since: 0.14.0
243 : : */
244 : 1 : props[PROP_BUSY] =
245 : 1 : g_param_spec_boolean ("busy", NULL, NULL,
246 : : FALSE,
247 : : G_PARAM_READABLE |
248 : : G_PARAM_STATIC_STRINGS);
249 : :
250 : 1 : g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
251 : :
252 : : /* Error domain registration for D-Bus. We do this here, rather than in a
253 : : * #GOnce section in mct_parent_timer_service_error_quark(), to avoid spreading the
254 : : * D-Bus code outside this file. */
255 [ + + ]: 6 : for (size_t i = 0; i < G_N_ELEMENTS (parent_timer_service_error_map); i++)
256 : 5 : g_dbus_error_register_error (MCT_PARENT_TIMER_SERVICE_ERROR,
257 : 5 : parent_timer_service_error_map[i].error_code,
258 : 5 : parent_timer_service_error_map[i].dbus_error_name);
259 : 1 : }
260 : :
261 : : static void
262 : 1 : mct_parent_timer_service_init (MctParentTimerService *self)
263 : : {
264 : 1 : self->cancellable = g_cancellable_new ();
265 : 1 : }
266 : :
267 : : static void
268 : 1 : mct_parent_timer_service_constructed (GObject *object)
269 : : {
270 : 1 : MctParentTimerService *self = MCT_PARENT_TIMER_SERVICE (object);
271 : :
272 : : /* Chain up. */
273 : 1 : G_OBJECT_CLASS (mct_parent_timer_service_parent_class)->constructed (object);
274 : :
275 : : /* Check our construct properties. */
276 : 1 : g_assert (G_IS_DBUS_CONNECTION (self->connection));
277 : 1 : g_assert (g_variant_is_object_path (self->object_path));
278 : 1 : g_assert (MCT_IS_TIMER_STORE (self->timer_store));
279 : 1 : g_assert (MCT_IS_USER_MANAGER (self->user_manager));
280 : 1 : g_assert (GSS_IS_PEER_MANAGER (self->peer_manager));
281 : :
282 : : /* Connect to signals on the timer store. */
283 : 1 : self->timer_store_estimated_end_times_changed_id =
284 : 1 : g_signal_connect (self->timer_store, "estimated-end-times-changed",
285 : : G_CALLBACK (timer_store_estimated_end_times_changed_cb),
286 : : self);
287 : 1 : }
288 : :
289 : : static void
290 : 0 : mct_parent_timer_service_dispose (GObject *object)
291 : : {
292 : 0 : MctParentTimerService *self = MCT_PARENT_TIMER_SERVICE (object);
293 : :
294 : 0 : g_assert (self->object_id == 0);
295 : 0 : g_assert (self->n_pending_operations == 0);
296 : :
297 [ # # ]: 0 : g_clear_object (&self->peer_manager);
298 [ # # ]: 0 : g_clear_object (&self->user_manager);
299 [ # # ]: 0 : g_clear_signal_handler (&self->timer_store_estimated_end_times_changed_id, self->timer_store);
300 [ # # ]: 0 : g_clear_object (&self->timer_store);
301 : :
302 [ # # ]: 0 : g_clear_object (&self->connection);
303 [ # # ]: 0 : g_clear_pointer (&self->object_path, g_free);
304 [ # # ]: 0 : g_clear_object (&self->cancellable);
305 : :
306 : : /* Chain up to the parent class */
307 : 0 : G_OBJECT_CLASS (mct_parent_timer_service_parent_class)->dispose (object);
308 : 0 : }
309 : :
310 : : static void
311 : 0 : mct_parent_timer_service_get_property (GObject *object,
312 : : unsigned int property_id,
313 : : GValue *value,
314 : : GParamSpec *pspec)
315 : : {
316 : 0 : MctParentTimerService *self = MCT_PARENT_TIMER_SERVICE (object);
317 : :
318 [ # # # # : 0 : switch ((MctParentTimerServiceProperty) property_id)
# # # ]
319 : : {
320 : 0 : case PROP_CONNECTION:
321 : 0 : g_value_set_object (value, self->connection);
322 : 0 : break;
323 : 0 : case PROP_OBJECT_PATH:
324 : 0 : g_value_set_string (value, self->object_path);
325 : 0 : break;
326 : 0 : case PROP_TIMER_STORE:
327 : 0 : g_value_set_object (value, self->timer_store);
328 : 0 : break;
329 : 0 : case PROP_USER_MANAGER:
330 : 0 : g_value_set_object (value, self->user_manager);
331 : 0 : break;
332 : 0 : case PROP_PEER_MANAGER:
333 : 0 : g_value_set_object (value, self->peer_manager);
334 : 0 : break;
335 : 0 : case PROP_BUSY:
336 : 0 : g_value_set_boolean (value, mct_parent_timer_service_get_busy (self));
337 : 0 : break;
338 : 0 : default:
339 : : g_assert_not_reached ();
340 : : }
341 : 0 : }
342 : :
343 : : static void
344 : 5 : mct_parent_timer_service_set_property (GObject *object,
345 : : unsigned int property_id,
346 : : const GValue *value,
347 : : GParamSpec *pspec)
348 : : {
349 : 5 : MctParentTimerService *self = MCT_PARENT_TIMER_SERVICE (object);
350 : :
351 [ + + + + : 5 : switch ((MctParentTimerServiceProperty) property_id)
+ - ]
352 : : {
353 : 1 : case PROP_CONNECTION:
354 : : /* Construct only. */
355 : 1 : g_assert (self->connection == NULL);
356 : 1 : self->connection = g_value_dup_object (value);
357 : 1 : break;
358 : 1 : case PROP_OBJECT_PATH:
359 : : /* Construct only. */
360 : 1 : g_assert (self->object_path == NULL);
361 : 1 : g_assert (g_variant_is_object_path (g_value_get_string (value)));
362 : 1 : self->object_path = g_value_dup_string (value);
363 : 1 : break;
364 : 1 : case PROP_TIMER_STORE:
365 : : /* Construct only. */
366 : 1 : g_assert (self->timer_store == NULL);
367 : 1 : self->timer_store = g_value_dup_object (value);
368 : 1 : break;
369 : 1 : case PROP_USER_MANAGER:
370 : : /* Construct only */
371 : 1 : g_assert (self->user_manager == NULL);
372 : 1 : self->user_manager = g_value_dup_object (value);
373 : 1 : break;
374 : 1 : case PROP_PEER_MANAGER:
375 : : /* Construct only. */
376 : 1 : g_assert (self->peer_manager == NULL);
377 : 1 : self->peer_manager = g_value_dup_object (value);
378 : 1 : break;
379 : 0 : case PROP_BUSY:
380 : : /* Read only. Fall through. */
381 : : G_GNUC_FALLTHROUGH;
382 : : default:
383 : : g_assert_not_reached ();
384 : : }
385 : 5 : }
386 : :
387 : : static void
388 : 0 : timer_store_estimated_end_times_changed_cb (MctTimerStore *timer_store,
389 : : const char *username,
390 : : void *user_data)
391 : : {
392 : 0 : MctParentTimerService *self = MCT_PARENT_TIMER_SERVICE (user_data);
393 : :
394 : : /* Ignore errors from emitting the signal; it can only fail if the parameters
395 : : * are invalid (not possible) or if the connection has been closed. */
396 : 0 : g_dbus_connection_emit_signal (self->connection,
397 : : NULL, /* destination bus name */
398 : 0 : self->object_path,
399 : : "org.freedesktop.MalcontentTimer1.Parent",
400 : : "UsageChanged",
401 : : NULL,
402 : : NULL);
403 : 0 : }
404 : :
405 : : /**
406 : : * mct_parent_timer_service_register:
407 : : * @self: a parent service
408 : : * @error: return location for a [type@GLib.Error]
409 : : *
410 : : * Register the schedule service objects on D-Bus using the connection details
411 : : * given in [property@Malcontent.ParentTimerService.connection] and
412 : : * [property@Malcontent.ParentTimerService.object-path].
413 : : *
414 : : * Use [method@Malcontent.ParentTimerService.unregister] to unregister them.
415 : : * Calls to these two functions must be well paired.
416 : : *
417 : : * Returns: true on success, false otherwise
418 : : * Since: 0.14.0
419 : : */
420 : : gboolean
421 : 1 : mct_parent_timer_service_register (MctParentTimerService *self,
422 : : GError **error)
423 : : {
424 : 1 : g_return_val_if_fail (MCT_IS_PARENT_TIMER_SERVICE (self), FALSE);
425 : 1 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
426 : :
427 : 1 : const GDBusInterfaceVTable interface_vtable =
428 : : {
429 : : mct_parent_timer_service_method_call,
430 : : NULL, /* handled in mct_parent_timer_service_method_call() */
431 : : NULL, /* handled in mct_parent_timer_service_method_call() */
432 : : { NULL, }, /* padding */
433 : : };
434 : :
435 : 1 : unsigned int id = g_dbus_connection_register_object (self->connection,
436 : 1 : self->object_path,
437 : : (GDBusInterfaceInfo *) &org_freedesktop_malcontent_timer1_parent_interface,
438 : : &interface_vtable,
439 : : g_object_ref (self),
440 : : g_object_unref,
441 : : error);
442 : :
443 [ - + ]: 1 : if (id == 0)
444 : 0 : return FALSE;
445 : :
446 : 1 : self->object_id = id;
447 : :
448 : : /* This has potentially changed. */
449 : 1 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUSY]);
450 : :
451 : 1 : return TRUE;
452 : : }
453 : :
454 : : /**
455 : : * mct_parent_timer_service_unregister:
456 : : * @self: a parent service
457 : : *
458 : : * Unregister objects from D-Bus which were previously registered using
459 : : * [method@Malcontent.ParentTimerService.register].
460 : : *
461 : : * Calls to these two functions must be well paired.
462 : : *
463 : : * Since: 0.14.0
464 : : */
465 : : void
466 : 1 : mct_parent_timer_service_unregister (MctParentTimerService *self)
467 : : {
468 : 1 : g_return_if_fail (MCT_IS_PARENT_TIMER_SERVICE (self));
469 : :
470 : 1 : g_dbus_connection_unregister_object (self->connection, self->object_id);
471 : 1 : self->object_id = 0;
472 : :
473 : : /* This has potentially changed. */
474 : 1 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUSY]);
475 : : }
476 : :
477 : : static gboolean
478 : 0 : validate_dbus_interface_name (GDBusMethodInvocation *invocation,
479 : : const char *interface_name)
480 : : {
481 [ # # ]: 0 : if (!g_dbus_is_interface_name (interface_name))
482 : : {
483 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
484 : : G_DBUS_ERROR_UNKNOWN_INTERFACE,
485 : : _("Invalid interface name ‘%s’."),
486 : : interface_name);
487 : 0 : return FALSE;
488 : : }
489 : :
490 : 0 : return TRUE;
491 : : }
492 : :
493 : : static gboolean
494 : 0 : validate_record_type_and_identifier (GDBusMethodInvocation *invocation,
495 : : const char *record_type,
496 : : const char *identifier)
497 : : {
498 : 0 : g_autoptr(GError) local_error = NULL;
499 : :
500 [ # # # # ]: 0 : if (!mct_timer_store_record_type_validate_string (record_type, &local_error) ||
501 : 0 : !mct_timer_store_record_type_validate_identifier (mct_timer_store_record_type_from_string (record_type), identifier, &local_error))
502 : : {
503 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_PARENT_TIMER_SERVICE_ERROR,
504 : : MCT_PARENT_TIMER_SERVICE_ERROR_INVALID_QUERY,
505 : : _("Invalid query parameters: %s"),
506 : 0 : local_error->message);
507 : 0 : return FALSE;
508 : : }
509 : :
510 : 0 : return TRUE;
511 : : }
512 : :
513 : : typedef void (*ParentMethodCallFunc) (MctParentTimerService *self,
514 : : GDBusConnection *connection,
515 : : const char *sender,
516 : : GVariant *parameters,
517 : : GDBusMethodInvocation *invocation);
518 : :
519 : : static const struct
520 : : {
521 : : const char *interface_name;
522 : : const char *method_name;
523 : : ParentMethodCallFunc func;
524 : : }
525 : : parent_methods[] =
526 : : {
527 : : /* Handle properties. */
528 : : { "org.freedesktop.DBus.Properties", "Get",
529 : : mct_parent_timer_service_properties_get },
530 : : { "org.freedesktop.DBus.Properties", "Set",
531 : : mct_parent_timer_service_properties_set },
532 : : { "org.freedesktop.DBus.Properties", "GetAll",
533 : : mct_parent_timer_service_properties_get_all },
534 : :
535 : : /* Parent methods. */
536 : : { "org.freedesktop.MalcontentTimer1.Parent", "QueryUsage",
537 : : mct_parent_timer_service_query_usage },
538 : : };
539 : :
540 : : static void
541 : 0 : mct_parent_timer_service_method_call (GDBusConnection *connection,
542 : : const char *sender,
543 : : const char *object_path,
544 : : const char *interface_name,
545 : : const char *method_name,
546 : : GVariant *parameters,
547 : : GDBusMethodInvocation *invocation,
548 : : void *user_data)
549 : : {
550 : 0 : MctParentTimerService *self = MCT_PARENT_TIMER_SERVICE (user_data);
551 : :
552 : : /* Check we’ve implemented all the methods. Unfortunately this can’t be a
553 : : * compile time check because the method array is declared in a separate
554 : : * compilation unit. */
555 : 0 : size_t n_parent_interface_methods = 0;
556 [ # # ]: 0 : for (size_t i = 0; org_freedesktop_malcontent_timer1_parent_interface.methods[i] != NULL; i++)
557 : 0 : n_parent_interface_methods++;
558 : :
559 : 0 : g_assert (G_N_ELEMENTS (parent_methods) ==
560 : : n_parent_interface_methods +
561 : : 3 /* o.fdo.DBus.Properties */);
562 : :
563 : : /* Remove the service prefix from the path. */
564 : 0 : g_assert (g_str_equal (object_path, self->object_path));
565 : :
566 : : /* Work out which method to call. */
567 [ # # ]: 0 : for (size_t i = 0; i < G_N_ELEMENTS (parent_methods); i++)
568 : : {
569 [ # # ]: 0 : if (g_str_equal (parent_methods[i].interface_name, interface_name) &&
570 [ # # ]: 0 : g_str_equal (parent_methods[i].method_name, method_name))
571 : : {
572 : 0 : parent_methods[i].func (self, connection, sender, parameters, invocation);
573 : 0 : return;
574 : : }
575 : : }
576 : :
577 : : /* Make sure we actually called a method implementation. GIO guarantees that
578 : : * this function is only called with methods we’ve declared in the interface
579 : : * info, so this should never fail. */
580 : : g_assert_not_reached ();
581 : : }
582 : :
583 : : static void
584 : 0 : mct_parent_timer_service_properties_get (MctParentTimerService *self,
585 : : GDBusConnection *connection,
586 : : const char *sender,
587 : : GVariant *parameters,
588 : : GDBusMethodInvocation *invocation)
589 : : {
590 : : const char *interface_name, *property_name;
591 : 0 : g_variant_get (parameters, "(&s&s)", &interface_name, &property_name);
592 : :
593 : : /* D-Bus property names can be anything. */
594 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
595 : 0 : return;
596 : :
597 : : /* No properties exposed. */
598 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
599 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
600 : : _("Unknown property ‘%s.%s’."),
601 : : interface_name, property_name);
602 : : }
603 : :
604 : : static void
605 : 0 : mct_parent_timer_service_properties_set (MctParentTimerService *self,
606 : : GDBusConnection *connection,
607 : : const char *sender,
608 : : GVariant *parameters,
609 : : GDBusMethodInvocation *invocation)
610 : : {
611 : : const char *interface_name, *property_name;
612 : 0 : g_variant_get (parameters, "(&s&sv)", &interface_name, &property_name, NULL);
613 : :
614 : : /* D-Bus property names can be anything. */
615 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
616 : 0 : return;
617 : :
618 : : /* No properties exposed. */
619 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
620 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
621 : : _("Unknown property ‘%s.%s’."),
622 : : interface_name, property_name);
623 : : }
624 : :
625 : : static void
626 : 0 : mct_parent_timer_service_properties_get_all (MctParentTimerService *self,
627 : : GDBusConnection *connection,
628 : : const char *sender,
629 : : GVariant *parameters,
630 : : GDBusMethodInvocation *invocation)
631 : : {
632 : : const char *interface_name;
633 : 0 : g_variant_get (parameters, "(&s)", &interface_name);
634 : :
635 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
636 : 0 : return;
637 : :
638 : : /* Try the interface. */
639 [ # # ]: 0 : if (g_str_equal (interface_name, "org.freedesktop.MalcontentTimer1.Parent"))
640 : 0 : g_dbus_method_invocation_return_value (invocation,
641 : : g_variant_new_parsed ("(@a{sv} {},)"));
642 : : else
643 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
644 : : G_DBUS_ERROR_UNKNOWN_INTERFACE,
645 : : _("Unknown interface ‘%s’."),
646 : : interface_name);
647 : : }
648 : :
649 : : typedef struct
650 : : {
651 : : MctParentTimerService *parent_timer_service; /* (owned) */
652 : : uid_t uid;
653 : : MctTimerStoreRecordType record_type;
654 : : char *identifier; /* (owned) */
655 : : GDBusMethodInvocation *invocation; /* (owned) */
656 : : MctUser *parent_user; /* (nullable) (owned) */
657 : : MctOperationCounter operation_counter;
658 : : } QueryUsageData;
659 : :
660 : : static void
661 : 0 : query_usage_data_free (QueryUsageData *data)
662 : : {
663 [ # # ]: 0 : g_clear_pointer (&data->identifier, g_free);
664 [ # # ]: 0 : g_clear_object (&data->invocation);
665 [ # # ]: 0 : g_clear_object (&data->parent_user);
666 [ # # ]: 0 : g_clear_object (&data->parent_timer_service);
667 : 0 : mct_operation_counter_release_and_clear (&data->operation_counter);
668 : 0 : g_free (data);
669 : 0 : }
670 : :
671 [ # # ]: 0 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (QueryUsageData, query_usage_data_free)
672 : :
673 : : static void query_usage_ensure_credentials_cb (GObject *object,
674 : : GAsyncResult *result,
675 : : void *user_data);
676 : : static void query_usage_get_parent_user_cb (GObject *object,
677 : : GAsyncResult *result,
678 : : void *user_data);
679 : : static void query_usage_get_child_user_cb (GObject *object,
680 : : GAsyncResult *result,
681 : : void *user_data);
682 : : static void query_usage_open_username_cb (GObject *object,
683 : : GAsyncResult *result,
684 : : void *user_data);
685 : :
686 : : static void
687 : 0 : mct_parent_timer_service_query_usage (MctParentTimerService *self,
688 : : GDBusConnection *connection,
689 : : const char *sender,
690 : : GVariant *parameters,
691 : : GDBusMethodInvocation *invocation)
692 : : {
693 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
694 : : uid_t uid;
695 : : const char *record_type, *identifier;
696 [ # # ]: 0 : g_autoptr(QueryUsageData) data = NULL;
697 : :
698 : : /* Validate the parameters. */
699 : 0 : g_variant_get (parameters, "(u&s&s)", &uid, &record_type, &identifier);
700 : :
701 [ # # ]: 0 : if (!validate_record_type_and_identifier (invocation, record_type, identifier))
702 : 0 : return;
703 : :
704 : : /* Load the peer’s credentials so we know which user is querying the usage
705 : : * entries, and whether they’re allowed to do that for @uid. */
706 : 0 : data = g_new0 (QueryUsageData, 1);
707 : 0 : data->parent_timer_service = g_object_ref (self);
708 : 0 : data->uid = uid;
709 : 0 : data->record_type = mct_timer_store_record_type_from_string (record_type);
710 : 0 : data->identifier = g_strdup (identifier);
711 : 0 : data->invocation = g_object_ref (invocation);
712 : 0 : mct_operation_counter_init_and_hold (&data->operation_counter,
713 : : &self->n_pending_operations,
714 : 0 : G_OBJECT (self), props[PROP_BUSY]);
715 : :
716 : 0 : gss_peer_manager_ensure_peer_credentials_async (self->peer_manager,
717 : : sender,
718 : : self->cancellable,
719 : : query_usage_ensure_credentials_cb,
720 : : g_steal_pointer (&data));
721 : : }
722 : :
723 : : static void
724 : 0 : query_usage_ensure_credentials_cb (GObject *object,
725 : : GAsyncResult *result,
726 : : void *user_data)
727 : : {
728 : 0 : GssPeerManager *peer_manager = GSS_PEER_MANAGER (object);
729 [ # # ]: 0 : g_autoptr(QueryUsageData) data = g_steal_pointer (&user_data);
730 : 0 : MctParentTimerService *self = data->parent_timer_service;
731 : 0 : GDBusMethodInvocation *invocation = data->invocation;
732 : : uid_t peer_uid;
733 [ # # # # ]: 0 : g_autoptr(MctUser) parent_user = NULL, child_user = NULL;
734 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
735 : :
736 : : /* Finish looking up the sender. */
737 [ # # ]: 0 : if (!gss_peer_manager_ensure_peer_credentials_finish (peer_manager,
738 : : result, &local_error))
739 : : {
740 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_PARENT_TIMER_SERVICE_ERROR,
741 : : MCT_PARENT_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
742 : : _("Error identifying user: %s"),
743 : 0 : local_error->message);
744 : 0 : return;
745 : : }
746 : :
747 : : /* Load details of the family relationship of the two users. */
748 : 0 : g_assert (mct_user_manager_get_is_loaded (self->user_manager));
749 : :
750 : 0 : peer_uid = gss_peer_manager_get_peer_uid (peer_manager, g_dbus_method_invocation_get_sender (invocation));
751 [ # # # # ]: 0 : if (peer_uid == 0 || peer_uid == (uid_t) -1)
752 : : {
753 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_PARENT_TIMER_SERVICE_ERROR,
754 : : MCT_PARENT_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
755 : : _("Error identifying user: %s"),
756 : 0 : _("Invalid or unknown user"));
757 : 0 : return;
758 : : }
759 : :
760 : 0 : mct_user_manager_get_user_by_uid_async (self->user_manager, peer_uid, self->cancellable,
761 : 0 : query_usage_get_parent_user_cb, g_steal_pointer (&data));
762 : : }
763 : :
764 : : static void
765 : 0 : query_usage_get_parent_user_cb (GObject *object,
766 : : GAsyncResult *result,
767 : : void *user_data)
768 : : {
769 : 0 : MctUserManager *user_manager = MCT_USER_MANAGER (object);
770 [ # # ]: 0 : g_autoptr(QueryUsageData) data = g_steal_pointer (&user_data);
771 : 0 : MctParentTimerService *self = data->parent_timer_service;
772 : 0 : GDBusMethodInvocation *invocation = data->invocation;
773 : : uid_t uid;
774 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
775 : :
776 : 0 : data->parent_user = mct_user_manager_get_user_by_uid_finish (user_manager, result, &local_error);
777 : :
778 [ # # ]: 0 : if (data->parent_user == NULL)
779 : : {
780 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_PARENT_TIMER_SERVICE_ERROR,
781 : : MCT_PARENT_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
782 : : _("Error identifying user: %s"),
783 : 0 : _("Invalid or unknown user"));
784 : 0 : return;
785 : : }
786 : :
787 : 0 : uid = data->uid;
788 : 0 : mct_user_manager_get_user_by_uid_async (user_manager, uid, self->cancellable,
789 : 0 : query_usage_get_child_user_cb, g_steal_pointer (&data));
790 : : }
791 : :
792 : : static void
793 : 0 : query_usage_get_child_user_cb (GObject *object,
794 : : GAsyncResult *result,
795 : : void *user_data)
796 : : {
797 : 0 : MctUserManager *user_manager = MCT_USER_MANAGER (object);
798 [ # # ]: 0 : g_autoptr(QueryUsageData) data = g_steal_pointer (&user_data);
799 : 0 : MctParentTimerService *self = data->parent_timer_service;
800 : 0 : GDBusMethodInvocation *invocation = data->invocation;
801 [ # # ]: 0 : g_autoptr(MctUser) child_user = NULL;
802 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
803 : :
804 : 0 : child_user = mct_user_manager_get_user_by_uid_finish (user_manager, result, &local_error);
805 : :
806 [ # # ]: 0 : if (child_user == NULL)
807 : : {
808 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_PARENT_TIMER_SERVICE_ERROR,
809 : : MCT_PARENT_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
810 : : _("Error identifying user: %s"),
811 : 0 : _("Invalid or unknown user"));
812 : 0 : return;
813 : : }
814 : :
815 : : /* Check that the sender is actually a parent of @data->uid */
816 [ # # ]: 0 : if (!mct_user_is_parent_of (data->parent_user, child_user))
817 : : {
818 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_PARENT_TIMER_SERVICE_ERROR,
819 : : MCT_PARENT_TIMER_SERVICE_ERROR_PERMISSION_DENIED,
820 : : _("Permission denied to query user usage"));
821 : 0 : return;
822 : : }
823 : :
824 : : /* Proceed to open the database for the child user so we can query their records. */
825 : 0 : mct_timer_store_open_username_async (self->timer_store,
826 : : mct_user_get_username (child_user),
827 : : self->cancellable,
828 : : query_usage_open_username_cb,
829 : 0 : g_steal_pointer (&data));
830 : : }
831 : :
832 : : static void
833 : 0 : query_usage_open_username_cb (GObject *object,
834 : : GAsyncResult *result,
835 : : void *user_data)
836 : : {
837 : 0 : MctTimerStore *timer_store = MCT_TIMER_STORE (object);
838 [ # # ]: 0 : g_autoptr(QueryUsageData) data = g_steal_pointer (&user_data);
839 : 0 : GDBusMethodInvocation *invocation = data->invocation;
840 : : const MctTimerStoreTransaction *transaction;
841 [ # # ]: 0 : g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(tt)"));
842 : 0 : const MctTimeSpan * const *time_spans = NULL;
843 : 0 : size_t n_time_spans = 0;
844 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
845 : :
846 : 0 : transaction = mct_timer_store_open_username_finish (timer_store, result, &local_error);
847 : :
848 [ # # ]: 0 : if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY))
849 : : {
850 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_PARENT_TIMER_SERVICE_ERROR,
851 : : MCT_PARENT_TIMER_SERVICE_ERROR_BUSY,
852 : : _("Error opening user file: %s"),
853 : 0 : local_error->message);
854 : 0 : return;
855 : : }
856 [ # # ]: 0 : else if (local_error != NULL) /* likely a GFileError */
857 : : {
858 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_PARENT_TIMER_SERVICE_ERROR,
859 : : MCT_PARENT_TIMER_SERVICE_ERROR_STORAGE_ERROR,
860 : : _("Error opening user file: %s"),
861 : 0 : local_error->message);
862 : 0 : return;
863 : : }
864 : :
865 : : /* Query the timer store for all the matching records */
866 : 0 : time_spans = mct_timer_store_query_time_spans (timer_store, transaction,
867 : 0 : data->record_type,
868 : 0 : data->identifier,
869 : : &n_time_spans);
870 : :
871 [ # # ]: 0 : for (size_t i = 0; i < n_time_spans; i++)
872 : : {
873 : 0 : const MctTimeSpan *time_span = time_spans[i];
874 : :
875 : 0 : g_variant_builder_add (&builder, "(tt)",
876 : : mct_time_span_get_start_time_secs (time_span),
877 : : mct_time_span_get_end_time_secs (time_span));
878 : : }
879 : :
880 : : /* Close the file again. */
881 : 0 : mct_timer_store_roll_back_transaction (timer_store, transaction);
882 : :
883 : : /* Return the results */
884 : 0 : g_dbus_method_invocation_return_value (invocation,
885 : : g_variant_new ("(@a(tt))",
886 : : g_variant_builder_end (&builder)));
887 : : }
888 : :
889 : : /**
890 : : * mct_parent_timer_service_new:
891 : : * @connection: (transfer none): D-Bus connection to export objects on
892 : : * @object_path: root path to export objects below; must be a valid D-Bus object
893 : : * path
894 : : * @timer_store: (transfer none): store to use for timer data
895 : : * @user_manager: (transfer none): user manager for querying family relationships
896 : : * @peer_manager: (transfer none): peer manager for querying D-Bus peers
897 : : *
898 : : * Create a new [class@Malcontent.ParentTimerService] instance which is set up
899 : : * to run as a service.
900 : : *
901 : : * Returns: (transfer full): a new [class@Malcontent.ParentTimerService]
902 : : * Since: 0.14.0
903 : : */
904 : : MctParentTimerService *
905 : 1 : mct_parent_timer_service_new (GDBusConnection *connection,
906 : : const char *object_path,
907 : : MctTimerStore *timer_store,
908 : : MctUserManager *user_manager,
909 : : GssPeerManager *peer_manager)
910 : : {
911 : 1 : g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
912 : 1 : g_return_val_if_fail (g_variant_is_object_path (object_path), NULL);
913 : 1 : g_return_val_if_fail (MCT_IS_TIMER_STORE (timer_store), NULL);
914 : 1 : g_return_val_if_fail (MCT_IS_USER_MANAGER (user_manager), NULL);
915 : 1 : g_return_val_if_fail (GSS_IS_PEER_MANAGER (peer_manager), NULL);
916 : :
917 : 1 : return g_object_new (MCT_TYPE_PARENT_TIMER_SERVICE,
918 : : "connection", connection,
919 : : "object-path", object_path,
920 : : "timer-store", timer_store,
921 : : "user-manager", user_manager,
922 : : "peer-manager", peer_manager,
923 : : NULL);
924 : : }
925 : :
926 : : /**
927 : : * mct_parent_timer_service_get_busy:
928 : : * @self: a parent service
929 : : *
930 : : * Get the value of [property@Malcontent.ParentTimerService.busy].
931 : : *
932 : : * Returns: true if the service is busy, false otherwise
933 : : * Since: 0.14.0
934 : : */
935 : : gboolean
936 : 5 : mct_parent_timer_service_get_busy (MctParentTimerService *self)
937 : : {
938 : 5 : g_return_val_if_fail (MCT_IS_PARENT_TIMER_SERVICE (self), FALSE);
939 : :
940 [ + + - + ]: 5 : return (self->object_id != 0 && self->n_pending_operations > 0);
941 : : }
942 : :
|