Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright 2024, 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 <errno.h>
28 : : #include <glib.h>
29 : : #include <glib-unix.h>
30 : : #include <glib/gi18n-lib.h>
31 : : #include <gio/gio.h>
32 : : #include <libgsystemservice/peer-manager.h>
33 : : #include <libmalcontent/manager.h>
34 : : #include <libmalcontent-timer/child-timer-service.h>
35 : : #include <libmalcontent-timer/time-span.h>
36 : : #include <libmalcontent-timer/timer-store.h>
37 : : #include <pwd.h>
38 : : #include <string.h>
39 : : #include <sys/types.h>
40 : :
41 : : #include "child-iface.h"
42 : : #include "enums.h"
43 : : #include "operation-counter-private.h"
44 : :
45 : :
46 : : static void mct_child_timer_service_constructed (GObject *object);
47 : : static void mct_child_timer_service_dispose (GObject *object);
48 : : static void mct_child_timer_service_get_property (GObject *object,
49 : : guint property_id,
50 : : GValue *value,
51 : : GParamSpec *pspec);
52 : : static void mct_child_timer_service_set_property (GObject *object,
53 : : guint property_id,
54 : : const GValue *value,
55 : : GParamSpec *pspec);
56 : :
57 : : static void mct_child_timer_service_method_call (GDBusConnection *connection,
58 : : const char *sender,
59 : : const char *object_path,
60 : : const char *interface_name,
61 : : const char *method_name,
62 : : GVariant *parameters,
63 : : GDBusMethodInvocation *invocation,
64 : : void *user_data);
65 : : static void mct_child_timer_service_properties_get (MctChildTimerService *self,
66 : : GDBusConnection *connection,
67 : : const char *sender,
68 : : GVariant *parameters,
69 : : GDBusMethodInvocation *invocation);
70 : : static void mct_child_timer_service_properties_set (MctChildTimerService *self,
71 : : GDBusConnection *connection,
72 : : const char *sender,
73 : : GVariant *parameters,
74 : : GDBusMethodInvocation *invocation);
75 : : static void mct_child_timer_service_properties_get_all (MctChildTimerService *self,
76 : : GDBusConnection *connection,
77 : : const char *sender,
78 : : GVariant *parameters,
79 : : GDBusMethodInvocation *invocation);
80 : :
81 : : static void timer_store_estimated_end_times_changed_cb (MctTimerStore *timer_store,
82 : : const char *username,
83 : : void *user_data);
84 : : static void mct_child_timer_service_record_usage (MctChildTimerService *self,
85 : : GDBusConnection *connection,
86 : : const char *sender,
87 : : GVariant *parameters,
88 : : GDBusMethodInvocation *invocation);
89 : : static void mct_child_timer_service_get_estimated_times (MctChildTimerService *self,
90 : : GDBusConnection *connection,
91 : : const char *sender,
92 : : GVariant *parameters,
93 : : GDBusMethodInvocation *invocation);
94 : :
95 : : /* These errors do go over the bus, and are registered in mct_child_timer_service_class_init(). */
96 [ + + ]: 6 : G_DEFINE_QUARK (MctChildTimerServiceError, mct_child_timer_service_error)
97 : :
98 : : static const gchar *child_timer_service_errors[] =
99 : : {
100 : : "org.freedesktop.MalcontentTimer1.Child.Error.InvalidRecord",
101 : : "org.freedesktop.MalcontentTimer1.Child.Error.StorageError",
102 : : "org.freedesktop.MalcontentTimer1.Child.Error.Busy",
103 : : "org.freedesktop.MalcontentTimer1.Child.Error.IdentifyingUser",
104 : : "org.freedesktop.MalcontentTimer1.Child.Error.QueryingPolicy",
105 : : "org.freedesktop.MalcontentTimer1.Child.Error.Disabled",
106 : : };
107 : : static const GDBusErrorEntry child_timer_service_error_map[] =
108 : : {
109 : : { MCT_CHILD_TIMER_SERVICE_ERROR_INVALID_RECORD, "org.freedesktop.MalcontentTimer1.Child.Error.InvalidRecord" },
110 : : { MCT_CHILD_TIMER_SERVICE_ERROR_STORAGE_ERROR, "org.freedesktop.MalcontentTimer1.Child.Error.StorageError" },
111 : : { MCT_CHILD_TIMER_SERVICE_ERROR_BUSY, "org.freedesktop.MalcontentTimer1.Child.Error.Busy" },
112 : : { MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER, "org.freedesktop.MalcontentTimer1.Child.Error.IdentifyingUser" },
113 : : { MCT_CHILD_TIMER_SERVICE_ERROR_QUERYING_POLICY, "org.freedesktop.MalcontentTimer1.Child.Error.QueryingPolicy" },
114 : : { MCT_CHILD_TIMER_SERVICE_ERROR_DISABLED, "org.freedesktop.MalcontentTimer1.Child.Error.Disabled" },
115 : :
116 : : };
117 : : G_STATIC_ASSERT (G_N_ELEMENTS (child_timer_service_error_map) == MCT_CHILD_TIMER_SERVICE_N_ERRORS);
118 : : G_STATIC_ASSERT (G_N_ELEMENTS (child_timer_service_error_map) == G_N_ELEMENTS (child_timer_service_errors));
119 : :
120 : : /**
121 : : * MctChildTimerService:
122 : : *
123 : : * An implementation of the `org.freedesktop.MalcontentTimer1.Child` D-Bus
124 : : * interface, allowing a trusted component in a child user account’s session to
125 : : * record screen time and app usage periods for that account.
126 : : *
127 : : * This will expose all the necessary objects on the bus for peers to interact
128 : : * with them, and hooks them up to internal state management using
129 : : * [property@Malcontent.ChildTimerService:timer-store].
130 : : *
131 : : * Since: 0.14.0
132 : : */
133 : : struct _MctChildTimerService
134 : : {
135 : : GObject parent;
136 : :
137 : : GDBusConnection *connection; /* (owned) */
138 : : char *object_path; /* (owned) */
139 : : unsigned int object_id;
140 : :
141 : : /* Used to cancel any pending operations when the object is unregistered. */
142 : : GCancellable *cancellable; /* (owned) */
143 : :
144 : : MctTimerStore *timer_store; /* (owned) */
145 : : unsigned long timer_store_estimated_end_times_changed_id;
146 : : GssPeerManager *peer_manager; /* (owned) */
147 : : MctManager *policy_manager; /* (owned) */
148 : : unsigned int n_pending_operations;
149 : : };
150 : :
151 : : typedef enum
152 : : {
153 : : PROP_CONNECTION = 1,
154 : : PROP_OBJECT_PATH,
155 : : PROP_TIMER_STORE,
156 : : PROP_PEER_MANAGER,
157 : : PROP_POLICY_MANAGER,
158 : : PROP_BUSY,
159 : : } MctChildTimerServiceProperty;
160 : :
161 : : static GParamSpec *props[PROP_BUSY + 1] = { NULL, };
162 : :
163 [ + + + - : 14 : G_DEFINE_TYPE (MctChildTimerService, mct_child_timer_service, G_TYPE_OBJECT)
+ + ]
164 : :
165 : : static void
166 : 1 : mct_child_timer_service_class_init (MctChildTimerServiceClass *klass)
167 : : {
168 : 1 : GObjectClass *object_class = (GObjectClass *) klass;
169 : :
170 : 1 : object_class->constructed = mct_child_timer_service_constructed;
171 : 1 : object_class->dispose = mct_child_timer_service_dispose;
172 : 1 : object_class->get_property = mct_child_timer_service_get_property;
173 : 1 : object_class->set_property = mct_child_timer_service_set_property;
174 : :
175 : : /**
176 : : * MctChildTimerService:connection:
177 : : *
178 : : * D-Bus connection to export objects on.
179 : : *
180 : : * Since: 0.14.0
181 : : */
182 : 1 : props[PROP_CONNECTION] =
183 : 1 : g_param_spec_object ("connection", NULL, NULL,
184 : : G_TYPE_DBUS_CONNECTION,
185 : : G_PARAM_READWRITE |
186 : : G_PARAM_CONSTRUCT_ONLY |
187 : : G_PARAM_STATIC_STRINGS);
188 : :
189 : : /**
190 : : * MctChildTimerService:object-path:
191 : : *
192 : : * Object path to root all exported objects at. If this does not end in a
193 : : * slash, one will be added.
194 : : *
195 : : * Since: 0.14.0
196 : : */
197 : 1 : props[PROP_OBJECT_PATH] =
198 : 1 : g_param_spec_string ("object-path", NULL, NULL,
199 : : "/",
200 : : G_PARAM_READWRITE |
201 : : G_PARAM_CONSTRUCT_ONLY |
202 : : G_PARAM_STATIC_STRINGS);
203 : :
204 : : /**
205 : : * MctChildTimerService:timer-store:
206 : : *
207 : : * Store for timer data.
208 : : *
209 : : * Since: 0.14.0
210 : : */
211 : 1 : props[PROP_TIMER_STORE] =
212 : 1 : g_param_spec_object ("timer-store", NULL, NULL,
213 : : MCT_TYPE_TIMER_STORE,
214 : : G_PARAM_READWRITE |
215 : : G_PARAM_CONSTRUCT_ONLY |
216 : : G_PARAM_STATIC_STRINGS);
217 : :
218 : : /**
219 : : * MctChildTimerService:peer-manager:
220 : : *
221 : : * Peer manager to identify incoming method calls.
222 : : *
223 : : * Since: 0.14.0
224 : : */
225 : 1 : props[PROP_PEER_MANAGER] =
226 : 1 : g_param_spec_object ("peer-manager", NULL, NULL,
227 : : GSS_TYPE_PEER_MANAGER,
228 : : G_PARAM_READWRITE |
229 : : G_PARAM_CONSTRUCT_ONLY |
230 : : G_PARAM_STATIC_STRINGS);
231 : :
232 : : /**
233 : : * MctChildTimerService:policy-manager:
234 : : *
235 : : * Policy manager to provide users’ parental controls policies.
236 : : *
237 : : * Since: 0.14.0
238 : : */
239 : 1 : props[PROP_POLICY_MANAGER] =
240 : 1 : g_param_spec_object ("policy-manager", NULL, NULL,
241 : : MCT_TYPE_MANAGER,
242 : : G_PARAM_READWRITE |
243 : : G_PARAM_CONSTRUCT_ONLY |
244 : : G_PARAM_STATIC_STRINGS);
245 : :
246 : : /**
247 : : * MctChildTimerService:busy:
248 : : *
249 : : * True if the D-Bus API is busy.
250 : : *
251 : : * For example, if there are any outstanding method calls which haven’t been
252 : : * replied to yet.
253 : : *
254 : : * Since: 0.14.0
255 : : */
256 : 1 : props[PROP_BUSY] =
257 : 1 : g_param_spec_boolean ("busy", NULL, NULL,
258 : : FALSE,
259 : : G_PARAM_READABLE |
260 : : G_PARAM_STATIC_STRINGS);
261 : :
262 : 1 : g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
263 : :
264 : : /* Error domain registration for D-Bus. We do this here, rather than in a
265 : : * #GOnce section in mct_child_timer_service_error_quark(), to avoid spreading the
266 : : * D-Bus code outside this file. */
267 [ + + ]: 7 : for (size_t i = 0; i < G_N_ELEMENTS (child_timer_service_error_map); i++)
268 : 6 : g_dbus_error_register_error (MCT_CHILD_TIMER_SERVICE_ERROR,
269 : 6 : child_timer_service_error_map[i].error_code,
270 : 6 : child_timer_service_error_map[i].dbus_error_name);
271 : 1 : }
272 : :
273 : : static void
274 : 1 : mct_child_timer_service_init (MctChildTimerService *self)
275 : : {
276 : 1 : self->cancellable = g_cancellable_new ();
277 : 1 : }
278 : :
279 : : static void
280 : 1 : mct_child_timer_service_constructed (GObject *object)
281 : : {
282 : 1 : MctChildTimerService *self = MCT_CHILD_TIMER_SERVICE (object);
283 : :
284 : : /* Chain up. */
285 : 1 : G_OBJECT_CLASS (mct_child_timer_service_parent_class)->constructed (object);
286 : :
287 : : /* Check our construct properties. */
288 : 1 : g_assert (G_IS_DBUS_CONNECTION (self->connection));
289 : 1 : g_assert (g_variant_is_object_path (self->object_path));
290 : 1 : g_assert (MCT_IS_TIMER_STORE (self->timer_store));
291 : 1 : g_assert (GSS_IS_PEER_MANAGER (self->peer_manager));
292 : 1 : g_assert (MCT_IS_MANAGER (self->policy_manager));
293 : :
294 : : /* Connect to signals on the timer store. */
295 : 1 : self->timer_store_estimated_end_times_changed_id =
296 : 1 : g_signal_connect (self->timer_store, "estimated-end-times-changed",
297 : : G_CALLBACK (timer_store_estimated_end_times_changed_cb),
298 : : self);
299 : 1 : }
300 : :
301 : : static void
302 : 0 : mct_child_timer_service_dispose (GObject *object)
303 : : {
304 : 0 : MctChildTimerService *self = MCT_CHILD_TIMER_SERVICE (object);
305 : :
306 : 0 : g_assert (self->object_id == 0);
307 : 0 : g_assert (self->n_pending_operations == 0);
308 : :
309 [ # # ]: 0 : g_clear_object (&self->policy_manager);
310 [ # # ]: 0 : g_clear_object (&self->peer_manager);
311 [ # # ]: 0 : g_clear_signal_handler (&self->timer_store_estimated_end_times_changed_id, self->timer_store);
312 [ # # ]: 0 : g_clear_object (&self->timer_store);
313 : :
314 [ # # ]: 0 : g_clear_object (&self->connection);
315 [ # # ]: 0 : g_clear_pointer (&self->object_path, g_free);
316 [ # # ]: 0 : g_clear_object (&self->cancellable);
317 : :
318 : : /* Chain up to the parent class */
319 : 0 : G_OBJECT_CLASS (mct_child_timer_service_parent_class)->dispose (object);
320 : 0 : }
321 : :
322 : : static void
323 : 0 : mct_child_timer_service_get_property (GObject *object,
324 : : guint property_id,
325 : : GValue *value,
326 : : GParamSpec *pspec)
327 : : {
328 : 0 : MctChildTimerService *self = MCT_CHILD_TIMER_SERVICE (object);
329 : :
330 [ # # # # : 0 : switch ((MctChildTimerServiceProperty) property_id)
# # # ]
331 : : {
332 : 0 : case PROP_CONNECTION:
333 : 0 : g_value_set_object (value, self->connection);
334 : 0 : break;
335 : 0 : case PROP_OBJECT_PATH:
336 : 0 : g_value_set_string (value, self->object_path);
337 : 0 : break;
338 : 0 : case PROP_TIMER_STORE:
339 : 0 : g_value_set_object (value, self->timer_store);
340 : 0 : break;
341 : 0 : case PROP_PEER_MANAGER:
342 : 0 : g_value_set_object (value, self->peer_manager);
343 : 0 : break;
344 : 0 : case PROP_POLICY_MANAGER:
345 : 0 : g_value_set_object (value, self->policy_manager);
346 : 0 : break;
347 : 0 : case PROP_BUSY:
348 : 0 : g_value_set_boolean (value, mct_child_timer_service_get_busy (self));
349 : 0 : break;
350 : 0 : default:
351 : : g_assert_not_reached ();
352 : : }
353 : 0 : }
354 : :
355 : : static void
356 : 5 : mct_child_timer_service_set_property (GObject *object,
357 : : guint property_id,
358 : : const GValue *value,
359 : : GParamSpec *pspec)
360 : : {
361 : 5 : MctChildTimerService *self = MCT_CHILD_TIMER_SERVICE (object);
362 : :
363 [ + + + + : 5 : switch ((MctChildTimerServiceProperty) property_id)
+ - ]
364 : : {
365 : 1 : case PROP_CONNECTION:
366 : : /* Construct only. */
367 : 1 : g_assert (self->connection == NULL);
368 : 1 : self->connection = g_value_dup_object (value);
369 : 1 : break;
370 : 1 : case PROP_OBJECT_PATH:
371 : : /* Construct only. */
372 : 1 : g_assert (self->object_path == NULL);
373 : 1 : g_assert (g_variant_is_object_path (g_value_get_string (value)));
374 : 1 : self->object_path = g_value_dup_string (value);
375 : 1 : break;
376 : 1 : case PROP_TIMER_STORE:
377 : : /* Construct only. */
378 : 1 : g_assert (self->timer_store == NULL);
379 : 1 : self->timer_store = g_value_dup_object (value);
380 : 1 : break;
381 : 1 : case PROP_PEER_MANAGER:
382 : : /* Construct only. */
383 : 1 : g_assert (self->peer_manager == NULL);
384 : 1 : self->peer_manager = g_value_dup_object (value);
385 : 1 : break;
386 : 1 : case PROP_POLICY_MANAGER:
387 : : /* Construct only. */
388 : 1 : g_assert (self->policy_manager == NULL);
389 : 1 : self->policy_manager = g_value_dup_object (value);
390 : 1 : break;
391 : 0 : case PROP_BUSY:
392 : : /* Read only. Fall through. */
393 : : G_GNUC_FALLTHROUGH;
394 : : default:
395 : : g_assert_not_reached ();
396 : : }
397 : 5 : }
398 : :
399 : : static void
400 : 0 : timer_store_estimated_end_times_changed_cb (MctTimerStore *timer_store,
401 : : const char *username,
402 : : void *user_data)
403 : : {
404 : 0 : MctChildTimerService *self = MCT_CHILD_TIMER_SERVICE (user_data);
405 : :
406 : : /* Ignore errors from emitting the signal; it can only fail if the parameters
407 : : * are invalid (not possible) or if the connection has been closed. */
408 : 0 : g_dbus_connection_emit_signal (self->connection,
409 : : NULL, /* destination bus name */
410 : 0 : self->object_path,
411 : : "org.freedesktop.MalcontentTimer1.Child",
412 : : "EstimatedTimesChanged",
413 : : NULL,
414 : : NULL);
415 : 0 : }
416 : :
417 : : /**
418 : : * mct_child_timer_service_register:
419 : : * @self: a child service
420 : : * @error: return location for a [type@GLib.Error]
421 : : *
422 : : * Register the schedule service objects on D-Bus using the connection details
423 : : * given in [property@Malcontent.ChildTimerService.connection] and
424 : : * [property@Malcontent.ChildTimerService.object-path].
425 : : *
426 : : * Use [method@Malcontent.ChildTimerService.unregister] to unregister them.
427 : : * Calls to these two functions must be well paired.
428 : : *
429 : : * Returns: true on success, false otherwise
430 : : * Since: 0.14.0
431 : : */
432 : : gboolean
433 : 1 : mct_child_timer_service_register (MctChildTimerService *self,
434 : : GError **error)
435 : : {
436 : 1 : g_return_val_if_fail (MCT_IS_CHILD_TIMER_SERVICE (self), FALSE);
437 : 1 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
438 : :
439 : 1 : const GDBusInterfaceVTable interface_vtable =
440 : : {
441 : : mct_child_timer_service_method_call,
442 : : NULL, /* handled in mct_child_timer_service_method_call() */
443 : : NULL, /* handled in mct_child_timer_service_method_call() */
444 : : { NULL, }, /* padding */
445 : : };
446 : :
447 : 1 : guint id = g_dbus_connection_register_object (self->connection,
448 : 1 : self->object_path,
449 : : (GDBusInterfaceInfo *) &org_freedesktop_malcontent_timer1_child_interface,
450 : : &interface_vtable,
451 : : g_object_ref (self),
452 : : g_object_unref,
453 : : error);
454 : :
455 [ - + ]: 1 : if (id == 0)
456 : 0 : return FALSE;
457 : :
458 : 1 : self->object_id = id;
459 : :
460 : : /* This has potentially changed. */
461 : 1 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUSY]);
462 : :
463 : 1 : return TRUE;
464 : : }
465 : :
466 : : /**
467 : : * mct_child_timer_service_unregister:
468 : : * @self: a child service
469 : : *
470 : : * Unregister objects from D-Bus which were previously registered using
471 : : * [method@Malcontent.ChildTimerService.register].
472 : : *
473 : : * Calls to these two functions must be well paired.
474 : : *
475 : : * Since: 0.14.0
476 : : */
477 : : void
478 : 1 : mct_child_timer_service_unregister (MctChildTimerService *self)
479 : : {
480 : 1 : g_return_if_fail (MCT_IS_CHILD_TIMER_SERVICE (self));
481 : :
482 : 1 : g_dbus_connection_unregister_object (self->connection, self->object_id);
483 : 1 : self->object_id = 0;
484 : :
485 : : /* This has potentially changed. */
486 : 1 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUSY]);
487 : : }
488 : :
489 : : static char *
490 : 0 : lookup_username (uid_t uid,
491 : : GError **error)
492 : : {
493 : : char buffer[4096];
494 : : struct passwd pwbuf;
495 : : struct passwd *result;
496 : : int pwuid_errno;
497 : 0 : g_autofree char *username = NULL;
498 : 0 : g_autoptr(GError) local_error = NULL;
499 : :
500 : 0 : pwuid_errno = getpwuid_r (uid, &pwbuf, buffer, sizeof (buffer), &result);
501 : :
502 [ # # ]: 0 : if (result != NULL &&
503 [ # # # # ]: 0 : result->pw_name != NULL && result->pw_name[0] != '\0')
504 : : {
505 : 0 : username = g_locale_to_utf8 (result->pw_name, -1, NULL, NULL, &local_error);
506 [ # # ]: 0 : if (username == NULL)
507 : : {
508 : 0 : g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
509 : : _("Error getting details for UID %d: %s"),
510 : 0 : (int) uid, local_error->message);
511 : 0 : return NULL;
512 : : }
513 : : }
514 [ # # ]: 0 : else if (result != NULL)
515 : : {
516 : 0 : username = g_strdup_printf ("%d", (int) uid);
517 : : }
518 [ # # ]: 0 : else if (pwuid_errno == 0)
519 : : {
520 : : /* User not found. */
521 : 0 : return NULL;
522 : : }
523 : : else
524 : : {
525 : : /* Error calling getpwuid_r(). */
526 : 0 : g_set_error (error, G_IO_ERROR, g_io_error_from_errno (pwuid_errno),
527 : : _("Error getting details for UID %d: %s"),
528 : : (int) uid, g_strerror (pwuid_errno));
529 : 0 : return NULL;
530 : : }
531 : :
532 : 0 : return g_steal_pointer (&username);
533 : : }
534 : :
535 : : static char *
536 : 0 : lookup_username_for_peer_invocation (GssPeerManager *peer_manager,
537 : : GDBusMethodInvocation *invocation,
538 : : uid_t *out_uid)
539 : : {
540 : : uid_t uid;
541 : 0 : g_autoptr(GError) local_error = NULL;
542 : 0 : g_autofree char *username = NULL;
543 : :
544 : 0 : uid = gss_peer_manager_get_peer_uid (peer_manager, g_dbus_method_invocation_get_sender (invocation));
545 [ # # # # ]: 0 : if (uid != 0 && uid != (uid_t) -1)
546 : 0 : username = lookup_username (uid, &local_error);
547 [ # # ]: 0 : if (local_error != NULL)
548 : 0 : g_debug ("Error looking up username for UID %d: %s", (int) uid, local_error->message);
549 : :
550 [ # # ]: 0 : if (out_uid != NULL)
551 : 0 : *out_uid = uid;
552 : :
553 : 0 : return g_steal_pointer (&username);
554 : : }
555 : :
556 : : static gboolean
557 : 0 : validate_dbus_interface_name (GDBusMethodInvocation *invocation,
558 : : const gchar *interface_name)
559 : : {
560 [ # # ]: 0 : if (!g_dbus_is_interface_name (interface_name))
561 : : {
562 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
563 : : G_DBUS_ERROR_UNKNOWN_INTERFACE,
564 : : _("Invalid interface name ‘%s’."),
565 : : interface_name);
566 : 0 : return FALSE;
567 : : }
568 : :
569 : 0 : return TRUE;
570 : : }
571 : :
572 : : static gboolean
573 : 0 : validate_record_type (GDBusMethodInvocation *invocation,
574 : : const char *record_type)
575 : : {
576 : 0 : g_autoptr(GError) local_error = NULL;
577 : :
578 [ # # ]: 0 : if (!mct_timer_store_record_type_validate_string (record_type, &local_error))
579 : : {
580 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
581 : : MCT_CHILD_TIMER_SERVICE_ERROR_INVALID_RECORD,
582 : : _("Invalid usage parameters: %s"),
583 : 0 : local_error->message);
584 : 0 : return FALSE;
585 : : }
586 : :
587 : 0 : return TRUE;
588 : : }
589 : :
590 : : static gboolean
591 : 0 : validate_record_type_and_identifier (GDBusMethodInvocation *invocation,
592 : : const char *record_type,
593 : : const char *identifier)
594 : : {
595 : 0 : g_autoptr(GError) local_error = NULL;
596 : :
597 [ # # # # ]: 0 : if (!mct_timer_store_record_type_validate_string (record_type, &local_error) ||
598 : 0 : !mct_timer_store_record_type_validate_identifier (mct_timer_store_record_type_from_string (record_type), identifier, &local_error))
599 : : {
600 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
601 : : MCT_CHILD_TIMER_SERVICE_ERROR_INVALID_RECORD,
602 : : _("Invalid usage parameters: %s"),
603 : 0 : local_error->message);
604 : 0 : return FALSE;
605 : : }
606 : :
607 : 0 : return TRUE;
608 : : }
609 : :
610 : : typedef void (*ChildMethodCallFunc) (MctChildTimerService *self,
611 : : GDBusConnection *connection,
612 : : const char *sender,
613 : : GVariant *parameters,
614 : : GDBusMethodInvocation *invocation);
615 : :
616 : : static const struct
617 : : {
618 : : const char *interface_name;
619 : : const char *method_name;
620 : : ChildMethodCallFunc func;
621 : : }
622 : : child_methods[] =
623 : : {
624 : : /* Handle properties. */
625 : : { "org.freedesktop.DBus.Properties", "Get",
626 : : mct_child_timer_service_properties_get },
627 : : { "org.freedesktop.DBus.Properties", "Set",
628 : : mct_child_timer_service_properties_set },
629 : : { "org.freedesktop.DBus.Properties", "GetAll",
630 : : mct_child_timer_service_properties_get_all },
631 : :
632 : : /* Child methods. */
633 : : { "org.freedesktop.MalcontentTimer1.Child", "RecordUsage",
634 : : mct_child_timer_service_record_usage },
635 : : { "org.freedesktop.MalcontentTimer1.Child", "GetEstimatedTimes",
636 : : mct_child_timer_service_get_estimated_times },
637 : : };
638 : :
639 : : static void
640 : 0 : mct_child_timer_service_method_call (GDBusConnection *connection,
641 : : const char *sender,
642 : : const char *object_path,
643 : : const char *interface_name,
644 : : const char *method_name,
645 : : GVariant *parameters,
646 : : GDBusMethodInvocation *invocation,
647 : : void *user_data)
648 : : {
649 : 0 : MctChildTimerService *self = MCT_CHILD_TIMER_SERVICE (user_data);
650 : :
651 : : /* Check we’ve implemented all the methods. Unfortunately this can’t be a
652 : : * compile time check because the method array is declared in a separate
653 : : * compilation unit. */
654 : 0 : size_t n_child_interface_methods = 0;
655 [ # # ]: 0 : for (size_t i = 0; org_freedesktop_malcontent_timer1_child_interface.methods[i] != NULL; i++)
656 : 0 : n_child_interface_methods++;
657 : :
658 : 0 : g_assert (G_N_ELEMENTS (child_methods) ==
659 : : n_child_interface_methods +
660 : : 3 /* o.fdo.DBus.Properties */);
661 : :
662 : : /* Remove the service prefix from the path. */
663 : 0 : g_assert (g_str_equal (object_path, self->object_path));
664 : :
665 : : /* Work out which method to call. */
666 [ # # ]: 0 : for (gsize i = 0; i < G_N_ELEMENTS (child_methods); i++)
667 : : {
668 [ # # ]: 0 : if (g_str_equal (child_methods[i].interface_name, interface_name) &&
669 [ # # ]: 0 : g_str_equal (child_methods[i].method_name, method_name))
670 : : {
671 : 0 : child_methods[i].func (self, connection, sender, parameters, invocation);
672 : 0 : return;
673 : : }
674 : : }
675 : :
676 : : /* Make sure we actually called a method implementation. GIO guarantees that
677 : : * this function is only called with methods we’ve declared in the interface
678 : : * info, so this should never fail. */
679 : : g_assert_not_reached ();
680 : : }
681 : :
682 : : static void
683 : 0 : mct_child_timer_service_properties_get (MctChildTimerService *self,
684 : : GDBusConnection *connection,
685 : : const char *sender,
686 : : GVariant *parameters,
687 : : GDBusMethodInvocation *invocation)
688 : : {
689 : : const char *interface_name, *property_name;
690 : 0 : g_variant_get (parameters, "(&s&s)", &interface_name, &property_name);
691 : :
692 : : /* D-Bus property names can be anything. */
693 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
694 : 0 : return;
695 : :
696 : : /* No properties exposed. */
697 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
698 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
699 : : _("Unknown property ‘%s.%s’."),
700 : : interface_name, property_name);
701 : : }
702 : :
703 : : static void
704 : 0 : mct_child_timer_service_properties_set (MctChildTimerService *self,
705 : : GDBusConnection *connection,
706 : : const char *sender,
707 : : GVariant *parameters,
708 : : GDBusMethodInvocation *invocation)
709 : : {
710 : : const char *interface_name, *property_name;
711 : 0 : g_variant_get (parameters, "(&s&sv)", &interface_name, &property_name, NULL);
712 : :
713 : : /* D-Bus property names can be anything. */
714 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
715 : 0 : return;
716 : :
717 : : /* No properties exposed. */
718 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
719 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
720 : : _("Unknown property ‘%s.%s’."),
721 : : interface_name, property_name);
722 : : }
723 : :
724 : : static void
725 : 0 : mct_child_timer_service_properties_get_all (MctChildTimerService *self,
726 : : GDBusConnection *connection,
727 : : const char *sender,
728 : : GVariant *parameters,
729 : : GDBusMethodInvocation *invocation)
730 : : {
731 : : const char *interface_name;
732 : 0 : g_variant_get (parameters, "(&s)", &interface_name);
733 : :
734 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
735 : 0 : return;
736 : :
737 : : /* Try the interface. */
738 [ # # ]: 0 : if (g_str_equal (interface_name, "org.freedesktop.MalcontentTimer1.Child"))
739 : 0 : g_dbus_method_invocation_return_value (invocation,
740 : : g_variant_new_parsed ("(@a{sv} {},)"));
741 : : else
742 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
743 : : G_DBUS_ERROR_UNKNOWN_INTERFACE,
744 : : _("Unknown interface ‘%s’."),
745 : : interface_name);
746 : : }
747 : :
748 : : typedef struct
749 : : {
750 : : MctChildTimerService *child_timer_service; /* (owned) */
751 : : GHashTable *time_spans_hash; /* (owned) (element-type utf8 GPtrArray<MctTimeSpan>) */
752 : : GDBusMethodInvocation *invocation; /* (owned) */
753 : : uint64_t now_secs;
754 : : MctOperationCounter operation_counter;
755 : : } RecordUsageData;
756 : :
757 : : static void
758 : 0 : record_usage_data_free (RecordUsageData *data)
759 : : {
760 [ # # ]: 0 : g_clear_object (&data->invocation);
761 [ # # ]: 0 : g_clear_pointer (&data->time_spans_hash, g_hash_table_unref);
762 [ # # ]: 0 : g_clear_object (&data->child_timer_service);
763 : 0 : mct_operation_counter_release_and_clear (&data->operation_counter);
764 : 0 : g_free (data);
765 : 0 : }
766 : :
767 [ # # ]: 0 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (RecordUsageData, record_usage_data_free)
768 : :
769 : : static void record_usage_ensure_credentials_cb (GObject *object,
770 : : GAsyncResult *result,
771 : : void *user_data);
772 : : static void record_usage_open_username_cb (GObject *object,
773 : : GAsyncResult *result,
774 : : void *user_data);
775 : : static void record_usage_save_transaction_cb (GObject *object,
776 : : GAsyncResult *result,
777 : : void *user_data);
778 : :
779 : : static void
780 : 0 : mct_child_timer_service_record_usage (MctChildTimerService *self,
781 : : GDBusConnection *connection,
782 : : const char *sender,
783 : : GVariant *parameters,
784 : : GDBusMethodInvocation *invocation)
785 : : {
786 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
787 [ # # ]: 0 : g_autoptr(GVariantIter) entries_iter = NULL;
788 : : uint64_t now_secs, start_time, end_time;
789 : : const char *record_type, *identifier;
790 [ # # ]: 0 : g_autoptr(GHashTable) time_spans_hash = NULL;
791 : : GPtrArray *time_spans; /* (element-type MctTimeSpan) */
792 [ # # ]: 0 : g_autoptr(RecordUsageData) data = NULL;
793 : :
794 : : /* Validate the parameters and add them to the timer store. */
795 : 0 : g_variant_get (parameters, "(a(ttss))", &entries_iter);
796 : 0 : time_spans_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_ptr_array_unref);
797 : 0 : now_secs = (uint64_t) g_get_real_time () / G_USEC_PER_SEC;
798 : :
799 : : /* Validate each entry. */
800 [ # # ]: 0 : while (g_variant_iter_loop (entries_iter, "(tt&s&s)", &start_time, &end_time, &record_type, &identifier))
801 : : {
802 [ # # ]: 0 : if (start_time > end_time)
803 : : {
804 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
805 : : MCT_CHILD_TIMER_SERVICE_ERROR_INVALID_RECORD,
806 : : _("Invalid usage parameters: %s"),
807 : 0 : _("Times out of order"));
808 : 0 : return;
809 : : }
810 [ # # ]: 0 : if (end_time > now_secs)
811 : : {
812 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
813 : : MCT_CHILD_TIMER_SERVICE_ERROR_INVALID_RECORD,
814 : : _("Invalid usage parameters: %s"),
815 : 0 : _("End time in the future"));
816 : 0 : return;
817 : : }
818 [ # # ]: 0 : if (!validate_record_type_and_identifier (invocation, record_type, identifier))
819 : 0 : return;
820 : :
821 : : /* Add to the in-progress data. */
822 : 0 : time_spans = g_hash_table_lookup (time_spans_hash, identifier);
823 [ # # ]: 0 : if (time_spans == NULL)
824 : : {
825 : 0 : time_spans = g_ptr_array_new_with_free_func ((GDestroyNotify) mct_time_span_free);
826 : 0 : g_hash_table_insert (time_spans_hash, (void *) identifier, time_spans);
827 : : }
828 : 0 : g_ptr_array_add (time_spans, mct_time_span_new (start_time, end_time));
829 : : }
830 : :
831 [ # # ]: 0 : if (g_hash_table_size (time_spans_hash) == 0)
832 : : {
833 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
834 : : MCT_CHILD_TIMER_SERVICE_ERROR_INVALID_RECORD,
835 : : _("Invalid usage parameters: %s"),
836 : 0 : _("No entries"));
837 : 0 : return;
838 : : }
839 : :
840 : : /* Load the peer’s credentials so we know which user the usage entries come
841 : : * from. */
842 : 0 : data = g_new0 (RecordUsageData, 1);
843 : 0 : data->child_timer_service = g_object_ref (self);
844 : 0 : data->time_spans_hash = g_steal_pointer (&time_spans_hash);
845 : 0 : data->invocation = g_object_ref (invocation);
846 : 0 : data->now_secs = now_secs;
847 : 0 : mct_operation_counter_init_and_hold (&data->operation_counter,
848 : : &self->n_pending_operations,
849 : 0 : G_OBJECT (self), props[PROP_BUSY]);
850 : :
851 : 0 : gss_peer_manager_ensure_peer_credentials_async (self->peer_manager,
852 : : sender,
853 : : self->cancellable,
854 : : record_usage_ensure_credentials_cb,
855 : : g_steal_pointer (&data));
856 : : }
857 : :
858 : : static void
859 : 0 : record_usage_ensure_credentials_cb (GObject *object,
860 : : GAsyncResult *result,
861 : : void *user_data)
862 : : {
863 : 0 : GssPeerManager *peer_manager = GSS_PEER_MANAGER (object);
864 [ # # ]: 0 : g_autoptr(RecordUsageData) data = g_steal_pointer (&user_data);
865 : 0 : MctChildTimerService *self = data->child_timer_service;
866 : 0 : GDBusMethodInvocation *invocation = data->invocation;
867 [ # # ]: 0 : g_autofree char *username = NULL;
868 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
869 : :
870 : : /* Finish looking up the sender. */
871 [ # # ]: 0 : if (!gss_peer_manager_ensure_peer_credentials_finish (peer_manager,
872 : : result, &local_error))
873 : : {
874 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
875 : : MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
876 : : _("Error identifying user: %s"),
877 : 0 : local_error->message);
878 : 0 : return;
879 : : }
880 : :
881 : 0 : username = lookup_username_for_peer_invocation (peer_manager, invocation, NULL);
882 [ # # ]: 0 : if (username == NULL)
883 : : {
884 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
885 : : MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
886 : : _("Error identifying user: %s"),
887 : 0 : _("Invalid or unknown user"));
888 : 0 : return;
889 : : }
890 : :
891 : 0 : mct_timer_store_open_username_async (self->timer_store,
892 : : username,
893 : : self->cancellable,
894 : : record_usage_open_username_cb,
895 : 0 : g_steal_pointer (&data));
896 : : }
897 : :
898 : : static void
899 : 0 : record_usage_open_username_cb (GObject *object,
900 : : GAsyncResult *result,
901 : : void *user_data)
902 : : {
903 : 0 : MctTimerStore *timer_store = MCT_TIMER_STORE (object);
904 [ # # ]: 0 : g_autoptr(RecordUsageData) data = g_steal_pointer (&user_data);
905 : 0 : MctChildTimerService *self = data->child_timer_service;
906 : 0 : GDBusMethodInvocation *invocation = data->invocation;
907 : 0 : GHashTable *time_spans_hash = data->time_spans_hash;
908 : : const MctTimerStoreTransaction *transaction;
909 : : GHashTableIter time_spans_hash_iter;
910 : : const char *identifier;
911 : : GPtrArray *time_spans; /* (element-type MctTimeSpan) */
912 : : uint64_t expiry_cutoff_secs;
913 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
914 : :
915 : 0 : transaction = mct_timer_store_open_username_finish (timer_store, result, &local_error);
916 : :
917 [ # # ]: 0 : if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY))
918 : : {
919 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
920 : : MCT_CHILD_TIMER_SERVICE_ERROR_BUSY,
921 : : _("Error opening user file: %s"),
922 : 0 : local_error->message);
923 : 0 : return;
924 : : }
925 [ # # ]: 0 : else if (local_error != NULL) /* likely a GFileError */
926 : : {
927 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
928 : : MCT_CHILD_TIMER_SERVICE_ERROR_STORAGE_ERROR,
929 : : _("Error opening user file: %s"),
930 : 0 : local_error->message);
931 : 0 : return;
932 : : }
933 : :
934 : : /* Now all the time spans have been validated, add them to the timer store. */
935 : 0 : g_hash_table_iter_init (&time_spans_hash_iter, time_spans_hash);
936 [ # # ]: 0 : while (g_hash_table_iter_next (&time_spans_hash_iter, (void **) &identifier, (void **) &time_spans))
937 : : {
938 : : MctTimerStoreRecordType record_type;
939 : :
940 : : /* For the moment, we can rely on the fact that the identifier spaces for
941 : : * the two current record types are disjoint, so we don’t have to
942 : : * explicitly pass the @record_type via the @time_spans_hash. We may have
943 : : * to in future, though, if more record types are added. */
944 [ # # ]: 0 : if (*identifier == '\0')
945 : 0 : record_type = MCT_TIMER_STORE_RECORD_TYPE_LOGIN_SESSION;
946 : : else
947 : 0 : record_type = MCT_TIMER_STORE_RECORD_TYPE_APP;
948 : :
949 : 0 : mct_timer_store_add_time_spans (timer_store, transaction, record_type, identifier,
950 : 0 : (const MctTimeSpan * const *) time_spans->pdata,
951 : 0 : time_spans->len);
952 : : }
953 : :
954 : : /* Expire old entries when saving.
955 : : * FIXME: Make this configurable */
956 : 0 : expiry_cutoff_secs = data->now_secs - 4 * 7 * 24 * 60 * 60;
957 : :
958 : : /* Save the changes. */
959 : 0 : mct_timer_store_save_transaction_async (timer_store,
960 : : transaction,
961 : : expiry_cutoff_secs,
962 : : self->cancellable,
963 : : record_usage_save_transaction_cb,
964 : 0 : g_steal_pointer (&data));
965 : : }
966 : :
967 : : static void
968 : 0 : record_usage_save_transaction_cb (GObject *object,
969 : : GAsyncResult *result,
970 : : void *user_data)
971 : : {
972 : 0 : MctTimerStore *timer_store = MCT_TIMER_STORE (object);
973 : 0 : g_autoptr(RecordUsageData) data = g_steal_pointer (&user_data);
974 : 0 : GDBusMethodInvocation *invocation = data->invocation;
975 : 0 : g_autoptr(GError) local_error = NULL;
976 : :
977 [ # # ]: 0 : if (!mct_timer_store_save_transaction_finish (timer_store, result, &local_error))
978 : : {
979 : : /* The error is likely a GIOError */
980 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
981 : : MCT_CHILD_TIMER_SERVICE_ERROR_STORAGE_ERROR,
982 : 0 : _("Error saving user file: %s"), local_error->message);
983 : : }
984 : : else
985 : : {
986 : 0 : g_dbus_method_invocation_return_value (invocation, NULL);
987 : : }
988 : 0 : }
989 : :
990 : : typedef struct
991 : : {
992 : : MctChildTimerService *child_timer_service; /* (owned) */
993 : : GDBusMethodInvocation *invocation; /* (owned) */
994 : : MctTimerStoreRecordType record_type;
995 : : char *username; /* (nullable) (owned) */
996 : : MctSessionLimits *session_limits_policy; /* (nullable) (owned) */
997 : : MctOperationCounter operation_counter;
998 : : } GetEstimatedTimesData;
999 : :
1000 : : static void
1001 : 0 : get_estimated_times_data_free (GetEstimatedTimesData *data)
1002 : : {
1003 [ # # ]: 0 : g_clear_object (&data->invocation);
1004 [ # # ]: 0 : g_clear_object (&data->child_timer_service);
1005 : 0 : g_free (data->username);
1006 [ # # ]: 0 : g_clear_pointer (&data->session_limits_policy, mct_session_limits_unref);
1007 : 0 : mct_operation_counter_release_and_clear (&data->operation_counter);
1008 : 0 : g_free (data);
1009 : 0 : }
1010 : :
1011 [ # # ]: 0 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (GetEstimatedTimesData, get_estimated_times_data_free)
1012 : :
1013 : : static void get_estimated_times_ensure_credentials_cb (GObject *object,
1014 : : GAsyncResult *result,
1015 : : void *user_data);
1016 : : static void get_estimated_times_get_session_limits_cb (GObject *object,
1017 : : GAsyncResult *result,
1018 : : void *user_data);
1019 : : static void get_estimated_times_open_username_cb (GObject *object,
1020 : : GAsyncResult *result,
1021 : : void *user_data);
1022 : :
1023 : : static void
1024 : 0 : mct_child_timer_service_get_estimated_times (MctChildTimerService *self,
1025 : : GDBusConnection *connection,
1026 : : const char *sender,
1027 : : GVariant *parameters,
1028 : : GDBusMethodInvocation *invocation)
1029 : : {
1030 [ # # ]: 0 : g_autoptr(GetEstimatedTimesData) data = NULL;
1031 : 0 : const char *record_type = NULL;
1032 : :
1033 : 0 : g_variant_get (parameters, "(&s)", &record_type);
1034 : :
1035 [ # # ]: 0 : if (!validate_record_type (invocation, record_type))
1036 : 0 : return;
1037 : :
1038 : : /* Load the peer’s credentials so we know which user is querying the usage. */
1039 : 0 : data = g_new0 (GetEstimatedTimesData, 1);
1040 : 0 : data->child_timer_service = g_object_ref (self);
1041 : 0 : data->invocation = g_object_ref (invocation);
1042 : 0 : data->record_type = mct_timer_store_record_type_from_string (record_type);
1043 : 0 : mct_operation_counter_init_and_hold (&data->operation_counter,
1044 : : &self->n_pending_operations,
1045 : 0 : G_OBJECT (self), props[PROP_BUSY]);
1046 : :
1047 : 0 : gss_peer_manager_ensure_peer_credentials_async (self->peer_manager,
1048 : : sender,
1049 : : self->cancellable,
1050 : : get_estimated_times_ensure_credentials_cb,
1051 : : g_steal_pointer (&data));
1052 : : }
1053 : :
1054 : : static void
1055 : 0 : get_estimated_times_ensure_credentials_cb (GObject *object,
1056 : : GAsyncResult *result,
1057 : : void *user_data)
1058 : : {
1059 : 0 : GssPeerManager *peer_manager = GSS_PEER_MANAGER (object);
1060 [ # # ]: 0 : g_autoptr(GetEstimatedTimesData) data = g_steal_pointer (&user_data);
1061 : 0 : MctChildTimerService *self = data->child_timer_service;
1062 : 0 : GDBusMethodInvocation *invocation = data->invocation;
1063 : : GDBusMessage *message;
1064 : : gboolean interactive;
1065 [ # # ]: 0 : g_autofree char *username = NULL;
1066 : : uid_t uid;
1067 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
1068 : :
1069 : : /* Finish looking up the sender. */
1070 [ # # ]: 0 : if (!gss_peer_manager_ensure_peer_credentials_finish (peer_manager,
1071 : : result, &local_error))
1072 : : {
1073 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1074 : : MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
1075 : : _("Error identifying user: %s"),
1076 : 0 : local_error->message);
1077 : 0 : return;
1078 : : }
1079 : :
1080 : 0 : username = lookup_username_for_peer_invocation (peer_manager, invocation, &uid);
1081 [ # # ]: 0 : if (username == NULL)
1082 : : {
1083 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1084 : : MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
1085 : : _("Error identifying user: %s"),
1086 : 0 : _("Invalid or unknown user"));
1087 : 0 : return;
1088 : : }
1089 : :
1090 : 0 : data->username = g_steal_pointer (&username);
1091 : :
1092 : : /* Query the user’s session limits policy. */
1093 : 0 : message = g_dbus_method_invocation_get_message (invocation);
1094 : 0 : interactive = g_dbus_message_get_flags (message) & G_DBUS_MESSAGE_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION;
1095 : :
1096 : 0 : mct_manager_get_session_limits_async (self->policy_manager,
1097 : : uid,
1098 : : interactive ? MCT_MANAGER_GET_VALUE_FLAGS_INTERACTIVE : MCT_MANAGER_GET_VALUE_FLAGS_NONE,
1099 : : self->cancellable,
1100 : : get_estimated_times_get_session_limits_cb,
1101 : : g_steal_pointer (&data));
1102 : : }
1103 : :
1104 : : static void
1105 : 0 : get_estimated_times_get_session_limits_cb (GObject *object,
1106 : : GAsyncResult *result,
1107 : : void *user_data)
1108 : : {
1109 : 0 : MctManager *policy_manager = MCT_MANAGER (object);
1110 [ # # ]: 0 : g_autoptr(GetEstimatedTimesData) data = g_steal_pointer (&user_data);
1111 : 0 : MctChildTimerService *self = data->child_timer_service;
1112 : 0 : GDBusMethodInvocation *invocation = data->invocation;
1113 : 0 : const char *username = data->username;
1114 [ # # ]: 0 : g_autoptr(MctSessionLimits) session_limits_policy = NULL;
1115 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
1116 : :
1117 : 0 : session_limits_policy = mct_manager_get_session_limits_finish (policy_manager,
1118 : : result, &local_error);
1119 : :
1120 [ # # # # ]: 0 : if (g_error_matches (local_error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_INVALID_USER) ||
1121 : 0 : g_error_matches (local_error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_PERMISSION_DENIED))
1122 : : {
1123 : : /* MCT_MANAGER_ERROR_INVALID_USER shouldn’t really happen as we’ve
1124 : : * already identified the user, but I guess there could be a problem
1125 : : * inside AccountsssService.
1126 : : *
1127 : : * MCT_MANAGER_ERROR_PERMISSION_DENIED definitely shouldn’t happen:
1128 : : * the user should always have permissions to query their own session
1129 : : * limits, otherwise the install must be corrupt. */
1130 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1131 : : MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
1132 : : _("Error identifying user: %s"),
1133 : 0 : local_error->message);
1134 : 0 : return;
1135 : : }
1136 [ # # ]: 0 : else if (g_error_matches (local_error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_DISABLED))
1137 : : {
1138 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1139 : : MCT_CHILD_TIMER_SERVICE_ERROR_DISABLED,
1140 : : _("Recording usage is disabled"));
1141 : 0 : return;
1142 : : }
1143 [ # # ]: 0 : else if (local_error != NULL)
1144 : : {
1145 : : /* Likely a GDBusError or GIOError, or MCT_MANAGER_ERROR_INVALID_DATA */
1146 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1147 : : MCT_CHILD_TIMER_SERVICE_ERROR_QUERYING_POLICY,
1148 : : _("Error querying user parental controls policy: %s"),
1149 : 0 : local_error->message);
1150 : 0 : return;
1151 : : }
1152 : :
1153 : 0 : data->session_limits_policy = g_steal_pointer (&session_limits_policy);
1154 : :
1155 : : /* Now open the user’s file. */
1156 : 0 : mct_timer_store_open_username_async (self->timer_store,
1157 : : username,
1158 : : self->cancellable,
1159 : : get_estimated_times_open_username_cb,
1160 : 0 : g_steal_pointer (&data));
1161 : : }
1162 : :
1163 : : static void
1164 : 0 : get_estimated_times_open_username_cb (GObject *object,
1165 : : GAsyncResult *result,
1166 : : void *user_data)
1167 : : {
1168 : 0 : MctTimerStore *timer_store = MCT_TIMER_STORE (object);
1169 [ # # ]: 0 : g_autoptr(GetEstimatedTimesData) data = g_steal_pointer (&user_data);
1170 : 0 : MctChildTimerService *self = data->child_timer_service;
1171 : 0 : GDBusMethodInvocation *invocation = data->invocation;
1172 : : const MctTimerStoreTransaction *transaction;
1173 [ # # ]: 0 : g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{s(btttt)}"));
1174 [ # # ]: 0 : g_autoptr(GHashTable) total_times_so_far_today = NULL; /* (element-type utf8 uint64_t) */
1175 : : uint64_t start_of_today_secs, now_secs;
1176 [ # # ]: 0 : g_autoptr(GDateTime) now_date_time = NULL;
1177 [ # # ]: 0 : g_autoptr(GDateTime) start_of_today = NULL;
1178 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
1179 : :
1180 : 0 : transaction = mct_timer_store_open_username_finish (timer_store, result, &local_error);
1181 : :
1182 [ # # ]: 0 : if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY))
1183 : : {
1184 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1185 : : MCT_CHILD_TIMER_SERVICE_ERROR_BUSY,
1186 : : _("Error opening user file: %s"),
1187 : 0 : local_error->message);
1188 : 0 : return;
1189 : : }
1190 [ # # ]: 0 : else if (local_error != NULL) /* likely a GFileError */
1191 : : {
1192 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1193 : : MCT_CHILD_TIMER_SERVICE_ERROR_STORAGE_ERROR,
1194 : : _("Error opening user file: %s"),
1195 : 0 : local_error->message);
1196 : 0 : return;
1197 : : }
1198 : :
1199 : : /* Work out some times. */
1200 : 0 : now_secs = g_get_real_time () / G_USEC_PER_SEC;
1201 : :
1202 : 0 : now_date_time = g_date_time_new_from_unix_local (now_secs);
1203 : 0 : start_of_today = g_date_time_new_local (g_date_time_get_year (now_date_time),
1204 : : g_date_time_get_month (now_date_time),
1205 : : g_date_time_get_day_of_month (now_date_time),
1206 : : 0, 0, 0);
1207 : 0 : g_assert (start_of_today != NULL);
1208 : 0 : start_of_today_secs = g_date_time_to_unix (start_of_today);
1209 : :
1210 : : /* Calculate the total time the user has spent on each session, app, etc.
1211 : : * since the start of today. */
1212 : 0 : total_times_so_far_today =
1213 : 0 : mct_timer_store_calculate_total_times_between (self->timer_store,
1214 : : transaction,
1215 : 0 : data->record_type,
1216 : : start_of_today_secs,
1217 : : now_secs);
1218 : :
1219 : : /* Close the file again. */
1220 : 0 : mct_timer_store_roll_back_transaction (self->timer_store, transaction);
1221 : :
1222 : : /* Get the estimated times. */
1223 [ # # # ]: 0 : switch (data->record_type)
1224 : : {
1225 : 0 : case MCT_TIMER_STORE_RECORD_TYPE_LOGIN_SESSION:
1226 : : {
1227 : : gboolean limit_reached;
1228 : 0 : uint64_t time_remaining_secs = 0;
1229 : 0 : gboolean time_limit_enabled = FALSE;
1230 : :
1231 : : limit_reached =
1232 : 0 : mct_session_limits_check_time_remaining (data->session_limits_policy,
1233 : : now_secs * G_USEC_PER_SEC,
1234 : : &time_remaining_secs,
1235 : : &time_limit_enabled);
1236 : :
1237 [ # # ]: 0 : if (time_limit_enabled)
1238 : : {
1239 : : uint64_t current_session_start_time_secs, current_session_estimated_end_time_secs;
1240 : : uint64_t next_session_start_time_secs, next_session_estimated_end_time_secs;
1241 : :
1242 [ # # ]: 0 : if (!limit_reached)
1243 : : {
1244 : : /* FIXME: Calculate more of these times */
1245 : 0 : current_session_start_time_secs = 0;
1246 : 0 : current_session_estimated_end_time_secs = now_secs + time_remaining_secs;
1247 : 0 : next_session_start_time_secs = 0;
1248 : 0 : next_session_estimated_end_time_secs = 0;
1249 : : }
1250 : : else
1251 : : {
1252 : : /* if the limit has been reached, the ‘current session’ is
1253 : : * actually the most recent session */
1254 : 0 : current_session_start_time_secs = 0;
1255 : 0 : current_session_estimated_end_time_secs = now_secs; // TODO actually need the time the session ended
1256 : 0 : next_session_start_time_secs = 0;
1257 : 0 : next_session_estimated_end_time_secs = 0;
1258 : : }
1259 : :
1260 : :
1261 : 0 : g_variant_builder_open (&builder, G_VARIANT_TYPE ("{s(btttt)}"));
1262 : 0 : g_variant_builder_add (&builder, "s", "");
1263 : 0 : g_variant_builder_add (&builder, "(btttt)",
1264 : : limit_reached,
1265 : : current_session_start_time_secs,
1266 : : current_session_estimated_end_time_secs,
1267 : : next_session_start_time_secs,
1268 : : next_session_estimated_end_time_secs);
1269 : 0 : g_variant_builder_close (&builder);
1270 : : }
1271 : 0 : break;
1272 : : }
1273 : 0 : case MCT_TIMER_STORE_RECORD_TYPE_APP:
1274 : : /* FIXME Not supported for now, as no support is in place in MctSessionLimits yet */
1275 : 0 : g_dbus_method_invocation_return_error (invocation, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
1276 : : "App time limits are not yet supported");
1277 : 0 : return;
1278 : 0 : default:
1279 : : g_assert_not_reached ();
1280 : : }
1281 : :
1282 : 0 : g_dbus_method_invocation_return_value (invocation,
1283 : : g_variant_new ("(t@a{s(btttt)})",
1284 : : now_secs,
1285 : : g_variant_builder_end (&builder)));
1286 : : }
1287 : :
1288 : : /**
1289 : : * mct_child_timer_service_new:
1290 : : * @connection: (transfer none): D-Bus connection to export objects on
1291 : : * @object_path: root path to export objects below; must be a valid D-Bus object
1292 : : * path
1293 : : * @timer_store: (transfer none): store to use for timer data
1294 : : * @peer_manager: (transfer none): peer manager for querying D-Bus peers
1295 : : * @policy_manager: (transfer none): policy manager for querying user parental
1296 : : * controls policies
1297 : : *
1298 : : * Create a new [class@Malcontent.ChildTimerService] instance which is set up to run
1299 : : * as a service.
1300 : : *
1301 : : * Returns: (transfer full): a new [class@Malcontent.ChildTimerService]
1302 : : * Since: 0.14.0
1303 : : */
1304 : : MctChildTimerService *
1305 : 1 : mct_child_timer_service_new (GDBusConnection *connection,
1306 : : const char *object_path,
1307 : : MctTimerStore *timer_store,
1308 : : GssPeerManager *peer_manager,
1309 : : MctManager *policy_manager)
1310 : : {
1311 : 1 : g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
1312 : 1 : g_return_val_if_fail (g_variant_is_object_path (object_path), NULL);
1313 : 1 : g_return_val_if_fail (MCT_IS_TIMER_STORE (timer_store), NULL);
1314 : 1 : g_return_val_if_fail (GSS_IS_PEER_MANAGER (peer_manager), NULL);
1315 : 1 : g_return_val_if_fail (MCT_IS_MANAGER (policy_manager), NULL);
1316 : :
1317 : 1 : return g_object_new (MCT_TYPE_CHILD_TIMER_SERVICE,
1318 : : "connection", connection,
1319 : : "object-path", object_path,
1320 : : "timer-store", timer_store,
1321 : : "peer-manager", peer_manager,
1322 : : "policy-manager", policy_manager,
1323 : : NULL);
1324 : : }
1325 : :
1326 : : /**
1327 : : * mct_child_timer_service_get_busy:
1328 : : * @self: a child service
1329 : : *
1330 : : * Get the value of [property@Malcontent.ChildTimerService.busy].
1331 : : *
1332 : : * Returns: true if the service is busy, false otherwise
1333 : : * Since: 0.14.0
1334 : : */
1335 : : gboolean
1336 : 3 : mct_child_timer_service_get_busy (MctChildTimerService *self)
1337 : : {
1338 : 3 : g_return_val_if_fail (MCT_IS_CHILD_TIMER_SERVICE (self), FALSE);
1339 : :
1340 [ + + - + ]: 3 : return (self->object_id != 0 && self->n_pending_operations > 0);
1341 : : }
1342 : :
|