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