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 policy_manager_session_limits_changed_cb (MctManager *policy_manager,
85 : : uint64_t uid,
86 : : void *user_data);
87 : : static void mct_child_timer_service_record_usage (MctChildTimerService *self,
88 : : GDBusConnection *connection,
89 : : const char *sender,
90 : : GVariant *parameters,
91 : : GDBusMethodInvocation *invocation);
92 : : static void mct_child_timer_service_get_estimated_times (MctChildTimerService *self,
93 : : GDBusConnection *connection,
94 : : const char *sender,
95 : : GVariant *parameters,
96 : : GDBusMethodInvocation *invocation);
97 : : static void mct_child_timer_service_request_extension (MctChildTimerService *self,
98 : : GDBusConnection *connection,
99 : : const char *sender,
100 : : GVariant *parameters,
101 : : GDBusMethodInvocation *invocation);
102 : :
103 : : /* These errors do go over the bus, and are registered in mct_child_timer_service_class_init(). */
104 [ + + ]: 8 : G_DEFINE_QUARK (MctChildTimerServiceError, mct_child_timer_service_error)
105 : :
106 : : static const gchar *child_timer_service_errors[] =
107 : : {
108 : : "org.freedesktop.MalcontentTimer1.Child.Error.InvalidRecord",
109 : : "org.freedesktop.MalcontentTimer1.Child.Error.StorageError",
110 : : "org.freedesktop.MalcontentTimer1.Child.Error.Busy",
111 : : "org.freedesktop.MalcontentTimer1.Child.Error.IdentifyingUser",
112 : : "org.freedesktop.MalcontentTimer1.Child.Error.QueryingPolicy",
113 : : "org.freedesktop.MalcontentTimer1.Child.Error.Disabled",
114 : : "org.freedesktop.MalcontentTimer1.Child.Error.CommunicatingWithAgent",
115 : : "org.freedesktop.MalcontentTimer1.Child.Error.RequestCancelled",
116 : : };
117 : : static const GDBusErrorEntry child_timer_service_error_map[] =
118 : : {
119 : : { MCT_CHILD_TIMER_SERVICE_ERROR_INVALID_RECORD, "org.freedesktop.MalcontentTimer1.Child.Error.InvalidRecord" },
120 : : { MCT_CHILD_TIMER_SERVICE_ERROR_STORAGE_ERROR, "org.freedesktop.MalcontentTimer1.Child.Error.StorageError" },
121 : : { MCT_CHILD_TIMER_SERVICE_ERROR_BUSY, "org.freedesktop.MalcontentTimer1.Child.Error.Busy" },
122 : : { MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER, "org.freedesktop.MalcontentTimer1.Child.Error.IdentifyingUser" },
123 : : { MCT_CHILD_TIMER_SERVICE_ERROR_QUERYING_POLICY, "org.freedesktop.MalcontentTimer1.Child.Error.QueryingPolicy" },
124 : : { MCT_CHILD_TIMER_SERVICE_ERROR_DISABLED, "org.freedesktop.MalcontentTimer1.Child.Error.Disabled" },
125 : : { MCT_CHILD_TIMER_SERVICE_ERROR_COMMUNICATING_WITH_AGENT, "org.freedesktop.MalcontentTimer1.Child.Error.CommunicatingWithAgent" },
126 : : { MCT_CHILD_TIMER_SERVICE_ERROR_REQUEST_CANCELLED, "org.freedesktop.MalcontentTimer1.Child.Error.RequestCancelled" },
127 : :
128 : : };
129 : : G_STATIC_ASSERT (G_N_ELEMENTS (child_timer_service_error_map) == MCT_CHILD_TIMER_SERVICE_N_ERRORS);
130 : : G_STATIC_ASSERT (G_N_ELEMENTS (child_timer_service_error_map) == G_N_ELEMENTS (child_timer_service_errors));
131 : :
132 : : /**
133 : : * MctChildTimerService:
134 : : *
135 : : * An implementation of the `org.freedesktop.MalcontentTimer1.Child` D-Bus
136 : : * interface, allowing a trusted component in a child user account’s session to
137 : : * record screen time and app usage periods for that account.
138 : : *
139 : : * This will expose all the necessary objects on the bus for peers to interact
140 : : * with them, and hooks them up to internal state management using
141 : : * [property@Malcontent.ChildTimerService:timer-store].
142 : : *
143 : : * Since: 0.14.0
144 : : */
145 : : struct _MctChildTimerService
146 : : {
147 : : GObject parent;
148 : :
149 : : GDBusConnection *connection; /* (owned) */
150 : : char *object_path; /* (owned) */
151 : : unsigned int object_id;
152 : :
153 : : /* Used to cancel any pending operations when the object is unregistered. */
154 : : GCancellable *cancellable; /* (owned) */
155 : :
156 : : MctTimerStore *timer_store; /* (owned) */
157 : : unsigned long timer_store_estimated_end_times_changed_id;
158 : : GssPeerManager *peer_manager; /* (owned) */
159 : : MctManager *policy_manager; /* (owned) */
160 : : unsigned long policy_manager_session_limits_changed_id;
161 : : unsigned int n_pending_operations;
162 : :
163 : : /* Communication with the extension request agent */
164 : : GPtrArray *pending_extension_agent_requests; /* (owned) (element-type RequestExtensionData) */
165 : : };
166 : :
167 : : typedef enum
168 : : {
169 : : PROP_CONNECTION = 1,
170 : : PROP_OBJECT_PATH,
171 : : PROP_TIMER_STORE,
172 : : PROP_PEER_MANAGER,
173 : : PROP_POLICY_MANAGER,
174 : : PROP_BUSY,
175 : : } MctChildTimerServiceProperty;
176 : :
177 : : static GParamSpec *props[PROP_BUSY + 1] = { NULL, };
178 : :
179 [ + + + - : 16 : G_DEFINE_TYPE (MctChildTimerService, mct_child_timer_service, G_TYPE_OBJECT)
+ + ]
180 : :
181 : : typedef struct
182 : : {
183 : : MctChildTimerService *child_timer_service; /* (owned) */
184 : : GDBusMethodInvocation *invocation; /* (owned) */
185 : : MctOperationCounter operation_counter;
186 : : char *record_type; /* (not nullable) */
187 : : char *identifier; /* (not nullable) */
188 : : uint64_t duration_secs;
189 : : GVariant *extra_data; /* (owned) */
190 : : char *cookie_path; /* (owned) */
191 : : GDBusConnection *connection; /* (nullable) (owned) */
192 : : unsigned int request_subscribe_id;
193 : : unsigned long cancelled_id;
194 : : unsigned int agent_name_watch_id;
195 : : unsigned int client_name_watch_id;
196 : : char *request_object_path; /* (owned) (nullable) */
197 : : } RequestExtensionData;
198 : :
199 : : static void
200 : 0 : request_extension_data_free (RequestExtensionData *data)
201 : : {
202 [ # # ]: 0 : if (data->request_subscribe_id != 0)
203 : 0 : g_dbus_connection_signal_unsubscribe (data->connection, data->request_subscribe_id);
204 : 0 : data->request_subscribe_id = 0;
205 [ # # ]: 0 : g_clear_object (&data->connection);
206 : :
207 [ # # ]: 0 : if (data->cancelled_id != 0)
208 : 0 : g_cancellable_disconnect (data->child_timer_service->cancellable, data->cancelled_id);
209 : 0 : data->cancelled_id = 0;
210 : :
211 [ # # ]: 0 : if (data->agent_name_watch_id != 0)
212 : 0 : g_bus_unwatch_name (data->agent_name_watch_id);
213 : 0 : data->agent_name_watch_id = 0;
214 : :
215 [ # # ]: 0 : if (data->client_name_watch_id != 0)
216 : 0 : g_bus_unwatch_name (data->client_name_watch_id);
217 : 0 : data->client_name_watch_id = 0;
218 : :
219 [ # # ]: 0 : g_clear_object (&data->invocation);
220 [ # # ]: 0 : g_clear_object (&data->child_timer_service);
221 : 0 : mct_operation_counter_release_and_clear (&data->operation_counter);
222 [ # # ]: 0 : g_clear_pointer (&data->extra_data, g_variant_unref);
223 [ # # ]: 0 : g_clear_pointer (&data->cookie_path, g_free);
224 [ # # ]: 0 : g_clear_pointer (&data->request_object_path, g_free);
225 [ # # ]: 0 : g_clear_pointer (&data->record_type, g_free);
226 [ # # ]: 0 : g_clear_pointer (&data->identifier, g_free);
227 : 0 : g_free (data);
228 : 0 : }
229 : :
230 [ # # ]: 0 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (RequestExtensionData, request_extension_data_free)
231 : :
232 : : static void
233 : 1 : mct_child_timer_service_class_init (MctChildTimerServiceClass *klass)
234 : : {
235 : 1 : GObjectClass *object_class = (GObjectClass *) klass;
236 : :
237 : 1 : object_class->constructed = mct_child_timer_service_constructed;
238 : 1 : object_class->dispose = mct_child_timer_service_dispose;
239 : 1 : object_class->get_property = mct_child_timer_service_get_property;
240 : 1 : object_class->set_property = mct_child_timer_service_set_property;
241 : :
242 : : /**
243 : : * MctChildTimerService:connection:
244 : : *
245 : : * D-Bus connection to export objects on.
246 : : *
247 : : * Since: 0.14.0
248 : : */
249 : 1 : props[PROP_CONNECTION] =
250 : 1 : g_param_spec_object ("connection", NULL, NULL,
251 : : G_TYPE_DBUS_CONNECTION,
252 : : G_PARAM_READWRITE |
253 : : G_PARAM_CONSTRUCT_ONLY |
254 : : G_PARAM_STATIC_STRINGS);
255 : :
256 : : /**
257 : : * MctChildTimerService:object-path:
258 : : *
259 : : * Object path to root all exported objects at. If this does not end in a
260 : : * slash, one will be added.
261 : : *
262 : : * Since: 0.14.0
263 : : */
264 : 1 : props[PROP_OBJECT_PATH] =
265 : 1 : g_param_spec_string ("object-path", NULL, NULL,
266 : : "/",
267 : : G_PARAM_READWRITE |
268 : : G_PARAM_CONSTRUCT_ONLY |
269 : : G_PARAM_STATIC_STRINGS);
270 : :
271 : : /**
272 : : * MctChildTimerService:timer-store:
273 : : *
274 : : * Store for timer data.
275 : : *
276 : : * Since: 0.14.0
277 : : */
278 : 1 : props[PROP_TIMER_STORE] =
279 : 1 : g_param_spec_object ("timer-store", NULL, NULL,
280 : : MCT_TYPE_TIMER_STORE,
281 : : G_PARAM_READWRITE |
282 : : G_PARAM_CONSTRUCT_ONLY |
283 : : G_PARAM_STATIC_STRINGS);
284 : :
285 : : /**
286 : : * MctChildTimerService:peer-manager:
287 : : *
288 : : * Peer manager to identify incoming method calls.
289 : : *
290 : : * Since: 0.14.0
291 : : */
292 : 1 : props[PROP_PEER_MANAGER] =
293 : 1 : g_param_spec_object ("peer-manager", NULL, NULL,
294 : : GSS_TYPE_PEER_MANAGER,
295 : : G_PARAM_READWRITE |
296 : : G_PARAM_CONSTRUCT_ONLY |
297 : : G_PARAM_STATIC_STRINGS);
298 : :
299 : : /**
300 : : * MctChildTimerService:policy-manager:
301 : : *
302 : : * Policy manager to provide users’ parental controls policies.
303 : : *
304 : : * Since: 0.14.0
305 : : */
306 : 1 : props[PROP_POLICY_MANAGER] =
307 : 1 : g_param_spec_object ("policy-manager", NULL, NULL,
308 : : MCT_TYPE_MANAGER,
309 : : G_PARAM_READWRITE |
310 : : G_PARAM_CONSTRUCT_ONLY |
311 : : G_PARAM_STATIC_STRINGS);
312 : :
313 : : /**
314 : : * MctChildTimerService:busy:
315 : : *
316 : : * True if the D-Bus API is busy.
317 : : *
318 : : * For example, if there are any outstanding method calls which haven’t been
319 : : * replied to yet.
320 : : *
321 : : * Since: 0.14.0
322 : : */
323 : 1 : props[PROP_BUSY] =
324 : 1 : g_param_spec_boolean ("busy", NULL, NULL,
325 : : FALSE,
326 : : G_PARAM_READABLE |
327 : : G_PARAM_STATIC_STRINGS);
328 : :
329 : 1 : g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
330 : :
331 : : /* Error domain registration for D-Bus. We do this here, rather than in a
332 : : * #GOnce section in mct_child_timer_service_error_quark(), to avoid spreading the
333 : : * D-Bus code outside this file. */
334 [ + + ]: 9 : for (size_t i = 0; i < G_N_ELEMENTS (child_timer_service_error_map); i++)
335 : 8 : g_dbus_error_register_error (MCT_CHILD_TIMER_SERVICE_ERROR,
336 : 8 : child_timer_service_error_map[i].error_code,
337 : 8 : child_timer_service_error_map[i].dbus_error_name);
338 : 1 : }
339 : :
340 : : static void
341 : 1 : mct_child_timer_service_init (MctChildTimerService *self)
342 : : {
343 : 1 : self->cancellable = g_cancellable_new ();
344 : 1 : self->pending_extension_agent_requests = g_ptr_array_new_with_free_func ((GDestroyNotify) request_extension_data_free);
345 : 1 : }
346 : :
347 : : static void
348 : 1 : mct_child_timer_service_constructed (GObject *object)
349 : : {
350 : 1 : MctChildTimerService *self = MCT_CHILD_TIMER_SERVICE (object);
351 : :
352 : : /* Chain up. */
353 : 1 : G_OBJECT_CLASS (mct_child_timer_service_parent_class)->constructed (object);
354 : :
355 : : /* Check our construct properties. */
356 : 1 : g_assert (G_IS_DBUS_CONNECTION (self->connection));
357 : 1 : g_assert (g_variant_is_object_path (self->object_path));
358 : 1 : g_assert (MCT_IS_TIMER_STORE (self->timer_store));
359 : 1 : g_assert (GSS_IS_PEER_MANAGER (self->peer_manager));
360 : 1 : g_assert (MCT_IS_MANAGER (self->policy_manager));
361 : :
362 : : /* Connect to signals on the timer store and policy manager. */
363 : 1 : self->timer_store_estimated_end_times_changed_id =
364 : 1 : g_signal_connect (self->timer_store, "estimated-end-times-changed",
365 : : G_CALLBACK (timer_store_estimated_end_times_changed_cb),
366 : : self);
367 : 1 : self->policy_manager_session_limits_changed_id =
368 : 1 : g_signal_connect (self->policy_manager, "session-limits-changed",
369 : : G_CALLBACK (policy_manager_session_limits_changed_cb),
370 : : self);
371 : 1 : }
372 : :
373 : : static void
374 : 0 : mct_child_timer_service_dispose (GObject *object)
375 : : {
376 : 0 : MctChildTimerService *self = MCT_CHILD_TIMER_SERVICE (object);
377 : :
378 : 0 : g_cancellable_cancel (self->cancellable);
379 [ # # ]: 0 : g_clear_object (&self->cancellable);
380 : :
381 : 0 : g_assert (self->object_id == 0);
382 : 0 : g_assert (self->n_pending_operations == 0);
383 : 0 : g_assert (self->pending_extension_agent_requests->len == 0);
384 : :
385 [ # # ]: 0 : g_clear_pointer (&self->pending_extension_agent_requests, g_ptr_array_unref);
386 : :
387 [ # # ]: 0 : g_clear_signal_handler (&self->policy_manager_session_limits_changed_id, self->policy_manager);
388 [ # # ]: 0 : g_clear_object (&self->policy_manager);
389 [ # # ]: 0 : g_clear_object (&self->peer_manager);
390 [ # # ]: 0 : g_clear_signal_handler (&self->timer_store_estimated_end_times_changed_id, self->timer_store);
391 [ # # ]: 0 : g_clear_object (&self->timer_store);
392 : :
393 [ # # ]: 0 : g_clear_object (&self->connection);
394 [ # # ]: 0 : g_clear_pointer (&self->object_path, g_free);
395 : :
396 : : /* Chain up to the parent class */
397 : 0 : G_OBJECT_CLASS (mct_child_timer_service_parent_class)->dispose (object);
398 : 0 : }
399 : :
400 : : static void
401 : 0 : mct_child_timer_service_get_property (GObject *object,
402 : : guint property_id,
403 : : GValue *value,
404 : : GParamSpec *pspec)
405 : : {
406 : 0 : MctChildTimerService *self = MCT_CHILD_TIMER_SERVICE (object);
407 : :
408 [ # # # # : 0 : switch ((MctChildTimerServiceProperty) property_id)
# # # ]
409 : : {
410 : 0 : case PROP_CONNECTION:
411 : 0 : g_value_set_object (value, self->connection);
412 : 0 : break;
413 : 0 : case PROP_OBJECT_PATH:
414 : 0 : g_value_set_string (value, self->object_path);
415 : 0 : break;
416 : 0 : case PROP_TIMER_STORE:
417 : 0 : g_value_set_object (value, self->timer_store);
418 : 0 : break;
419 : 0 : case PROP_PEER_MANAGER:
420 : 0 : g_value_set_object (value, self->peer_manager);
421 : 0 : break;
422 : 0 : case PROP_POLICY_MANAGER:
423 : 0 : g_value_set_object (value, self->policy_manager);
424 : 0 : break;
425 : 0 : case PROP_BUSY:
426 : 0 : g_value_set_boolean (value, mct_child_timer_service_get_busy (self));
427 : 0 : break;
428 : 0 : default:
429 : : g_assert_not_reached ();
430 : : }
431 : 0 : }
432 : :
433 : : static void
434 : 5 : mct_child_timer_service_set_property (GObject *object,
435 : : guint property_id,
436 : : const GValue *value,
437 : : GParamSpec *pspec)
438 : : {
439 : 5 : MctChildTimerService *self = MCT_CHILD_TIMER_SERVICE (object);
440 : :
441 [ + + + + : 5 : switch ((MctChildTimerServiceProperty) property_id)
+ - ]
442 : : {
443 : 1 : case PROP_CONNECTION:
444 : : /* Construct only. */
445 : 1 : g_assert (self->connection == NULL);
446 : 1 : self->connection = g_value_dup_object (value);
447 : 1 : break;
448 : 1 : case PROP_OBJECT_PATH:
449 : : /* Construct only. */
450 : 1 : g_assert (self->object_path == NULL);
451 : 1 : g_assert (g_variant_is_object_path (g_value_get_string (value)));
452 : 1 : self->object_path = g_value_dup_string (value);
453 : 1 : break;
454 : 1 : case PROP_TIMER_STORE:
455 : : /* Construct only. */
456 : 1 : g_assert (self->timer_store == NULL);
457 : 1 : self->timer_store = g_value_dup_object (value);
458 : 1 : break;
459 : 1 : case PROP_PEER_MANAGER:
460 : : /* Construct only. */
461 : 1 : g_assert (self->peer_manager == NULL);
462 : 1 : self->peer_manager = g_value_dup_object (value);
463 : 1 : break;
464 : 1 : case PROP_POLICY_MANAGER:
465 : : /* Construct only. */
466 : 1 : g_assert (self->policy_manager == NULL);
467 : 1 : self->policy_manager = g_value_dup_object (value);
468 : 1 : break;
469 : 0 : case PROP_BUSY:
470 : : /* Read only. Fall through. */
471 : : G_GNUC_FALLTHROUGH;
472 : : default:
473 : : g_assert_not_reached ();
474 : : }
475 : 5 : }
476 : :
477 : : static void
478 : 0 : emit_estimated_times_changed (MctChildTimerService *self)
479 : : {
480 : : /* Ignore errors from emitting the signal; it can only fail if the parameters
481 : : * are invalid (not possible) or if the connection has been closed. */
482 : 0 : g_dbus_connection_emit_signal (self->connection,
483 : : NULL, /* destination bus name */
484 : 0 : self->object_path,
485 : : "org.freedesktop.MalcontentTimer1.Child",
486 : : "EstimatedTimesChanged",
487 : : NULL,
488 : : NULL);
489 : 0 : }
490 : :
491 : : static void
492 : 0 : timer_store_estimated_end_times_changed_cb (MctTimerStore *timer_store,
493 : : const char *username,
494 : : void *user_data)
495 : : {
496 : 0 : MctChildTimerService *self = MCT_CHILD_TIMER_SERVICE (user_data);
497 : :
498 : 0 : emit_estimated_times_changed (self);
499 : 0 : }
500 : :
501 : : static void
502 : 0 : policy_manager_session_limits_changed_cb (MctManager *policy_manager,
503 : : uint64_t uid,
504 : : void *user_data)
505 : : {
506 : 0 : MctChildTimerService *self = MCT_CHILD_TIMER_SERVICE (user_data);
507 : :
508 : : /* A change in the session limits policy will _probably_ result in the
509 : : * estimated end time changing. It might not, though, so this signal will be
510 : : * over-emitted. Simpler than calculating exactly whether it has changed,
511 : : * though, as that would require retaining state from the last time it was
512 : : * emitted.
513 : : *
514 : : * Note that malcontent-timerd may not even be running when the session limits
515 : : * policy is changed, so this signal is not _guaranteed_ to be emitted.
516 : : * Clients should also listen to MctManager::session-limits-changed
517 : : * themselves. */
518 : 0 : emit_estimated_times_changed (self);
519 : 0 : }
520 : :
521 : : /**
522 : : * mct_child_timer_service_register:
523 : : * @self: a child service
524 : : * @error: return location for a [type@GLib.Error]
525 : : *
526 : : * Register the child timer service objects on D-Bus using the connection
527 : : * details given in [property@Malcontent.ChildTimerService.connection] and
528 : : * [property@Malcontent.ChildTimerService.object-path].
529 : : *
530 : : * Use [method@Malcontent.ChildTimerService.unregister] to unregister them.
531 : : * Calls to these two functions must be well paired.
532 : : *
533 : : * Returns: true on success, false otherwise
534 : : * Since: 0.14.0
535 : : */
536 : : gboolean
537 : 1 : mct_child_timer_service_register (MctChildTimerService *self,
538 : : GError **error)
539 : : {
540 : 1 : g_return_val_if_fail (MCT_IS_CHILD_TIMER_SERVICE (self), FALSE);
541 : 1 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
542 : :
543 : 1 : const GDBusInterfaceVTable interface_vtable =
544 : : {
545 : : mct_child_timer_service_method_call,
546 : : NULL, /* handled in mct_child_timer_service_method_call() */
547 : : NULL, /* handled in mct_child_timer_service_method_call() */
548 : : { NULL, }, /* padding */
549 : : };
550 : :
551 : 1 : guint id = g_dbus_connection_register_object (self->connection,
552 : 1 : self->object_path,
553 : : (GDBusInterfaceInfo *) &org_freedesktop_malcontent_timer1_child_interface,
554 : : &interface_vtable,
555 : : g_object_ref (self),
556 : : g_object_unref,
557 : : error);
558 : :
559 [ - + ]: 1 : if (id == 0)
560 : 0 : return FALSE;
561 : :
562 : 1 : self->object_id = id;
563 : :
564 : : /* This has potentially changed. */
565 : 1 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUSY]);
566 : :
567 : 1 : return TRUE;
568 : : }
569 : :
570 : : /**
571 : : * mct_child_timer_service_unregister:
572 : : * @self: a child service
573 : : *
574 : : * Unregister objects from D-Bus which were previously registered using
575 : : * [method@Malcontent.ChildTimerService.register].
576 : : *
577 : : * Calls to these two functions must be well paired.
578 : : *
579 : : * Since: 0.14.0
580 : : */
581 : : void
582 : 1 : mct_child_timer_service_unregister (MctChildTimerService *self)
583 : : {
584 : 1 : g_return_if_fail (MCT_IS_CHILD_TIMER_SERVICE (self));
585 : :
586 : 1 : g_cancellable_cancel (self->cancellable);
587 : :
588 : 1 : g_dbus_connection_unregister_object (self->connection, self->object_id);
589 : 1 : self->object_id = 0;
590 : :
591 : : /* This has potentially changed. */
592 : 1 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUSY]);
593 : : }
594 : :
595 : : static char *
596 : 0 : lookup_username (uid_t uid,
597 : : GError **error)
598 : : {
599 : : char buffer[4096];
600 : : struct passwd pwbuf;
601 : : struct passwd *result;
602 : : int pwuid_errno;
603 : 0 : g_autofree char *username = NULL;
604 : 0 : g_autoptr(GError) local_error = NULL;
605 : :
606 : 0 : pwuid_errno = getpwuid_r (uid, &pwbuf, buffer, sizeof (buffer), &result);
607 : :
608 [ # # ]: 0 : if (result != NULL &&
609 [ # # # # ]: 0 : result->pw_name != NULL && result->pw_name[0] != '\0')
610 : : {
611 : 0 : username = g_locale_to_utf8 (result->pw_name, -1, NULL, NULL, &local_error);
612 [ # # ]: 0 : if (username == NULL)
613 : : {
614 : 0 : g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
615 : : _("Error getting details for UID %d: %s"),
616 : 0 : (int) uid, local_error->message);
617 : 0 : return NULL;
618 : : }
619 : : }
620 [ # # ]: 0 : else if (result != NULL)
621 : : {
622 : 0 : username = g_strdup_printf ("%d", (int) uid);
623 : : }
624 [ # # ]: 0 : else if (pwuid_errno == 0)
625 : : {
626 : : /* User not found. */
627 : 0 : return NULL;
628 : : }
629 : : else
630 : : {
631 : : /* Error calling getpwuid_r(). */
632 : 0 : g_set_error (error, G_IO_ERROR, g_io_error_from_errno (pwuid_errno),
633 : : _("Error getting details for UID %d: %s"),
634 : : (int) uid, g_strerror (pwuid_errno));
635 : 0 : return NULL;
636 : : }
637 : :
638 : 0 : return g_steal_pointer (&username);
639 : : }
640 : :
641 : : static char *
642 : 0 : lookup_username_for_peer_invocation (GssPeerManager *peer_manager,
643 : : GDBusMethodInvocation *invocation,
644 : : uid_t *out_uid)
645 : : {
646 : : uid_t uid;
647 : 0 : g_autoptr(GError) local_error = NULL;
648 : 0 : g_autofree char *username = NULL;
649 : :
650 : 0 : uid = gss_peer_manager_get_peer_uid (peer_manager, g_dbus_method_invocation_get_sender (invocation));
651 [ # # # # ]: 0 : if (uid != 0 && uid != (uid_t) -1)
652 : 0 : username = lookup_username (uid, &local_error);
653 [ # # ]: 0 : if (local_error != NULL)
654 : 0 : g_debug ("Error looking up username for UID %d: %s", (int) uid, local_error->message);
655 : :
656 [ # # ]: 0 : if (out_uid != NULL)
657 : 0 : *out_uid = uid;
658 : :
659 : 0 : return g_steal_pointer (&username);
660 : : }
661 : :
662 : : static gboolean
663 : 0 : validate_dbus_interface_name (GDBusMethodInvocation *invocation,
664 : : const gchar *interface_name)
665 : : {
666 [ # # ]: 0 : if (!g_dbus_is_interface_name (interface_name))
667 : : {
668 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
669 : : G_DBUS_ERROR_UNKNOWN_INTERFACE,
670 : : _("Invalid interface name ‘%s’."),
671 : : interface_name);
672 : 0 : return FALSE;
673 : : }
674 : :
675 : 0 : return TRUE;
676 : : }
677 : :
678 : : static gboolean
679 : 0 : validate_record_type (GDBusMethodInvocation *invocation,
680 : : const char *record_type)
681 : : {
682 : 0 : g_autoptr(GError) local_error = NULL;
683 : :
684 [ # # ]: 0 : if (!mct_timer_store_record_type_validate_string (record_type, &local_error))
685 : : {
686 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
687 : : MCT_CHILD_TIMER_SERVICE_ERROR_INVALID_RECORD,
688 : : _("Invalid usage parameters: %s"),
689 : 0 : local_error->message);
690 : 0 : return FALSE;
691 : : }
692 : :
693 : 0 : return TRUE;
694 : : }
695 : :
696 : : static gboolean
697 : 0 : validate_record_type_and_identifier (GDBusMethodInvocation *invocation,
698 : : const char *record_type,
699 : : const char *identifier)
700 : : {
701 : 0 : g_autoptr(GError) local_error = NULL;
702 : :
703 [ # # # # ]: 0 : if (!mct_timer_store_record_type_validate_string (record_type, &local_error) ||
704 : 0 : !mct_timer_store_record_type_validate_identifier (mct_timer_store_record_type_from_string (record_type), identifier, &local_error))
705 : : {
706 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
707 : : MCT_CHILD_TIMER_SERVICE_ERROR_INVALID_RECORD,
708 : : _("Invalid usage parameters: %s"),
709 : 0 : local_error->message);
710 : 0 : return FALSE;
711 : : }
712 : :
713 : 0 : return TRUE;
714 : : }
715 : :
716 : : typedef void (*ChildMethodCallFunc) (MctChildTimerService *self,
717 : : GDBusConnection *connection,
718 : : const char *sender,
719 : : GVariant *parameters,
720 : : GDBusMethodInvocation *invocation);
721 : :
722 : : static const struct
723 : : {
724 : : const char *interface_name;
725 : : const char *method_name;
726 : : ChildMethodCallFunc func;
727 : : }
728 : : child_methods[] =
729 : : {
730 : : /* Handle properties. */
731 : : { "org.freedesktop.DBus.Properties", "Get",
732 : : mct_child_timer_service_properties_get },
733 : : { "org.freedesktop.DBus.Properties", "Set",
734 : : mct_child_timer_service_properties_set },
735 : : { "org.freedesktop.DBus.Properties", "GetAll",
736 : : mct_child_timer_service_properties_get_all },
737 : :
738 : : /* Child methods. */
739 : : { "org.freedesktop.MalcontentTimer1.Child", "RecordUsage",
740 : : mct_child_timer_service_record_usage },
741 : : { "org.freedesktop.MalcontentTimer1.Child", "GetEstimatedTimes",
742 : : mct_child_timer_service_get_estimated_times },
743 : : { "org.freedesktop.MalcontentTimer1.Child", "RequestExtension",
744 : : mct_child_timer_service_request_extension },
745 : : };
746 : :
747 : : static void
748 : 0 : mct_child_timer_service_method_call (GDBusConnection *connection,
749 : : const char *sender,
750 : : const char *object_path,
751 : : const char *interface_name,
752 : : const char *method_name,
753 : : GVariant *parameters,
754 : : GDBusMethodInvocation *invocation,
755 : : void *user_data)
756 : : {
757 : 0 : MctChildTimerService *self = MCT_CHILD_TIMER_SERVICE (user_data);
758 : :
759 : : /* Check we’ve implemented all the methods. Unfortunately this can’t be a
760 : : * compile time check because the method array is declared in a separate
761 : : * compilation unit. */
762 : 0 : size_t n_child_interface_methods = 0;
763 [ # # ]: 0 : for (size_t i = 0; org_freedesktop_malcontent_timer1_child_interface.methods[i] != NULL; i++)
764 : 0 : n_child_interface_methods++;
765 : :
766 : 0 : g_assert (G_N_ELEMENTS (child_methods) ==
767 : : n_child_interface_methods +
768 : : 3 /* o.fdo.DBus.Properties */);
769 : :
770 : : /* Remove the service prefix from the path. */
771 : 0 : g_assert (g_str_equal (object_path, self->object_path));
772 : :
773 : : /* Work out which method to call. */
774 [ # # ]: 0 : for (gsize i = 0; i < G_N_ELEMENTS (child_methods); i++)
775 : : {
776 [ # # ]: 0 : if (g_str_equal (child_methods[i].interface_name, interface_name) &&
777 [ # # ]: 0 : g_str_equal (child_methods[i].method_name, method_name))
778 : : {
779 : 0 : child_methods[i].func (self, connection, sender, parameters, invocation);
780 : 0 : return;
781 : : }
782 : : }
783 : :
784 : : /* Make sure we actually called a method implementation. GIO guarantees that
785 : : * this function is only called with methods we’ve declared in the interface
786 : : * info, so this should never fail. */
787 : : g_assert_not_reached ();
788 : : }
789 : :
790 : : static void
791 : 0 : mct_child_timer_service_properties_get (MctChildTimerService *self,
792 : : GDBusConnection *connection,
793 : : const char *sender,
794 : : GVariant *parameters,
795 : : GDBusMethodInvocation *invocation)
796 : : {
797 : : const char *interface_name, *property_name;
798 : 0 : g_variant_get (parameters, "(&s&s)", &interface_name, &property_name);
799 : :
800 : : /* D-Bus property names can be anything. */
801 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
802 : 0 : return;
803 : :
804 : : /* No properties exposed. */
805 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
806 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
807 : : _("Unknown property ‘%s.%s’."),
808 : : interface_name, property_name);
809 : : }
810 : :
811 : : static void
812 : 0 : mct_child_timer_service_properties_set (MctChildTimerService *self,
813 : : GDBusConnection *connection,
814 : : const char *sender,
815 : : GVariant *parameters,
816 : : GDBusMethodInvocation *invocation)
817 : : {
818 : : const char *interface_name, *property_name;
819 : 0 : g_variant_get (parameters, "(&s&sv)", &interface_name, &property_name, NULL);
820 : :
821 : : /* D-Bus property names can be anything. */
822 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
823 : 0 : return;
824 : :
825 : : /* No properties exposed. */
826 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
827 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
828 : : _("Unknown property ‘%s.%s’."),
829 : : interface_name, property_name);
830 : : }
831 : :
832 : : static void
833 : 0 : mct_child_timer_service_properties_get_all (MctChildTimerService *self,
834 : : GDBusConnection *connection,
835 : : const char *sender,
836 : : GVariant *parameters,
837 : : GDBusMethodInvocation *invocation)
838 : : {
839 : : const char *interface_name;
840 : 0 : g_variant_get (parameters, "(&s)", &interface_name);
841 : :
842 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
843 : 0 : return;
844 : :
845 : : /* Try the interface. */
846 [ # # ]: 0 : if (g_str_equal (interface_name, "org.freedesktop.MalcontentTimer1.Child"))
847 : 0 : g_dbus_method_invocation_return_value (invocation,
848 : : g_variant_new_parsed ("(@a{sv} {},)"));
849 : : else
850 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
851 : : G_DBUS_ERROR_UNKNOWN_INTERFACE,
852 : : _("Unknown interface ‘%s’."),
853 : : interface_name);
854 : : }
855 : :
856 : : typedef struct
857 : : {
858 : : MctChildTimerService *child_timer_service; /* (owned) */
859 : : GHashTable *time_spans_hash; /* (owned) (element-type utf8 GPtrArray<MctTimeSpan>) */
860 : : GDBusMethodInvocation *invocation; /* (owned) */
861 : : uint64_t now_secs;
862 : : MctOperationCounter operation_counter;
863 : : } RecordUsageData;
864 : :
865 : : static void
866 : 0 : record_usage_data_free (RecordUsageData *data)
867 : : {
868 [ # # ]: 0 : g_clear_object (&data->invocation);
869 [ # # ]: 0 : g_clear_pointer (&data->time_spans_hash, g_hash_table_unref);
870 [ # # ]: 0 : g_clear_object (&data->child_timer_service);
871 : 0 : mct_operation_counter_release_and_clear (&data->operation_counter);
872 : 0 : g_free (data);
873 : 0 : }
874 : :
875 [ # # ]: 0 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (RecordUsageData, record_usage_data_free)
876 : :
877 : : static void record_usage_ensure_credentials_cb (GObject *object,
878 : : GAsyncResult *result,
879 : : void *user_data);
880 : : static void record_usage_open_username_cb (GObject *object,
881 : : GAsyncResult *result,
882 : : void *user_data);
883 : : static void record_usage_save_transaction_cb (GObject *object,
884 : : GAsyncResult *result,
885 : : void *user_data);
886 : :
887 : : static void
888 : 0 : mct_child_timer_service_record_usage (MctChildTimerService *self,
889 : : GDBusConnection *connection,
890 : : const char *sender,
891 : : GVariant *parameters,
892 : : GDBusMethodInvocation *invocation)
893 : : {
894 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
895 [ # # ]: 0 : g_autoptr(GVariantIter) entries_iter = NULL;
896 : : uint64_t now_secs, start_time, end_time;
897 : : const char *record_type, *identifier;
898 [ # # ]: 0 : g_autoptr(GHashTable) time_spans_hash = NULL;
899 : : GPtrArray *time_spans; /* (element-type MctTimeSpan) */
900 [ # # ]: 0 : g_autoptr(RecordUsageData) data = NULL;
901 : :
902 : : /* Validate the parameters and add them to the timer store. */
903 : 0 : g_variant_get (parameters, "(a(ttss))", &entries_iter);
904 : 0 : time_spans_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_ptr_array_unref);
905 : 0 : now_secs = (uint64_t) g_get_real_time () / G_USEC_PER_SEC;
906 : :
907 : : /* Validate each entry. */
908 [ # # ]: 0 : while (g_variant_iter_loop (entries_iter, "(tt&s&s)", &start_time, &end_time, &record_type, &identifier))
909 : : {
910 [ # # ]: 0 : if (start_time > end_time)
911 : : {
912 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
913 : : MCT_CHILD_TIMER_SERVICE_ERROR_INVALID_RECORD,
914 : : _("Invalid usage parameters: %s"),
915 : 0 : _("Times out of order"));
916 : 0 : return;
917 : : }
918 [ # # ]: 0 : if (end_time > now_secs)
919 : : {
920 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
921 : : MCT_CHILD_TIMER_SERVICE_ERROR_INVALID_RECORD,
922 : : _("Invalid usage parameters: %s"),
923 : 0 : _("End time in the future"));
924 : 0 : return;
925 : : }
926 [ # # ]: 0 : if (!validate_record_type_and_identifier (invocation, record_type, identifier))
927 : 0 : return;
928 : :
929 : : /* Add to the in-progress data. */
930 : 0 : time_spans = g_hash_table_lookup (time_spans_hash, identifier);
931 [ # # ]: 0 : if (time_spans == NULL)
932 : : {
933 : 0 : time_spans = g_ptr_array_new_with_free_func ((GDestroyNotify) mct_time_span_free);
934 : 0 : g_hash_table_insert (time_spans_hash, (void *) identifier, time_spans);
935 : : }
936 : 0 : g_ptr_array_add (time_spans, mct_time_span_new (start_time, end_time));
937 : : }
938 : :
939 [ # # ]: 0 : if (g_hash_table_size (time_spans_hash) == 0)
940 : : {
941 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
942 : : MCT_CHILD_TIMER_SERVICE_ERROR_INVALID_RECORD,
943 : : _("Invalid usage parameters: %s"),
944 : 0 : _("No entries"));
945 : 0 : return;
946 : : }
947 : :
948 : : /* Load the peer’s credentials so we know which user the usage entries come
949 : : * from. */
950 : 0 : data = g_new0 (RecordUsageData, 1);
951 : 0 : data->child_timer_service = g_object_ref (self);
952 : 0 : data->time_spans_hash = g_steal_pointer (&time_spans_hash);
953 : 0 : data->invocation = g_object_ref (invocation);
954 : 0 : data->now_secs = now_secs;
955 : 0 : mct_operation_counter_init_and_hold (&data->operation_counter,
956 : : &self->n_pending_operations,
957 : 0 : G_OBJECT (self), props[PROP_BUSY]);
958 : :
959 : 0 : gss_peer_manager_ensure_peer_credentials_async (self->peer_manager,
960 : : sender,
961 : : self->cancellable,
962 : : record_usage_ensure_credentials_cb,
963 : : g_steal_pointer (&data));
964 : : }
965 : :
966 : : static void
967 : 0 : record_usage_ensure_credentials_cb (GObject *object,
968 : : GAsyncResult *result,
969 : : void *user_data)
970 : : {
971 : 0 : GssPeerManager *peer_manager = GSS_PEER_MANAGER (object);
972 [ # # ]: 0 : g_autoptr(RecordUsageData) data = g_steal_pointer (&user_data);
973 : 0 : MctChildTimerService *self = data->child_timer_service;
974 : 0 : GDBusMethodInvocation *invocation = data->invocation;
975 [ # # ]: 0 : g_autofree char *username = NULL;
976 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
977 : :
978 : : /* Finish looking up the sender. */
979 [ # # ]: 0 : if (!gss_peer_manager_ensure_peer_credentials_finish (peer_manager,
980 : : result, &local_error))
981 : : {
982 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
983 : : MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
984 : : _("Error identifying user: %s"),
985 : 0 : local_error->message);
986 : 0 : return;
987 : : }
988 : :
989 : 0 : username = lookup_username_for_peer_invocation (peer_manager, invocation, NULL);
990 [ # # ]: 0 : if (username == NULL)
991 : : {
992 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
993 : : MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
994 : : _("Error identifying user: %s"),
995 : 0 : _("Invalid or unknown user"));
996 : 0 : return;
997 : : }
998 : :
999 : 0 : mct_timer_store_open_username_async (self->timer_store,
1000 : : username,
1001 : : self->cancellable,
1002 : : record_usage_open_username_cb,
1003 : 0 : g_steal_pointer (&data));
1004 : : }
1005 : :
1006 : : static void
1007 : 0 : record_usage_open_username_cb (GObject *object,
1008 : : GAsyncResult *result,
1009 : : void *user_data)
1010 : : {
1011 : 0 : MctTimerStore *timer_store = MCT_TIMER_STORE (object);
1012 [ # # ]: 0 : g_autoptr(RecordUsageData) data = g_steal_pointer (&user_data);
1013 : 0 : MctChildTimerService *self = data->child_timer_service;
1014 : 0 : GDBusMethodInvocation *invocation = data->invocation;
1015 : 0 : GHashTable *time_spans_hash = data->time_spans_hash;
1016 : : const MctTimerStoreTransaction *transaction;
1017 : : GHashTableIter time_spans_hash_iter;
1018 : : const char *identifier;
1019 : : GPtrArray *time_spans; /* (element-type MctTimeSpan) */
1020 : : uint64_t expiry_cutoff_secs;
1021 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
1022 : :
1023 : 0 : transaction = mct_timer_store_open_username_finish (timer_store, result, &local_error);
1024 : :
1025 [ # # ]: 0 : if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY))
1026 : : {
1027 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1028 : : MCT_CHILD_TIMER_SERVICE_ERROR_BUSY,
1029 : : _("Error opening user file: %s"),
1030 : 0 : local_error->message);
1031 : 0 : return;
1032 : : }
1033 [ # # ]: 0 : else if (local_error != NULL) /* likely a GFileError */
1034 : : {
1035 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1036 : : MCT_CHILD_TIMER_SERVICE_ERROR_STORAGE_ERROR,
1037 : : _("Error opening user file: %s"),
1038 : 0 : local_error->message);
1039 : 0 : return;
1040 : : }
1041 : :
1042 : : /* Now all the time spans have been validated, add them to the timer store. */
1043 : 0 : g_hash_table_iter_init (&time_spans_hash_iter, time_spans_hash);
1044 [ # # ]: 0 : while (g_hash_table_iter_next (&time_spans_hash_iter, (void **) &identifier, (void **) &time_spans))
1045 : : {
1046 : : MctTimerStoreRecordType record_type;
1047 : :
1048 : : /* For the moment, we can rely on the fact that the identifier spaces for
1049 : : * the two current record types are disjoint, so we don’t have to
1050 : : * explicitly pass the @record_type via the @time_spans_hash. We may have
1051 : : * to in future, though, if more record types are added. */
1052 [ # # ]: 0 : if (*identifier == '\0')
1053 : 0 : record_type = MCT_TIMER_STORE_RECORD_TYPE_LOGIN_SESSION;
1054 : : else
1055 : 0 : record_type = MCT_TIMER_STORE_RECORD_TYPE_APP;
1056 : :
1057 : 0 : mct_timer_store_add_time_spans (timer_store, transaction, record_type, identifier,
1058 : 0 : (const MctTimeSpan * const *) time_spans->pdata,
1059 : 0 : time_spans->len);
1060 : : }
1061 : :
1062 : : /* Expire old entries when saving.
1063 : : * FIXME: Make this configurable */
1064 : 0 : expiry_cutoff_secs = data->now_secs - 4 * 7 * 24 * 60 * 60;
1065 : :
1066 : : /* Save the changes. */
1067 : 0 : mct_timer_store_save_transaction_async (timer_store,
1068 : : transaction,
1069 : : expiry_cutoff_secs,
1070 : : self->cancellable,
1071 : : record_usage_save_transaction_cb,
1072 : 0 : g_steal_pointer (&data));
1073 : : }
1074 : :
1075 : : static void
1076 : 0 : record_usage_save_transaction_cb (GObject *object,
1077 : : GAsyncResult *result,
1078 : : void *user_data)
1079 : : {
1080 : 0 : MctTimerStore *timer_store = MCT_TIMER_STORE (object);
1081 : 0 : g_autoptr(RecordUsageData) data = g_steal_pointer (&user_data);
1082 : 0 : GDBusMethodInvocation *invocation = data->invocation;
1083 : 0 : g_autoptr(GError) local_error = NULL;
1084 : :
1085 [ # # ]: 0 : if (!mct_timer_store_save_transaction_finish (timer_store, result, &local_error))
1086 : : {
1087 : : /* The error is likely a GIOError */
1088 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1089 : : MCT_CHILD_TIMER_SERVICE_ERROR_STORAGE_ERROR,
1090 : 0 : _("Error saving user file: %s"), local_error->message);
1091 : : }
1092 : : else
1093 : : {
1094 : 0 : g_dbus_method_invocation_return_value (invocation, NULL);
1095 : : }
1096 : 0 : }
1097 : :
1098 : : typedef struct
1099 : : {
1100 : : MctChildTimerService *child_timer_service; /* (owned) */
1101 : : GDBusMethodInvocation *invocation; /* (owned) */
1102 : : MctTimerStoreRecordType record_type;
1103 : : char *username; /* (nullable) (owned) */
1104 : : MctSessionLimits *session_limits_policy; /* (nullable) (owned) */
1105 : : MctOperationCounter operation_counter;
1106 : : unsigned int n_open_retries;
1107 : : } GetEstimatedTimesData;
1108 : :
1109 : : static void
1110 : 0 : get_estimated_times_data_free (GetEstimatedTimesData *data)
1111 : : {
1112 [ # # ]: 0 : g_clear_object (&data->invocation);
1113 [ # # ]: 0 : g_clear_object (&data->child_timer_service);
1114 : 0 : g_free (data->username);
1115 [ # # ]: 0 : g_clear_pointer (&data->session_limits_policy, mct_session_limits_unref);
1116 : 0 : mct_operation_counter_release_and_clear (&data->operation_counter);
1117 : 0 : g_free (data);
1118 : 0 : }
1119 : :
1120 [ # # ]: 0 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (GetEstimatedTimesData, get_estimated_times_data_free)
1121 : :
1122 : : static void get_estimated_times_ensure_credentials_cb (GObject *object,
1123 : : GAsyncResult *result,
1124 : : void *user_data);
1125 : : static void get_estimated_times_get_session_limits_cb (GObject *object,
1126 : : GAsyncResult *result,
1127 : : void *user_data);
1128 : : static void get_estimated_times_open_username_cb (GObject *object,
1129 : : GAsyncResult *result,
1130 : : void *user_data);
1131 : :
1132 : : static void
1133 : 0 : mct_child_timer_service_get_estimated_times (MctChildTimerService *self,
1134 : : GDBusConnection *connection,
1135 : : const char *sender,
1136 : : GVariant *parameters,
1137 : : GDBusMethodInvocation *invocation)
1138 : : {
1139 [ # # ]: 0 : g_autoptr(GetEstimatedTimesData) data = NULL;
1140 : 0 : const char *record_type = NULL;
1141 : :
1142 : 0 : g_variant_get (parameters, "(&s)", &record_type);
1143 : :
1144 [ # # ]: 0 : if (!validate_record_type (invocation, record_type))
1145 : 0 : return;
1146 : :
1147 : : /* Load the peer’s credentials so we know which user is querying the usage. */
1148 : 0 : data = g_new0 (GetEstimatedTimesData, 1);
1149 : 0 : data->child_timer_service = g_object_ref (self);
1150 : 0 : data->invocation = g_object_ref (invocation);
1151 : 0 : data->record_type = mct_timer_store_record_type_from_string (record_type);
1152 : 0 : mct_operation_counter_init_and_hold (&data->operation_counter,
1153 : : &self->n_pending_operations,
1154 : 0 : G_OBJECT (self), props[PROP_BUSY]);
1155 : :
1156 : 0 : gss_peer_manager_ensure_peer_credentials_async (self->peer_manager,
1157 : : sender,
1158 : : self->cancellable,
1159 : : get_estimated_times_ensure_credentials_cb,
1160 : : g_steal_pointer (&data));
1161 : : }
1162 : :
1163 : : static void
1164 : 0 : get_estimated_times_ensure_credentials_cb (GObject *object,
1165 : : GAsyncResult *result,
1166 : : void *user_data)
1167 : : {
1168 : 0 : GssPeerManager *peer_manager = GSS_PEER_MANAGER (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 : : GDBusMessage *message;
1173 : : gboolean interactive;
1174 [ # # ]: 0 : g_autofree char *username = NULL;
1175 : : uid_t uid;
1176 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
1177 : :
1178 : : /* Finish looking up the sender. */
1179 [ # # ]: 0 : if (!gss_peer_manager_ensure_peer_credentials_finish (peer_manager,
1180 : : result, &local_error))
1181 : : {
1182 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1183 : : MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
1184 : : _("Error identifying user: %s"),
1185 : 0 : local_error->message);
1186 : 0 : return;
1187 : : }
1188 : :
1189 : 0 : username = lookup_username_for_peer_invocation (peer_manager, invocation, &uid);
1190 [ # # ]: 0 : if (username == NULL)
1191 : : {
1192 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1193 : : MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
1194 : : _("Error identifying user: %s"),
1195 : 0 : _("Invalid or unknown user"));
1196 : 0 : return;
1197 : : }
1198 : :
1199 : 0 : data->username = g_steal_pointer (&username);
1200 : :
1201 : : /* Query the user’s session limits policy. */
1202 : 0 : message = g_dbus_method_invocation_get_message (invocation);
1203 : 0 : interactive = g_dbus_message_get_flags (message) & G_DBUS_MESSAGE_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION;
1204 : :
1205 : 0 : mct_manager_get_session_limits_async (self->policy_manager,
1206 : : uid,
1207 : : interactive ? MCT_MANAGER_GET_VALUE_FLAGS_INTERACTIVE : MCT_MANAGER_GET_VALUE_FLAGS_NONE,
1208 : : self->cancellable,
1209 : : get_estimated_times_get_session_limits_cb,
1210 : : g_steal_pointer (&data));
1211 : : }
1212 : :
1213 : : static void
1214 : 0 : get_estimated_times_get_session_limits_cb (GObject *object,
1215 : : GAsyncResult *result,
1216 : : void *user_data)
1217 : : {
1218 : 0 : MctManager *policy_manager = MCT_MANAGER (object);
1219 [ # # ]: 0 : g_autoptr(GetEstimatedTimesData) data = g_steal_pointer (&user_data);
1220 : 0 : MctChildTimerService *self = data->child_timer_service;
1221 : 0 : GDBusMethodInvocation *invocation = data->invocation;
1222 : 0 : const char *username = data->username;
1223 [ # # ]: 0 : g_autoptr(MctSessionLimits) session_limits_policy = NULL;
1224 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
1225 : :
1226 : 0 : session_limits_policy = mct_manager_get_session_limits_finish (policy_manager,
1227 : : result, &local_error);
1228 : :
1229 [ # # # # ]: 0 : if (g_error_matches (local_error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_INVALID_USER) ||
1230 : 0 : g_error_matches (local_error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_PERMISSION_DENIED))
1231 : : {
1232 : : /* MCT_MANAGER_ERROR_INVALID_USER shouldn’t really happen as we’ve
1233 : : * already identified the user, but I guess there could be a problem
1234 : : * inside AccountsssService.
1235 : : *
1236 : : * MCT_MANAGER_ERROR_PERMISSION_DENIED definitely shouldn’t happen:
1237 : : * the user should always have permissions to query their own session
1238 : : * limits, otherwise the install must be corrupt. */
1239 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1240 : : MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
1241 : : _("Error identifying user: %s"),
1242 : 0 : local_error->message);
1243 : 0 : return;
1244 : : }
1245 [ # # ]: 0 : else if (g_error_matches (local_error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_DISABLED))
1246 : : {
1247 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1248 : : MCT_CHILD_TIMER_SERVICE_ERROR_DISABLED,
1249 : : _("Recording usage is disabled"));
1250 : 0 : return;
1251 : : }
1252 [ # # ]: 0 : else if (local_error != NULL)
1253 : : {
1254 : : /* Likely a GDBusError or GIOError, or MCT_MANAGER_ERROR_INVALID_DATA */
1255 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1256 : : MCT_CHILD_TIMER_SERVICE_ERROR_QUERYING_POLICY,
1257 : : _("Error querying user parental controls policy: %s"),
1258 : 0 : local_error->message);
1259 : 0 : return;
1260 : : }
1261 : :
1262 : 0 : data->session_limits_policy = g_steal_pointer (&session_limits_policy);
1263 : :
1264 : : /* Now open the user’s file. */
1265 : 0 : mct_timer_store_open_username_async (self->timer_store,
1266 : : username,
1267 : : self->cancellable,
1268 : : get_estimated_times_open_username_cb,
1269 : 0 : g_steal_pointer (&data));
1270 : : }
1271 : :
1272 : : static void
1273 : 0 : get_estimated_times_open_username_cb (GObject *object,
1274 : : GAsyncResult *result,
1275 : : void *user_data)
1276 : : {
1277 : 0 : MctTimerStore *timer_store = MCT_TIMER_STORE (object);
1278 [ # # ]: 0 : g_autoptr(GetEstimatedTimesData) data = g_steal_pointer (&user_data);
1279 : 0 : MctChildTimerService *self = data->child_timer_service;
1280 : 0 : GDBusMethodInvocation *invocation = data->invocation;
1281 : 0 : const char *username = data->username;
1282 : : const MctTimerStoreTransaction *transaction;
1283 [ # # ]: 0 : g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{s(btttt)}"));
1284 [ # # ]: 0 : g_autoptr(GHashTable) total_times_so_far_today = NULL; /* (element-type utf8 uint64_t) */
1285 : : uint64_t start_of_today_secs, now_secs, now_time_of_day_secs, start_of_tomorrow_secs;
1286 [ # # ]: 0 : g_autoptr(GDateTime) now_date_time = NULL;
1287 [ # # ]: 0 : g_autoptr(GDateTime) start_of_today = NULL;
1288 [ # # ]: 0 : g_autoptr(GDateTime) start_of_tomorrow = NULL;
1289 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
1290 : :
1291 : 0 : transaction = mct_timer_store_open_username_finish (timer_store, result, &local_error);
1292 : :
1293 [ # # # # ]: 0 : if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY) && data->n_open_retries >= 10)
1294 : : {
1295 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1296 : : MCT_CHILD_TIMER_SERVICE_ERROR_BUSY,
1297 : : _("Error opening user file: %s"),
1298 : 0 : local_error->message);
1299 : 0 : return;
1300 : : }
1301 [ # # ]: 0 : else if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY))
1302 : : {
1303 : : /* Try again */
1304 : 0 : data->n_open_retries++;
1305 : 0 : mct_timer_store_open_username_async (timer_store,
1306 : : username,
1307 : : self->cancellable,
1308 : : get_estimated_times_open_username_cb,
1309 : 0 : g_steal_pointer (&data));
1310 : 0 : return;
1311 : : }
1312 [ # # ]: 0 : else if (local_error != NULL) /* likely a GFileError */
1313 : : {
1314 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1315 : : MCT_CHILD_TIMER_SERVICE_ERROR_STORAGE_ERROR,
1316 : : _("Error opening user file: %s"),
1317 : 0 : local_error->message);
1318 : 0 : return;
1319 : : }
1320 : :
1321 : : /* Work out some times. */
1322 : 0 : now_secs = g_get_real_time () / G_USEC_PER_SEC;
1323 : :
1324 : 0 : now_date_time = g_date_time_new_from_unix_local (now_secs);
1325 : 0 : now_time_of_day_secs = ((g_date_time_get_hour (now_date_time) * 60 +
1326 : 0 : g_date_time_get_minute (now_date_time)) * 60 +
1327 : 0 : g_date_time_get_second (now_date_time));
1328 : 0 : start_of_today = g_date_time_new_local (g_date_time_get_year (now_date_time),
1329 : : g_date_time_get_month (now_date_time),
1330 : : g_date_time_get_day_of_month (now_date_time),
1331 : : 0, 0, 0);
1332 : 0 : start_of_tomorrow = g_date_time_add_days (start_of_today, 1);
1333 : 0 : g_assert (start_of_today != NULL);
1334 : 0 : g_assert (start_of_tomorrow != NULL);
1335 : 0 : start_of_today_secs = g_date_time_to_unix (start_of_today);
1336 : 0 : start_of_tomorrow_secs = g_date_time_to_unix (start_of_tomorrow);
1337 : :
1338 : : /* Calculate the total time the user has spent on each session, app, etc.
1339 : : * since the start of today. */
1340 : 0 : total_times_so_far_today =
1341 : 0 : mct_timer_store_calculate_total_times_between (self->timer_store,
1342 : : transaction,
1343 : 0 : data->record_type,
1344 : : start_of_today_secs,
1345 : : now_secs);
1346 : :
1347 : : /* Close the file again. */
1348 : 0 : mct_timer_store_roll_back_transaction (self->timer_store, transaction);
1349 : :
1350 : : /* Get the estimated times. */
1351 [ # # # ]: 0 : switch (data->record_type)
1352 : : {
1353 : 0 : case MCT_TIMER_STORE_RECORD_TYPE_LOGIN_SESSION:
1354 : : {
1355 : : gboolean limit_reached;
1356 : 0 : uint64_t time_remaining_secs = 0;
1357 : 0 : gboolean time_limit_enabled = FALSE;
1358 : : uint64_t *total_session_time_so_far_today_ptr;
1359 : :
1360 : : /* Session record is always identified by "" */
1361 : 0 : total_session_time_so_far_today_ptr = g_hash_table_lookup (total_times_so_far_today, "");
1362 : :
1363 : 0 : limit_reached =
1364 [ # # ]: 0 : !mct_session_limits_check_time_remaining (data->session_limits_policy,
1365 : : now_date_time,
1366 : : (total_session_time_so_far_today_ptr != NULL) ? *total_session_time_so_far_today_ptr : 0,
1367 : : &time_remaining_secs,
1368 : : &time_limit_enabled);
1369 : :
1370 [ # # ]: 0 : if (time_limit_enabled)
1371 : : {
1372 : : uint64_t current_session_start_time_secs, current_session_estimated_end_time_secs;
1373 : : uint64_t next_session_start_time_secs, next_session_estimated_end_time_secs;
1374 : : gboolean daily_schedule_set, daily_limit_set;
1375 : : unsigned int start_time_secs, end_time_secs, daily_limit_secs;
1376 : :
1377 : 0 : daily_schedule_set = mct_session_limits_get_daily_schedule (data->session_limits_policy,
1378 : : &start_time_secs,
1379 : : &end_time_secs);
1380 : 0 : daily_limit_set = mct_session_limits_get_daily_limit (data->session_limits_policy,
1381 : : &daily_limit_secs);
1382 : :
1383 : : /* Calculate current session times */
1384 [ # # ]: 0 : if (!limit_reached)
1385 : : {
1386 : : /* FIXME: Calculate more of these times */
1387 : 0 : current_session_start_time_secs = 0;
1388 : 0 : current_session_estimated_end_time_secs = now_secs + time_remaining_secs;
1389 : : }
1390 : : else
1391 : : {
1392 : : /* if the limit has been reached, the ‘current session’ is
1393 : : * actually the most recent session */
1394 : 0 : current_session_start_time_secs = 0;
1395 : 0 : current_session_estimated_end_time_secs = now_secs; // TODO actually need the time the session ended
1396 : : }
1397 : :
1398 : : /* Calculate next session times */
1399 [ # # ]: 0 : if (daily_schedule_set)
1400 : : {
1401 [ # # ]: 0 : if (now_time_of_day_secs >= start_time_secs)
1402 : : {
1403 : 0 : next_session_start_time_secs = start_of_tomorrow_secs + start_time_secs;
1404 [ # # ]: 0 : next_session_estimated_end_time_secs = start_of_tomorrow_secs + (daily_limit_set ? MIN (end_time_secs, start_time_secs + daily_limit_secs) : end_time_secs);
1405 : : }
1406 : : else
1407 : : {
1408 : 0 : next_session_start_time_secs = start_of_today_secs + start_time_secs;
1409 [ # # ]: 0 : next_session_estimated_end_time_secs = start_of_today_secs + (daily_limit_set ? MIN (end_time_secs, start_time_secs + daily_limit_secs) : end_time_secs);
1410 : : }
1411 : : }
1412 [ # # ]: 0 : else if (daily_limit_set)
1413 : : {
1414 : 0 : next_session_start_time_secs = start_of_tomorrow_secs;
1415 : 0 : next_session_estimated_end_time_secs = start_of_tomorrow_secs + daily_limit_secs;
1416 : : }
1417 : : else
1418 : : {
1419 : : /* Unknown limit type */
1420 : 0 : next_session_start_time_secs = 0;
1421 : 0 : next_session_estimated_end_time_secs = 0;
1422 : : }
1423 : :
1424 : 0 : g_variant_builder_open (&builder, G_VARIANT_TYPE ("{s(btttt)}"));
1425 : 0 : g_variant_builder_add (&builder, "s", "");
1426 : 0 : g_variant_builder_add (&builder, "(btttt)",
1427 : : limit_reached,
1428 : : current_session_start_time_secs,
1429 : : current_session_estimated_end_time_secs,
1430 : : next_session_start_time_secs,
1431 : : next_session_estimated_end_time_secs);
1432 : 0 : g_variant_builder_close (&builder);
1433 : : }
1434 : 0 : break;
1435 : : }
1436 : 0 : case MCT_TIMER_STORE_RECORD_TYPE_APP:
1437 : : /* FIXME Not supported for now, as no support is in place in MctSessionLimits yet */
1438 : 0 : g_dbus_method_invocation_return_error (invocation, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
1439 : : "App time limits are not yet supported");
1440 : 0 : return;
1441 : 0 : default:
1442 : : g_assert_not_reached ();
1443 : : }
1444 : :
1445 : 0 : g_dbus_method_invocation_return_value (invocation,
1446 : : g_variant_new ("(t@a{s(btttt)})",
1447 : : now_secs,
1448 : : g_variant_builder_end (&builder)));
1449 : : }
1450 : :
1451 : : static void request_extension_ensure_credentials_cb (GObject *object,
1452 : : GAsyncResult *result,
1453 : : void *user_data);
1454 : : static void request_extension_agent_cb (GObject *object,
1455 : : GAsyncResult *result,
1456 : : void *user_data);
1457 : : static void request_extension_signal_cb (GDBusConnection *connection,
1458 : : const char *sender_name,
1459 : : const char *object_path,
1460 : : const char *interface_name,
1461 : : const char *signal_name,
1462 : : GVariant *parameters,
1463 : : void *user_data);
1464 : : static void request_extension_cancelled_cb (GCancellable *cancellable,
1465 : : void *user_data);
1466 : : static void request_extension_agent_name_lost_cb (GDBusConnection *connection,
1467 : : const char *name,
1468 : : void *user_data);
1469 : : static void request_extension_client_name_lost_cb (GDBusConnection *connection,
1470 : : const char *name,
1471 : : void *user_data);
1472 : :
1473 : : static void
1474 : 0 : mct_child_timer_service_request_extension (MctChildTimerService *self,
1475 : : GDBusConnection *connection,
1476 : : const char *sender,
1477 : : GVariant *parameters,
1478 : : GDBusMethodInvocation *invocation)
1479 : : {
1480 [ # # ]: 0 : g_autoptr(RequestExtensionData) data = NULL;
1481 [ # # ]: 0 : g_autofree char *record_type = NULL;
1482 [ # # ]: 0 : g_autofree char *identifier = NULL;
1483 : 0 : uint64_t duration_secs = 0;
1484 [ # # ]: 0 : g_autoptr(GVariant) extra_data = NULL;
1485 : :
1486 : 0 : g_variant_get (parameters, "(sst@a{sv})", &record_type, &identifier, &duration_secs, &extra_data);
1487 : :
1488 [ # # ]: 0 : if (!validate_record_type_and_identifier (invocation, record_type, identifier))
1489 : 0 : return;
1490 : :
1491 : : /* Load the peer’s credentials so we know which user is requesting an extension
1492 : : * (in particular, we know their session ID for the agent to use). */
1493 : 0 : data = g_new0 (RequestExtensionData, 1);
1494 : 0 : data->child_timer_service = g_object_ref (self);
1495 : 0 : data->invocation = g_object_ref (invocation);
1496 : 0 : data->record_type = g_steal_pointer (&record_type);
1497 : 0 : data->identifier = g_steal_pointer (&identifier);
1498 : 0 : data->duration_secs = duration_secs;
1499 : 0 : data->extra_data = g_variant_ref_sink (extra_data);
1500 : 0 : mct_operation_counter_init_and_hold (&data->operation_counter,
1501 : : &self->n_pending_operations,
1502 : 0 : G_OBJECT (self), props[PROP_BUSY]);
1503 : :
1504 : : /* Use a made up object path as a cookie for the caller. It’s opaque to them,
1505 : : * but could be used in future to expose a two-way request conversation to the
1506 : : * child, if needed. For now, it’s just an opaque and unique cookie. */
1507 : 0 : data->cookie_path = g_strdup_printf ("%s/ExtensionRequest%u", self->object_path, g_random_int ());
1508 : :
1509 : 0 : gss_peer_manager_ensure_peer_credentials_async (self->peer_manager,
1510 : : sender,
1511 : : self->cancellable,
1512 : : request_extension_ensure_credentials_cb,
1513 : : g_steal_pointer (&data));
1514 : : }
1515 : :
1516 : : static void
1517 : 0 : request_extension_ensure_credentials_cb (GObject *object,
1518 : : GAsyncResult *result,
1519 : : void *user_data)
1520 : : {
1521 : 0 : GssPeerManager *peer_manager = GSS_PEER_MANAGER (object);
1522 [ # # ]: 0 : g_autoptr(RequestExtensionData) data = g_steal_pointer (&user_data);
1523 : 0 : MctChildTimerService *self = data->child_timer_service;
1524 : 0 : GDBusMethodInvocation *invocation = data->invocation;
1525 : : const char *sender;
1526 : : int subject_pidfd;
1527 : : uid_t subject_uid;
1528 [ # # ]: 0 : g_autoptr(GUnixFDList) fd_list = NULL;
1529 : : int subject_pidfd_idx;
1530 [ # # ]: 0 : g_autoptr(GVariant) subject_details = NULL;
1531 [ # # ]: 0 : g_autoptr(GVariant) args = NULL;
1532 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
1533 : : GDBusCallFlags flags;
1534 : :
1535 : : /* Finish looking up the sender. */
1536 [ # # ]: 0 : if (!gss_peer_manager_ensure_peer_credentials_finish (peer_manager,
1537 : : result, &local_error))
1538 : : {
1539 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1540 : : MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
1541 : : _("Error identifying user: %s"),
1542 : 0 : local_error->message);
1543 : 0 : return;
1544 : : }
1545 : :
1546 : : /* Subscribe to signals from the agent before making the request, to avoid a
1547 : : * potential race. */
1548 : 0 : data->connection = g_object_ref (self->connection);
1549 : 0 : data->request_subscribe_id =
1550 : 0 : g_dbus_connection_signal_subscribe (self->connection,
1551 : : "org.freedesktop.MalcontentTimer1.ExtensionAgent",
1552 : : "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
1553 : : NULL, /* match all signal names */
1554 : : NULL, /* match all object paths */
1555 : : NULL, /* match all arg0 values */
1556 : : G_DBUS_SIGNAL_FLAGS_NONE,
1557 : : request_extension_signal_cb,
1558 : : data,
1559 : : NULL);
1560 : :
1561 : : /* Listen for cancellation too. */
1562 : 0 : data->cancelled_id = g_cancellable_connect (self->cancellable,
1563 : : G_CALLBACK (request_extension_cancelled_cb),
1564 : : data,
1565 : : NULL);
1566 : :
1567 : : /* And watch to see if the agent or client lose their name, in which case
1568 : : * they’ll have lost all state to do with our request. */
1569 : 0 : sender = g_dbus_method_invocation_get_sender (invocation);
1570 : :
1571 : 0 : data->agent_name_watch_id =
1572 : 0 : g_bus_watch_name_on_connection (self->connection,
1573 : : "org.freedesktop.MalcontentTimer1.ExtensionAgent",
1574 : : G_BUS_NAME_WATCHER_FLAGS_NONE,
1575 : : NULL,
1576 : : request_extension_agent_name_lost_cb,
1577 : : data,
1578 : : NULL);
1579 : 0 : data->client_name_watch_id =
1580 : 0 : g_bus_watch_name_on_connection (self->connection,
1581 : : sender,
1582 : : G_BUS_NAME_WATCHER_FLAGS_NONE,
1583 : : NULL,
1584 : : request_extension_client_name_lost_cb,
1585 : : data,
1586 : : NULL);
1587 : :
1588 : 0 : subject_pidfd = gss_peer_manager_get_peer_pidfd (peer_manager, sender);
1589 : 0 : subject_uid = gss_peer_manager_get_peer_uid (peer_manager, sender);
1590 : :
1591 [ # # # # ]: 0 : if (subject_pidfd < 0 || subject_uid == (uid_t) -1)
1592 : : {
1593 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1594 : : MCT_CHILD_TIMER_SERVICE_ERROR_IDENTIFYING_USER,
1595 : : _("Error identifying user: %s"),
1596 : 0 : _("Invalid or unknown user"));
1597 : 0 : return;
1598 : : }
1599 : :
1600 : : /* Call the extension agent.
1601 : : *
1602 : : * See https://www.freedesktop.org/software/polkit/docs/latest/eggdbus-interface-org.freedesktop.PolicyKit1.Authority.html#eggdbus-struct-Subject
1603 : : * for documentation on the `subject` argument. */
1604 : 0 : fd_list = g_unix_fd_list_new ();
1605 : 0 : subject_pidfd_idx = g_unix_fd_list_append (fd_list, subject_pidfd, &local_error);
1606 : :
1607 [ # # ]: 0 : if (subject_pidfd_idx < 0)
1608 : : {
1609 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1610 : : MCT_CHILD_TIMER_SERVICE_ERROR_COMMUNICATING_WITH_AGENT,
1611 : : _("Error communicating with agent: %s"),
1612 : 0 : local_error->message);
1613 : 0 : return;
1614 : : }
1615 : :
1616 : 0 : g_auto(GVariantBuilder) subject_details_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
1617 : 0 : g_variant_builder_add (&subject_details_builder, "{sv}", "pidfd", g_variant_new_handle (subject_pidfd_idx));
1618 : 0 : g_variant_builder_add (&subject_details_builder, "{sv}", "uid", g_variant_new_int32 (subject_uid));
1619 : 0 : subject_details = g_variant_builder_end (&subject_details_builder);
1620 : :
1621 : 0 : args = g_variant_new ("(sst(s@a{sv})@a{sv})",
1622 : 0 : data->record_type,
1623 : 0 : data->identifier,
1624 : 0 : data->duration_secs,
1625 : : "unix-process",
1626 : 0 : g_steal_pointer (&subject_details),
1627 : 0 : data->extra_data);
1628 : :
1629 : 0 : flags = G_DBUS_CALL_FLAGS_NONE;
1630 [ # # ]: 0 : if (g_dbus_message_get_flags (g_dbus_method_invocation_get_message (invocation)) & G_DBUS_MESSAGE_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION)
1631 : 0 : flags |= G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION;
1632 : :
1633 : 0 : g_dbus_connection_call_with_unix_fd_list (self->connection,
1634 : : "org.freedesktop.MalcontentTimer1.ExtensionAgent",
1635 : : "/org/freedesktop/MalcontentTimer1/ExtensionAgent",
1636 : : "org.freedesktop.MalcontentTimer1.ExtensionAgent",
1637 : : "RequestExtension",
1638 : 0 : g_steal_pointer (&args),
1639 : : G_VARIANT_TYPE ("(o)"),
1640 : : flags,
1641 : : -1, /* timeout (ms) */
1642 : : fd_list,
1643 : : self->cancellable,
1644 : : request_extension_agent_cb,
1645 : : g_steal_pointer (&data));
1646 : : }
1647 : :
1648 : : static void
1649 : 0 : request_extension_agent_cb (GObject *object,
1650 : : GAsyncResult *result,
1651 : : void *user_data)
1652 : : {
1653 : 0 : GDBusConnection *connection = G_DBUS_CONNECTION (object);
1654 [ # # ]: 0 : g_autoptr(RequestExtensionData) data = g_steal_pointer (&user_data);
1655 : 0 : MctChildTimerService *self = data->child_timer_service;
1656 : 0 : GDBusMethodInvocation *invocation = data->invocation;
1657 [ # # ]: 0 : g_autoptr(GVariant) reply = NULL;
1658 : : const char *request_object_path;
1659 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
1660 : :
1661 : 0 : reply = g_dbus_connection_call_finish (connection, result, &local_error);
1662 [ # # ]: 0 : if (reply == NULL)
1663 : : {
1664 : : /* We could parse the error code from the agent here, but it wouldn’t
1665 : : * actually change what we report to the client. */
1666 : 0 : g_dbus_method_invocation_return_error (invocation, MCT_CHILD_TIMER_SERVICE_ERROR,
1667 : : MCT_CHILD_TIMER_SERVICE_ERROR_COMMUNICATING_WITH_AGENT,
1668 : : _("Error communicating with agent: %s"),
1669 : 0 : local_error->message);
1670 : 0 : return;
1671 : : }
1672 : :
1673 : 0 : g_variant_get (reply, "(&o)", &request_object_path);
1674 : 0 : g_debug ("%s: got extension request object path %s", G_STRFUNC, request_object_path);
1675 : 0 : data->request_object_path = g_strdup (request_object_path);
1676 : :
1677 : : /* Note: We explicitly don’t return the `request_object_path` to the caller
1678 : : * (a process belonging to the child), because they cannot (and should not)
1679 : : * talk directly to the agent. Instead we return an arbitrary cookie they can
1680 : : * use to associate this method call with a subsequent `ExtensionResponse`
1681 : : * signal. */
1682 : 0 : g_dbus_method_invocation_return_value (invocation,
1683 : 0 : g_variant_new ("(o)", data->cookie_path));
1684 : :
1685 : : /* Continue to track the request until we receive the signal, or are cancelled. */
1686 : 0 : g_ptr_array_add (self->pending_extension_agent_requests, g_steal_pointer (&data));
1687 : : }
1688 : :
1689 : : static const char *
1690 : 0 : agent_error_name_to_client_error_name (const char *agent_error_name)
1691 : : {
1692 [ # # ]: 0 : if (g_str_equal (agent_error_name, "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.Cancelled"))
1693 : 0 : return "org.freedesktop.MalcontentTimer1.Child.Error.RequestCancelled";
1694 : : else
1695 : 0 : return "org.freedesktop.MalcontentTimer1.Child.Error.CommunicatingWithAgent";
1696 : : }
1697 : :
1698 : : static void
1699 : 0 : request_extension_send_reply (RequestExtensionData *data,
1700 : : gboolean granted,
1701 : : GVariant *extra_data)
1702 : : {
1703 : 0 : g_autoptr(GVariant) client_extra_data = NULL;
1704 : : const char *agent_error_name;
1705 : :
1706 : : /* @extra_data must be provided, even if it’s an empty dict */
1707 : 0 : g_assert (extra_data != NULL);
1708 : :
1709 : : /* Convert the @extra_data from the agent into something suitable for sending
1710 : : * to a client. */
1711 : 0 : g_auto(GVariantBuilder) client_extra_data_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{sv}"));
1712 [ # # ]: 0 : if (g_variant_lookup (extra_data, "error-name", "&s", &agent_error_name))
1713 : 0 : g_variant_builder_add (&client_extra_data_builder, "{sv}", "error-name",
1714 : 0 : g_variant_new_string (agent_error_name_to_client_error_name (agent_error_name)));
1715 : 0 : client_extra_data = g_variant_ref_sink (g_variant_builder_end (&client_extra_data_builder));
1716 : :
1717 : : /* Ignore errors from emitting the signal; it can only fail if the
1718 : : * parameters are invalid (not possible) or if the connection has been
1719 : : * closed. */
1720 : 0 : g_dbus_connection_emit_signal (data->connection,
1721 : : NULL, /* destination bus name */
1722 : : "/org/freedesktop/MalcontentTimer1",
1723 : : "org.freedesktop.MalcontentTimer1.Child",
1724 : : "ExtensionResponse",
1725 : : g_variant_new ("(bo@a{sv})",
1726 : : granted,
1727 : : data->cookie_path,
1728 : : client_extra_data),
1729 : : NULL);
1730 : :
1731 : : /* Mark the extension agent request as finished with. */
1732 [ # # ]: 0 : if (data->request_object_path != NULL)
1733 : : {
1734 : 0 : g_dbus_connection_call (data->connection,
1735 : : "org.freedesktop.MalcontentTimer1.ExtensionAgent",
1736 : 0 : data->request_object_path,
1737 : : "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
1738 : : "Close",
1739 : : NULL,
1740 : : NULL,
1741 : : G_DBUS_CALL_FLAGS_NONE,
1742 : : -1, /* timeout (ms) */
1743 : : NULL,
1744 : : NULL,
1745 : : NULL);
1746 : : }
1747 : 0 : }
1748 : :
1749 : : static void
1750 : 0 : request_extension_signal_cb (GDBusConnection *connection,
1751 : : const char *sender_name,
1752 : : const char *object_path,
1753 : : const char *interface_name,
1754 : : const char *signal_name,
1755 : : GVariant *parameters,
1756 : : void *user_data)
1757 : : {
1758 : 0 : RequestExtensionData *data = user_data;
1759 : 0 : MctChildTimerService *self = data->child_timer_service;
1760 : :
1761 : : /* These two are fixed by our signal subscription. The other arguments could
1762 : : * vary. */
1763 : : /* this will actually be a unique name, but effectively it is the following:
1764 : : * g_assert (g_strcmp0 (sender_name, "org.freedesktop.MalcontentTimer1.ExtensionAgent") == 0); */
1765 : 0 : g_assert (g_strcmp0 (interface_name, "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request") == 0);
1766 : :
1767 [ # # # # ]: 0 : if (data->request_object_path == NULL ||
1768 : 0 : g_strcmp0 (data->request_object_path, object_path) != 0)
1769 : : {
1770 : 0 : g_debug ("Ignoring signal with non-matching object path ‘%s’", object_path);
1771 : 0 : return;
1772 : : }
1773 : :
1774 [ # # ]: 0 : if (g_strcmp0 (signal_name, "Response") == 0)
1775 : : {
1776 : 0 : gboolean granted = FALSE;
1777 [ # # ]: 0 : g_autoptr(GVariant) extra_data = NULL;
1778 : :
1779 [ # # ]: 0 : if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ba{sv})")))
1780 : : {
1781 : 0 : g_debug ("Ignoring signal ‘%s.%s’ with incorrect type ‘%s’",
1782 : : interface_name, signal_name, g_variant_get_type_string (parameters));
1783 : 0 : return;
1784 : : }
1785 : :
1786 : 0 : g_variant_get (parameters, "(b@a{sv})", &granted, &extra_data);
1787 : :
1788 : : /* Send a reply to the child process. */
1789 : 0 : request_extension_send_reply (data, granted, extra_data);
1790 : :
1791 : : /* This will free @data, so don’t use it after then. */
1792 : 0 : g_ptr_array_remove_fast (self->pending_extension_agent_requests, data);
1793 : : }
1794 : : else
1795 : : {
1796 : 0 : g_debug ("Ignoring unknown signal ‘%s.%s’", interface_name, signal_name);
1797 : 0 : return;
1798 : : }
1799 : : }
1800 : :
1801 : : static void
1802 : 0 : request_extension_cancelled_cb (GCancellable *cancellable,
1803 : : void *user_data)
1804 : : {
1805 : 0 : RequestExtensionData *data = user_data;
1806 : 0 : MctChildTimerService *self = data->child_timer_service;
1807 : :
1808 : : /* Send cancellation to the child process. */
1809 : 0 : request_extension_send_reply (data, FALSE, g_variant_new_parsed ("@a{sv} {}"));
1810 : :
1811 : : /* This will free @data, so don’t use it after then. */
1812 : 0 : g_ptr_array_remove_fast (self->pending_extension_agent_requests, data);
1813 : 0 : }
1814 : :
1815 : : static void
1816 : 0 : request_extension_agent_name_lost_cb (GDBusConnection *connection,
1817 : : const char *name,
1818 : : void *user_data)
1819 : : {
1820 : 0 : RequestExtensionData *data = user_data;
1821 : 0 : MctChildTimerService *self = data->child_timer_service;
1822 : :
1823 : : /* This is fixed by our name watch handler. */
1824 : 0 : g_assert (g_strcmp0 (name, "org.freedesktop.MalcontentTimer1.ExtensionAgent") == 0);
1825 : :
1826 : : /* Don’t do anything if the agent hasn’t actually been contacted and returned
1827 : : * us a request object path yet. This can happen when we install the name
1828 : : * watch handler if the agent is not currently on the bus. */
1829 [ # # ]: 0 : if (data->request_object_path == NULL)
1830 : 0 : return;
1831 : :
1832 : : /* Send cancellation to the child process. */
1833 : 0 : request_extension_send_reply (data, FALSE, g_variant_new_parsed ("@a{sv} {}"));
1834 : :
1835 : : /* This will free @data, so don’t use it after then. */
1836 : 0 : g_ptr_array_remove_fast (self->pending_extension_agent_requests, data);
1837 : : }
1838 : :
1839 : : static void
1840 : 0 : request_extension_client_name_lost_cb (GDBusConnection *connection,
1841 : : const char *name,
1842 : : void *user_data)
1843 : : {
1844 : 0 : RequestExtensionData *data = user_data;
1845 : 0 : MctChildTimerService *self = data->child_timer_service;
1846 : :
1847 : : /* Don’t do anything if the agent hasn’t actually been contacted and returned
1848 : : * us a request object path yet. If there’s no request object we can’t close
1849 : : * it. */
1850 [ # # ]: 0 : if (data->request_object_path == NULL)
1851 : 0 : return;
1852 : :
1853 : : /* Send cancellation; even though the child process isn’t around to hear it,
1854 : : * we need to change state consistently. */
1855 : 0 : request_extension_send_reply (data, FALSE, g_variant_new_parsed ("@a{sv} {}"));
1856 : :
1857 : : /* This will free @data, so don’t use it after then. */
1858 : 0 : g_ptr_array_remove_fast (self->pending_extension_agent_requests, data);
1859 : : }
1860 : :
1861 : : /**
1862 : : * mct_child_timer_service_new:
1863 : : * @connection: (transfer none): D-Bus connection to export objects on
1864 : : * @object_path: root path to export objects below; must be a valid D-Bus object
1865 : : * path
1866 : : * @timer_store: (transfer none): store to use for timer data
1867 : : * @peer_manager: (transfer none): peer manager for querying D-Bus peers
1868 : : * @policy_manager: (transfer none): policy manager for querying user parental
1869 : : * controls policies
1870 : : *
1871 : : * Create a new [class@Malcontent.ChildTimerService] instance which is set up to run
1872 : : * as a service.
1873 : : *
1874 : : * Returns: (transfer full): a new [class@Malcontent.ChildTimerService]
1875 : : * Since: 0.14.0
1876 : : */
1877 : : MctChildTimerService *
1878 : 1 : mct_child_timer_service_new (GDBusConnection *connection,
1879 : : const char *object_path,
1880 : : MctTimerStore *timer_store,
1881 : : GssPeerManager *peer_manager,
1882 : : MctManager *policy_manager)
1883 : : {
1884 : 1 : g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
1885 : 1 : g_return_val_if_fail (g_variant_is_object_path (object_path), NULL);
1886 : 1 : g_return_val_if_fail (MCT_IS_TIMER_STORE (timer_store), NULL);
1887 : 1 : g_return_val_if_fail (GSS_IS_PEER_MANAGER (peer_manager), NULL);
1888 : 1 : g_return_val_if_fail (MCT_IS_MANAGER (policy_manager), NULL);
1889 : :
1890 : 1 : return g_object_new (MCT_TYPE_CHILD_TIMER_SERVICE,
1891 : : "connection", connection,
1892 : : "object-path", object_path,
1893 : : "timer-store", timer_store,
1894 : : "peer-manager", peer_manager,
1895 : : "policy-manager", policy_manager,
1896 : : NULL);
1897 : : }
1898 : :
1899 : : /**
1900 : : * mct_child_timer_service_get_busy:
1901 : : * @self: a child service
1902 : : *
1903 : : * Get the value of [property@Malcontent.ChildTimerService.busy].
1904 : : *
1905 : : * Returns: true if the service is busy, false otherwise
1906 : : * Since: 0.14.0
1907 : : */
1908 : : gboolean
1909 : 5 : mct_child_timer_service_get_busy (MctChildTimerService *self)
1910 : : {
1911 : 5 : g_return_val_if_fail (MCT_IS_CHILD_TIMER_SERVICE (self), FALSE);
1912 : :
1913 : : /* Note: We don’t have to include pending_extension_agent_requests in this
1914 : : * expression as RequestExtensionData holds an increment on
1915 : : * n_pending_operations. */
1916 [ + + - + ]: 5 : return (self->object_id != 0 && self->n_pending_operations > 0);
1917 : : }
1918 : :
|