Line | Branch | Exec | Source |
---|---|---|---|
1 | /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- | ||
2 | * | ||
3 | * Copyright © 2017 Endless Mobile, 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 <withnall@endlessm.com> | ||
23 | */ | ||
24 | |||
25 | #include "config.h" | ||
26 | |||
27 | #include <glib.h> | ||
28 | #include <glib-object.h> | ||
29 | #include <glib-unix.h> | ||
30 | #include <glib/gi18n-lib.h> | ||
31 | #include <gio/gio.h> | ||
32 | #include <libgsystemservice/service.h> | ||
33 | #include <locale.h> | ||
34 | #include <polkit/polkit.h> | ||
35 | #include <stdio.h> | ||
36 | #include <systemd/sd-daemon.h> | ||
37 | |||
38 | |||
39 | /** | ||
40 | * SECTION:service | ||
41 | * @short_description: System service base class | ||
42 | * @stability: Stable | ||
43 | * @include: libgsystemservice/service.h | ||
44 | * | ||
45 | * A skeleton implementation of a system service, which exposes itself on the | ||
46 | * bus with a well-known name. | ||
47 | * | ||
48 | * It follows the implementation recommendations in `man 7 daemon`. | ||
49 | * | ||
50 | * Since 0.2.0, it registers a D-Bus object at `/org/freedesktop/Debugging` | ||
51 | * which exposes controls for debugging the service. Requests to these controls | ||
52 | * from peers must be authorized. The default policy is to check against polkit | ||
53 | * using the action ID set as #GssService:debug-controller-action-id, if set. | ||
54 | * | ||
55 | * If #GssService:debug-controller-action-id is not set, requests to the debug | ||
56 | * object will be accepted unconditionally if the service is running on the | ||
57 | * D-Bus session bus, and rejected unconditionally if the service is running on | ||
58 | * the system bus. | ||
59 | * | ||
60 | * Since: 0.1.0 | ||
61 | */ | ||
62 | |||
63 | /* These require polkit 0.114 */ | ||
64 | #ifndef HAVE_POLKIT_114 | ||
65 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitAuthority, g_object_unref) | ||
66 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitSubject, g_object_unref) | ||
67 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitAuthorizationResult, g_object_unref) | ||
68 | #endif /* !HAVE_POLKIT_114 */ | ||
69 | |||
70 | /* These errors do not need to be registered with | ||
71 | * g_dbus_error_register_error_domain() as they never go over the bus. */ | ||
72 | ✗ | G_DEFINE_QUARK (GssServiceError, gss_service_error) | |
73 | |||
74 | /* A way of automatically removing bus names when going out of scope. */ | ||
75 | typedef guint BusNameId; | ||
76 | ✗ | G_DEFINE_AUTO_CLEANUP_FREE_FUNC (BusNameId, g_bus_unown_name, 0) | |
77 | |||
78 | static void gss_service_dispose (GObject *object); | ||
79 | static void gss_service_get_property (GObject *object, | ||
80 | guint property_id, | ||
81 | GValue *value, | ||
82 | GParamSpec *pspec); | ||
83 | static void gss_service_set_property (GObject *object, | ||
84 | guint property_id, | ||
85 | const GValue *value, | ||
86 | GParamSpec *pspec); | ||
87 | |||
88 | static void cancel_inactivity_timeout (GssService *self); | ||
89 | |||
90 | /** | ||
91 | * GssService: | ||
92 | * | ||
93 | * A skeleton implementation of a system service, which exposes itself on the | ||
94 | * bus with a well-known name. | ||
95 | * | ||
96 | * It follows the implementation recommendations in `man 7 daemon`. | ||
97 | * | ||
98 | * Since: 0.1.0 | ||
99 | */ | ||
100 | typedef struct | ||
101 | { | ||
102 | GPtrArray *option_groups; /* (owned) (element-type GOptionGroup) */ | ||
103 | gchar *translation_domain; /* (owned) */ | ||
104 | gchar *parameter_string; /* (owned) */ | ||
105 | gchar *summary; /* (owned) */ | ||
106 | GBusType bus_type; | ||
107 | gchar *service_id; /* (owned) */ | ||
108 | |||
109 | GCancellable *cancellable; /* (owned) */ | ||
110 | GDBusConnection *connection; /* (owned) */ | ||
111 | GError *run_error; /* (nullable) (owned) */ | ||
112 | gboolean run_exited; | ||
113 | int run_exit_signal; | ||
114 | gboolean allow_root; | ||
115 | |||
116 | GMainContext *context; /* (owned) */ | ||
117 | |||
118 | GSource *sigint_source; /* (owned) (nullable) */ | ||
119 | GSource *sigterm_source; /* (owned) (nullable) */ | ||
120 | |||
121 | guint inactivity_timeout_ms; /* 0 indicates no timeout */ | ||
122 | GSource *inactivity_timeout_source; /* (owned) (nullable) */ | ||
123 | guint hold_count; | ||
124 | |||
125 | GDebugController *debug_controller; /* (owned) (nullable) */ | ||
126 | gulong debug_controller_authorize_id; | ||
127 | gchar *debug_controller_action_id; /* (owned) (nullable) */ | ||
128 | } GssServicePrivate; | ||
129 | |||
130 | typedef enum | ||
131 | { | ||
132 | PROP_TRANSLATION_DOMAIN = 1, | ||
133 | PROP_PARAMETER_STRING, | ||
134 | PROP_SUMMARY, | ||
135 | PROP_BUS_TYPE, | ||
136 | PROP_SERVICE_ID, | ||
137 | PROP_INACTIVITY_TIMEOUT, | ||
138 | PROP_ALLOW_ROOT, | ||
139 | PROP_DEBUG_CONTROLLER_ACTION_ID, | ||
140 | PROP_DEBUG_CONTROLLER, | ||
141 | } GssServiceProperty; | ||
142 | |||
143 |
5/7✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 8 times.
|
42 | G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GssService, gss_service, G_TYPE_OBJECT) |
144 | |||
145 | static void | ||
146 | 1 | gss_service_class_init (GssServiceClass *klass) | |
147 | { | ||
148 | 1 | GObjectClass *object_class = (GObjectClass *) klass; | |
149 | 1 | GParamSpec *props[PROP_DEBUG_CONTROLLER + 1] = { NULL, }; | |
150 | |||
151 | 1 | object_class->dispose = gss_service_dispose; | |
152 | 1 | object_class->get_property = gss_service_get_property; | |
153 | 1 | object_class->set_property = gss_service_set_property; | |
154 | |||
155 | /** | ||
156 | * GssService:translation-domain: | ||
157 | * | ||
158 | * The gettext translation domain to use for translating command line help. | ||
159 | * This is typically `GETTEXT_PACKAGE`. | ||
160 | * | ||
161 | * Since: 0.1.0 | ||
162 | */ | ||
163 | 1 | props[PROP_TRANSLATION_DOMAIN] = | |
164 | 1 | g_param_spec_string ("translation-domain", "Translation Domain", | |
165 | "The gettext translation domain to use for " | ||
166 | "translating command line help.", | ||
167 | NULL, | ||
168 | G_PARAM_READWRITE | | ||
169 | G_PARAM_CONSTRUCT_ONLY | | ||
170 | G_PARAM_STATIC_STRINGS); | ||
171 | |||
172 | /** | ||
173 | * GssService:parameter-string: | ||
174 | * | ||
175 | * A string which is displayed on the first line of `--help` output, after the | ||
176 | * usage summary. It should be a sentence fragment which describes further | ||
177 | * parameters, or summarises the functionality of the program (after an | ||
178 | * em-dash). | ||
179 | * | ||
180 | * Since: 0.1.0 | ||
181 | */ | ||
182 | 1 | props[PROP_PARAMETER_STRING] = | |
183 | 1 | g_param_spec_string ("parameter-string", "Parameter String", | |
184 | "A string which is displayed on the first line of " | ||
185 | "--help output, after the usage summary.", | ||
186 | NULL, | ||
187 | G_PARAM_READWRITE | | ||
188 | G_PARAM_CONSTRUCT_ONLY | | ||
189 | G_PARAM_STATIC_STRINGS); | ||
190 | |||
191 | /** | ||
192 | * GssService:summary: | ||
193 | * | ||
194 | * Summary of the service to display as part of the command line help. This | ||
195 | * should be translated, and be one or more complete sentences. | ||
196 | * | ||
197 | * Since: 0.1.0 | ||
198 | */ | ||
199 | 1 | props[PROP_SUMMARY] = | |
200 | 1 | g_param_spec_string ("summary", "Summary", | |
201 | "Summary of the service to display as part of the " | ||
202 | "command line help.", | ||
203 | NULL, | ||
204 | G_PARAM_READWRITE | | ||
205 | G_PARAM_CONSTRUCT_ONLY | | ||
206 | G_PARAM_STATIC_STRINGS); | ||
207 | |||
208 | /** | ||
209 | * GssService:bus-type: | ||
210 | * | ||
211 | * The type of bus which the service’s well-known name should be exposed on. | ||
212 | * This can be overridden on the command line. | ||
213 | * | ||
214 | * Since: 0.1.0 | ||
215 | */ | ||
216 | 1 | props[PROP_BUS_TYPE] = | |
217 | 1 | g_param_spec_enum ("bus-type", "Bus Type", | |
218 | "The type of bus which the service’s well-known name " | ||
219 | "should be exposed on.", | ||
220 | G_TYPE_BUS_TYPE, | ||
221 | G_BUS_TYPE_SYSTEM, | ||
222 | G_PARAM_READWRITE | | ||
223 | G_PARAM_CONSTRUCT_ONLY | | ||
224 | G_PARAM_STATIC_STRINGS); | ||
225 | |||
226 | /** | ||
227 | * GssService:service-id: | ||
228 | * | ||
229 | * The ID of the service, which must be a well-known D-Bus name to uniquely | ||
230 | * identify the service. | ||
231 | * | ||
232 | * Since: 0.1.0 | ||
233 | */ | ||
234 | 1 | props[PROP_SERVICE_ID] = | |
235 | 1 | g_param_spec_string ("service-id", "Service ID", | |
236 | "The ID of the service, which must be a well-known " | ||
237 | "D-Bus name to uniquely identify the service.", | ||
238 | NULL, | ||
239 | G_PARAM_READWRITE | | ||
240 | G_PARAM_CONSTRUCT_ONLY | | ||
241 | G_PARAM_STATIC_STRINGS); | ||
242 | |||
243 | /** | ||
244 | * GssService:inactivity-timeout: | ||
245 | * | ||
246 | * An inactivity timeout (in ms), after which the service will automatically | ||
247 | * exit unless its hold count is greater than zero. Increase/Decrease the hold | ||
248 | * count by calling gss_service_hold()/gss_service_release(). | ||
249 | * | ||
250 | * A timeout of zero means the service will never automatically exit. | ||
251 | * | ||
252 | * Since: 0.1.0 | ||
253 | */ | ||
254 | 1 | props[PROP_INACTIVITY_TIMEOUT] = | |
255 | 1 | g_param_spec_uint ("inactivity-timeout", "Inactivity Timeout", | |
256 | "An inactivity timeout (in ms), after which the " | ||
257 | "service will automatically exit.", | ||
258 | 0, G_MAXUINT, 0, | ||
259 | G_PARAM_READWRITE | | ||
260 | G_PARAM_STATIC_STRINGS); | ||
261 | |||
262 | /** | ||
263 | * GssService:allow-root: | ||
264 | * | ||
265 | * If %TRUE, the service can be run by root. Otherwise, and by default, | ||
266 | * gss_service_run() will fail if the UID or effective UID is zero. | ||
267 | * | ||
268 | * Since: 0.1.0 | ||
269 | */ | ||
270 | 1 | props[PROP_ALLOW_ROOT] = | |
271 | 1 | g_param_spec_boolean ("allow-root", "Allow Root", | |
272 | "Whether the service should be allowed to run " | ||
273 | "as root (UID 0).", | ||
274 | FALSE, | ||
275 | G_PARAM_READWRITE | | ||
276 | G_PARAM_CONSTRUCT_ONLY | | ||
277 | G_PARAM_STATIC_STRINGS); | ||
278 | |||
279 | /** | ||
280 | * GssService:debug-controller-action-id: (nullable) | ||
281 | * | ||
282 | * The ID of a polkit action to check for authorization when a peer requests | ||
283 | * to change the debug settings. | ||
284 | * | ||
285 | * polkit will be queried with this action ID and the peer as the subject. | ||
286 | * | ||
287 | * If this is %NULL, the default security policy will be applied, as | ||
288 | * documented in #GssService. | ||
289 | * | ||
290 | * Since: 0.2.0 | ||
291 | */ | ||
292 | 1 | props[PROP_DEBUG_CONTROLLER_ACTION_ID] = | |
293 | 1 | g_param_spec_string ("debug-controller-action-id", | |
294 | "Debug Controller Polkit Action ID", | ||
295 | "The ID of a polkit action to check for " | ||
296 | "authorization when a peer requests to change the " | ||
297 | "debug settings.", | ||
298 | NULL, | ||
299 | G_PARAM_READWRITE | | ||
300 | G_PARAM_CONSTRUCT_ONLY | | ||
301 | G_PARAM_STATIC_STRINGS); | ||
302 | |||
303 | /** | ||
304 | * GssService:debug-controller: (nullable) | ||
305 | * | ||
306 | * The debug controller used for the service. This exposes information about | ||
307 | * debug settings, such as whether debug output is enabled. | ||
308 | * | ||
309 | * Since: 0.2.0 | ||
310 | */ | ||
311 | 1 | props[PROP_DEBUG_CONTROLLER] = | |
312 | 1 | g_param_spec_object ("debug-controller", | |
313 | "Debug Controller", | ||
314 | "The debug controller used for the service.", | ||
315 | G_TYPE_DEBUG_CONTROLLER, | ||
316 | G_PARAM_READABLE | | ||
317 | G_PARAM_STATIC_STRINGS); | ||
318 | |||
319 | 1 | g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props); | |
320 | 1 | } | |
321 | |||
322 | static void | ||
323 | 1 | gss_service_init (GssService *self) | |
324 | { | ||
325 | 1 | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
326 | |||
327 | 1 | priv->cancellable = g_cancellable_new (); | |
328 | 1 | priv->context = g_main_context_ref_thread_default (); | |
329 | 1 | } | |
330 | |||
331 | static void | ||
332 | ✗ | source_destroy_and_unref (GSource *source) | |
333 | { | ||
334 | ✗ | if (source != NULL) | |
335 | { | ||
336 | ✗ | g_source_destroy (source); | |
337 | ✗ | g_source_unref (source); | |
338 | } | ||
339 | ✗ | } | |
340 | |||
341 | static void | ||
342 | 1 | gss_service_dispose (GObject *object) | |
343 | { | ||
344 | 1 | GssService *self = GSS_SERVICE (object); | |
345 | 1 | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
346 | |||
347 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_clear_pointer (&priv->sigint_source, source_destroy_and_unref); |
348 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_clear_pointer (&priv->sigterm_source, source_destroy_and_unref); |
349 | |||
350 | 1 | cancel_inactivity_timeout (self); | |
351 | |||
352 | 1 | g_cancellable_cancel (priv->cancellable); | |
353 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | g_clear_object (&priv->cancellable); |
354 | |||
355 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_clear_pointer (&priv->option_groups, g_ptr_array_unref); |
356 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | g_clear_pointer (&priv->translation_domain, g_free); |
357 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | g_clear_pointer (&priv->parameter_string, g_free); |
358 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_clear_pointer (&priv->summary, g_free); |
359 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | g_clear_pointer (&priv->service_id, g_free); |
360 | 1 | g_clear_error (&priv->run_error); | |
361 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_clear_signal_handler (&priv->debug_controller_authorize_id, priv->debug_controller); |
362 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_clear_object (&priv->debug_controller); |
363 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_clear_pointer (&priv->debug_controller_action_id, g_free); |
364 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_clear_object (&priv->connection); |
365 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | g_clear_pointer (&priv->context, g_main_context_unref); |
366 | |||
367 | /* Chain up to the parent class */ | ||
368 | 1 | G_OBJECT_CLASS (gss_service_parent_class)->dispose (object); | |
369 | 1 | } | |
370 | |||
371 | static void | ||
372 | ✗ | gss_service_get_property (GObject *object, | |
373 | guint property_id, | ||
374 | GValue *value, | ||
375 | GParamSpec *pspec) | ||
376 | { | ||
377 | ✗ | GssService *self = GSS_SERVICE (object); | |
378 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
379 | |||
380 | ✗ | switch ((GssServiceProperty) property_id) | |
381 | { | ||
382 | ✗ | case PROP_TRANSLATION_DOMAIN: | |
383 | ✗ | g_value_set_string (value, priv->translation_domain); | |
384 | ✗ | break; | |
385 | ✗ | case PROP_PARAMETER_STRING: | |
386 | ✗ | g_value_set_string (value, priv->parameter_string); | |
387 | ✗ | break; | |
388 | ✗ | case PROP_SUMMARY: | |
389 | ✗ | g_value_set_string (value, priv->summary); | |
390 | ✗ | break; | |
391 | ✗ | case PROP_BUS_TYPE: | |
392 | ✗ | g_value_set_enum (value, priv->bus_type); | |
393 | ✗ | break; | |
394 | ✗ | case PROP_SERVICE_ID: | |
395 | ✗ | g_value_set_string (value, priv->service_id); | |
396 | ✗ | break; | |
397 | ✗ | case PROP_INACTIVITY_TIMEOUT: | |
398 | ✗ | g_value_set_uint (value, priv->inactivity_timeout_ms); | |
399 | ✗ | break; | |
400 | ✗ | case PROP_ALLOW_ROOT: | |
401 | ✗ | g_value_set_boolean (value, priv->allow_root); | |
402 | ✗ | break; | |
403 | ✗ | case PROP_DEBUG_CONTROLLER_ACTION_ID: | |
404 | ✗ | g_value_set_string (value, priv->debug_controller_action_id); | |
405 | ✗ | break; | |
406 | ✗ | case PROP_DEBUG_CONTROLLER: | |
407 | ✗ | g_value_set_object (value, priv->debug_controller); | |
408 | ✗ | break; | |
409 | ✗ | default: | |
410 | ✗ | g_assert_not_reached (); | |
411 | } | ||
412 | ✗ | } | |
413 | |||
414 | static void | ||
415 | 7 | gss_service_set_property (GObject *object, | |
416 | guint property_id, | ||
417 | const GValue *value, | ||
418 | GParamSpec *pspec) | ||
419 | { | ||
420 | 7 | GssService *self = GSS_SERVICE (object); | |
421 | 7 | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
422 | |||
423 |
7/9✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
|
7 | switch ((GssServiceProperty) property_id) |
424 | { | ||
425 | 1 | case PROP_TRANSLATION_DOMAIN: | |
426 | /* Construct only. */ | ||
427 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_assert (priv->translation_domain == NULL); |
428 | 1 | priv->translation_domain = g_value_dup_string (value); | |
429 | 1 | break; | |
430 | 1 | case PROP_PARAMETER_STRING: | |
431 | /* Construct only. */ | ||
432 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_assert (priv->parameter_string == NULL); |
433 | 1 | priv->parameter_string = g_value_dup_string (value); | |
434 | 1 | break; | |
435 | 1 | case PROP_SUMMARY: | |
436 | /* Construct only. */ | ||
437 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_assert (priv->summary == NULL); |
438 | 1 | priv->summary = g_value_dup_string (value); | |
439 | 1 | break; | |
440 | 1 | case PROP_BUS_TYPE: | |
441 | /* Construct only. */ | ||
442 | 1 | priv->bus_type = g_value_get_enum (value); | |
443 | 1 | break; | |
444 | 1 | case PROP_SERVICE_ID: | |
445 | /* Construct only. */ | ||
446 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_assert (priv->service_id == NULL); |
447 | 1 | priv->service_id = g_value_dup_string (value); | |
448 | 1 | break; | |
449 | ✗ | case PROP_INACTIVITY_TIMEOUT: | |
450 | ✗ | gss_service_set_inactivity_timeout (self, g_value_get_uint (value)); | |
451 | ✗ | break; | |
452 | 1 | case PROP_ALLOW_ROOT: | |
453 | /* Construct only. */ | ||
454 | 1 | priv->allow_root = g_value_get_boolean (value); | |
455 | 1 | break; | |
456 | 1 | case PROP_DEBUG_CONTROLLER_ACTION_ID: | |
457 | /* Construct only. */ | ||
458 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_assert (priv->debug_controller_action_id == NULL); |
459 | 1 | priv->debug_controller_action_id = g_value_dup_string (value); | |
460 | 1 | break; | |
461 | ✗ | case PROP_DEBUG_CONTROLLER: | |
462 | /* Read only */ | ||
463 | G_GNUC_FALLTHROUGH; | ||
464 | default: | ||
465 | ✗ | g_assert_not_reached (); | |
466 | } | ||
467 | 7 | } | |
468 | |||
469 | /** | ||
470 | * gss_service_add_option_group: | ||
471 | * @self: a #GssService | ||
472 | * @group: (transfer none): an option group to add | ||
473 | * | ||
474 | * Add an option group to the command line options. The options in this group | ||
475 | * will be listed in the help output, and their values will be set when | ||
476 | * gss_service_run() is called. | ||
477 | * | ||
478 | * This is effectively a wrapper around g_option_context_add_group(), so see the | ||
479 | * documentation for that for more information. | ||
480 | * | ||
481 | * Since: 0.1.0 | ||
482 | */ | ||
483 | void | ||
484 | ✗ | gss_service_add_option_group (GssService *self, | |
485 | GOptionGroup *group) | ||
486 | { | ||
487 | ✗ | g_return_if_fail (GSS_IS_SERVICE (self)); | |
488 | ✗ | g_return_if_fail (group != NULL); | |
489 | |||
490 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
491 | |||
492 | ✗ | if (priv->option_groups == NULL) | |
493 | ✗ | priv->option_groups = g_ptr_array_new_with_free_func ((GDestroyNotify) g_option_group_unref); | |
494 | |||
495 | ✗ | g_ptr_array_add (priv->option_groups, g_option_group_ref (group)); | |
496 | } | ||
497 | |||
498 | static gboolean | ||
499 | ✗ | signal_sigint_cb (gpointer user_data) | |
500 | { | ||
501 | ✗ | GssService *self = GSS_SERVICE (user_data); | |
502 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
503 | |||
504 | ✗ | gss_service_exit (self, NULL, SIGINT); | |
505 | |||
506 | /* Remove the signal handler so we can re-raise it later without entering a | ||
507 | * loop. */ | ||
508 | ✗ | g_clear_pointer (&priv->sigint_source, source_destroy_and_unref); | |
509 | ✗ | return G_SOURCE_REMOVE; | |
510 | } | ||
511 | |||
512 | static gboolean | ||
513 | ✗ | signal_sigterm_cb (gpointer user_data) | |
514 | { | ||
515 | ✗ | GssService *self = GSS_SERVICE (user_data); | |
516 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
517 | |||
518 | ✗ | gss_service_exit (self, NULL, SIGTERM); | |
519 | |||
520 | /* Remove the signal handler so we can re-raise it later without entering a | ||
521 | * loop. */ | ||
522 | ✗ | g_clear_pointer (&priv->sigterm_source, source_destroy_and_unref); | |
523 | ✗ | return G_SOURCE_REMOVE; | |
524 | } | ||
525 | |||
526 | static void | ||
527 | ✗ | name_acquired_cb (GDBusConnection *connection, | |
528 | const gchar *name, | ||
529 | gpointer user_data) | ||
530 | { | ||
531 | ✗ | GssService *self = GSS_SERVICE (user_data); | |
532 | |||
533 | /* Notify systemd we’re ready. */ | ||
534 | ✗ | sd_notify (0, "READY=1"); | |
535 | |||
536 | /* Potentially start a timeout to exiting due to inactivity. */ | ||
537 | ✗ | gss_service_release (self); | |
538 | ✗ | } | |
539 | |||
540 | static void | ||
541 | ✗ | name_lost_cb (GDBusConnection *connection, | |
542 | const gchar *name, | ||
543 | gpointer user_data) | ||
544 | { | ||
545 | ✗ | GssService *self = GSS_SERVICE (user_data); | |
546 | ✗ | g_autoptr(GError) error = NULL; | |
547 | |||
548 | ✗ | gss_service_release (self); | |
549 | |||
550 | ✗ | g_set_error (&error, GSS_SERVICE_ERROR, GSS_SERVICE_ERROR_NAME_UNAVAILABLE, | |
551 | _("Lost D-Bus name ‘%s’; exiting."), name); | ||
552 | ✗ | gss_service_exit (self, error, 0); | |
553 | ✗ | } | |
554 | |||
555 | static gboolean | ||
556 | ✗ | inactivity_timeout_cb (gpointer user_data) | |
557 | { | ||
558 | ✗ | GssService *self = GSS_SERVICE (user_data); | |
559 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
560 | |||
561 | ✗ | if (priv->hold_count == 0) | |
562 | { | ||
563 | ✗ | g_autoptr(GError) local_error = NULL; | |
564 | ✗ | g_set_error_literal (&local_error, GSS_SERVICE_ERROR, GSS_SERVICE_ERROR_TIMEOUT, | |
565 | _("Inactivity timeout reached; exiting.")); | ||
566 | ✗ | gss_service_exit (self, local_error, 0); | |
567 | } | ||
568 | |||
569 | ✗ | return G_SOURCE_REMOVE; | |
570 | } | ||
571 | |||
572 | static void | ||
573 | 1 | cancel_inactivity_timeout (GssService *self) | |
574 | { | ||
575 | 1 | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
576 | |||
577 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_debug ("%s: Cancelling inactivity timeout (was %s)", |
578 | G_STRFUNC, (priv->inactivity_timeout_source != NULL) ? "set" : "unset"); | ||
579 | |||
580 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | g_clear_pointer (&priv->inactivity_timeout_source, |
581 | source_destroy_and_unref); | ||
582 | 1 | } | |
583 | |||
584 | static void | ||
585 | ✗ | maybe_schedule_inactivity_timeout (GssService *self) | |
586 | { | ||
587 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
588 | |||
589 | ✗ | g_debug ("%s: Maybe scheduling inactivity timeout, hold_count: %u, inactivity_timeout_ms: %u", | |
590 | G_STRFUNC, priv->hold_count, priv->inactivity_timeout_ms); | ||
591 | |||
592 | ✗ | if (priv->hold_count == 0) | |
593 | { | ||
594 | ✗ | cancel_inactivity_timeout (self); | |
595 | |||
596 | ✗ | if (priv->inactivity_timeout_ms != 0) | |
597 | { | ||
598 | ✗ | g_debug ("%s: Scheduling inactivity timeout", G_STRFUNC); | |
599 | |||
600 | ✗ | if ((priv->inactivity_timeout_ms % 1000) == 0) | |
601 | ✗ | priv->inactivity_timeout_source = g_timeout_source_new_seconds (priv->inactivity_timeout_ms / 1000); | |
602 | else | ||
603 | ✗ | priv->inactivity_timeout_source = g_timeout_source_new (priv->inactivity_timeout_ms); | |
604 | ✗ | g_source_set_callback (priv->inactivity_timeout_source, inactivity_timeout_cb, self, NULL); | |
605 | ✗ | g_source_attach (priv->inactivity_timeout_source, priv->context); | |
606 | } | ||
607 | } | ||
608 | ✗ | } | |
609 | |||
610 | static void | ||
611 | ✗ | result_cb (GObject *obj, | |
612 | GAsyncResult *result, | ||
613 | gpointer user_data) | ||
614 | { | ||
615 | ✗ | GAsyncResult **result_out = user_data; | |
616 | |||
617 | ✗ | *result_out = g_object_ref (result); | |
618 | ✗ | } | |
619 | |||
620 | static gboolean | ||
621 | ✗ | check_for_early_exit (GssService *self, | |
622 | GError **error) | ||
623 | { | ||
624 | ✗ | g_return_val_if_fail (GSS_IS_SERVICE (self), FALSE); | |
625 | |||
626 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
627 | |||
628 | ✗ | if (priv->run_exited) | |
629 | { | ||
630 | ✗ | if (priv->run_error != NULL) | |
631 | ✗ | g_propagate_error (error, g_steal_pointer (&priv->run_error)); | |
632 | |||
633 | ✗ | gss_service_release (self); | |
634 | ✗ | return FALSE; | |
635 | } | ||
636 | |||
637 | ✗ | return TRUE; | |
638 | } | ||
639 | |||
640 | static gboolean | ||
641 | ✗ | debug_controller_authorize_cb (GDebugControllerDBus *debug_controller, | |
642 | GDBusMethodInvocation *invocation, | ||
643 | gpointer user_data) | ||
644 | { | ||
645 | ✗ | GssService *self = GSS_SERVICE (user_data); | |
646 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
647 | |||
648 | /* Implement a security policy for allowing debug output to be enabled at | ||
649 | * runtime. | ||
650 | * | ||
651 | * If #GssService:debug-controller-action-id is set, polkit will be queried | ||
652 | * using that action ID, and its response will determine whether debug output | ||
653 | * can be enabled. | ||
654 | * | ||
655 | * Otherwise, if #GssService:bus-type is %G_BUS_TYPE_SESSION, enabling debug | ||
656 | * output will be allowed without authorization. | ||
657 | * | ||
658 | * Otherwise, enabling debug output will be denied. | ||
659 | * | ||
660 | * If services require more control over the security policy, #GssService will | ||
661 | * have to be extended to allow the service to provide their own | ||
662 | * #GDebugController, which they have hooked up to an appropriate security | ||
663 | * policy. | ||
664 | */ | ||
665 | ✗ | if (priv->debug_controller_action_id != NULL) | |
666 | { | ||
667 | ✗ | g_autoptr(PolkitAuthority) authority = NULL; | |
668 | ✗ | g_autoptr(PolkitSubject) subject = NULL; | |
669 | ✗ | g_autoptr(PolkitAuthorizationResult) auth_result = NULL; | |
670 | ✗ | g_autoptr(GError) local_error = NULL; | |
671 | GDBusMessage *message; | ||
672 | GDBusMessageFlags message_flags; | ||
673 | ✗ | PolkitCheckAuthorizationFlags flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE; | |
674 | |||
675 | ✗ | message = g_dbus_method_invocation_get_message (invocation); | |
676 | ✗ | message_flags = g_dbus_message_get_flags (message); | |
677 | |||
678 | ✗ | authority = polkit_authority_get_sync (NULL, &local_error); | |
679 | ✗ | if (authority == NULL) | |
680 | { | ||
681 | ✗ | g_warning ("Failed to get polkit authority: %s", local_error->message); | |
682 | ✗ | return FALSE; | |
683 | } | ||
684 | |||
685 | ✗ | if (message_flags & G_DBUS_MESSAGE_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION) | |
686 | ✗ | flags |= POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; | |
687 | |||
688 | ✗ | subject = polkit_system_bus_name_new (g_dbus_method_invocation_get_sender (invocation)); | |
689 | |||
690 | ✗ | auth_result = polkit_authority_check_authorization_sync (authority, | |
691 | subject, | ||
692 | ✗ | priv->debug_controller_action_id, | |
693 | NULL, | ||
694 | flags, | ||
695 | NULL, | ||
696 | &local_error); | ||
697 | ✗ | if (auth_result == NULL) | |
698 | { | ||
699 | ✗ | g_warning ("Failed to get check polkit authorization: %s", local_error->message); | |
700 | ✗ | return FALSE; | |
701 | } | ||
702 | |||
703 | ✗ | return polkit_authorization_result_get_is_authorized (auth_result); | |
704 | } | ||
705 | ✗ | else if (priv->bus_type == G_BUS_TYPE_SESSION) | |
706 | { | ||
707 | /* All peers on the session bus are at the same privilege level anyway. */ | ||
708 | ✗ | return TRUE; | |
709 | } | ||
710 | else | ||
711 | { | ||
712 | /* Peers on the system bus (or other buses) may have different privilege | ||
713 | * levels, so can’t be trusted to unconditionally enable debug mode on | ||
714 | * services. */ | ||
715 | ✗ | return FALSE; | |
716 | } | ||
717 | } | ||
718 | |||
719 | /** | ||
720 | * gss_service_run: | ||
721 | * @self: a #GssService | ||
722 | * @argc: number of arguments in @argv | ||
723 | * @argv: (array length=argc): argument array | ||
724 | * @error: return location for a #GError | ||
725 | * | ||
726 | * Run the service, and return when the process should exit. If it should exit | ||
727 | * with an error status, @error is set; otherwise it should exit with exit code | ||
728 | * zero (success). | ||
729 | * | ||
730 | * This handles UNIX signals and command line parsing. If you wish to schedule | ||
731 | * some work to happen asynchronously while gss_service_run() is running in your | ||
732 | * `main()` function, use g_idle_add(). This function, like the rest of the | ||
733 | * library, is not thread-safe. | ||
734 | * | ||
735 | * Since: 0.1.0 | ||
736 | */ | ||
737 | void | ||
738 | ✗ | gss_service_run (GssService *self, | |
739 | int argc, | ||
740 | char **argv, | ||
741 | GError **error) | ||
742 | { | ||
743 | ✗ | g_return_if_fail (GSS_IS_SERVICE (self)); | |
744 | ✗ | g_return_if_fail (argc > 0); | |
745 | ✗ | g_return_if_fail (argv != NULL); | |
746 | |||
747 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
748 | ✗ | GssServiceClass *service_class = GSS_SERVICE_GET_CLASS (self); | |
749 | |||
750 | /* Command line parameters. */ | ||
751 | ✗ | g_autofree gchar *bus_address = NULL; | |
752 | ✗ | gint64 inactivity_timeout_ms = priv->inactivity_timeout_ms; | |
753 | |||
754 | ✗ | const GOptionEntry entries[] = | |
755 | { | ||
756 | { "bus-address", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &bus_address, | ||
757 | N_("Address of the D-Bus daemon to connect to and own a name on"), | ||
758 | N_("ADDRESS") }, | ||
759 | { "inactivity-timeout", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT64, &inactivity_timeout_ms, | ||
760 | N_("Inactivity timeout to wait for before exiting (in milliseconds)"), | ||
761 | N_("MS") }, | ||
762 | { NULL, }, | ||
763 | }; | ||
764 | |||
765 | /* Localisation */ | ||
766 | ✗ | setlocale (LC_ALL, ""); | |
767 | ✗ | bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); | |
768 | ✗ | bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); | |
769 | ✗ | textdomain (GETTEXT_PACKAGE); | |
770 | |||
771 | ✗ | if (!priv->allow_root) | |
772 | { | ||
773 | /* Ensure we are not running as root — we don’t need those privileges. */ | ||
774 | ✗ | if (getuid () == 0 || geteuid () == 0) | |
775 | { | ||
776 | ✗ | g_set_error_literal (error, GSS_SERVICE_ERROR, | |
777 | GSS_SERVICE_ERROR_INVALID_ENVIRONMENT, | ||
778 | _("This daemon must not be run as root.")); | ||
779 | ✗ | return; | |
780 | } | ||
781 | } | ||
782 | |||
783 | ✗ | gss_service_hold (self); | |
784 | |||
785 | /* Set up signal handlers. */ | ||
786 | ✗ | priv->sigint_source = g_unix_signal_source_new (SIGINT); | |
787 | ✗ | g_source_set_callback (priv->sigint_source, signal_sigint_cb, self, NULL); | |
788 | ✗ | g_source_attach (priv->sigint_source, priv->context); | |
789 | ✗ | priv->sigterm_source = g_unix_signal_source_new (SIGTERM); | |
790 | ✗ | g_source_set_callback (priv->sigterm_source, signal_sigterm_cb, self, NULL); | |
791 | ✗ | g_source_attach (priv->sigterm_source, priv->context); | |
792 | |||
793 | /* Handle command line parameters. */ | ||
794 | ✗ | g_autoptr(GOptionContext) context = g_option_context_new (priv->parameter_string); | |
795 | ✗ | g_option_context_set_summary (context, priv->summary); | |
796 | ✗ | g_option_context_add_main_entries (context, entries, | |
797 | ✗ | priv->translation_domain); | |
798 | |||
799 | ✗ | if (service_class->get_main_option_entries != NULL) | |
800 | { | ||
801 | GOptionGroup *main_group; | ||
802 | ✗ | g_autofree GOptionEntry *main_entries = NULL; | |
803 | |||
804 | ✗ | main_group = g_option_context_get_main_group (context); | |
805 | ✗ | main_entries = service_class->get_main_option_entries (self); | |
806 | ✗ | g_option_group_add_entries (main_group, main_entries); | |
807 | } | ||
808 | |||
809 | ✗ | for (guint i = 0; priv->option_groups != NULL && i < priv->option_groups->len; i++) | |
810 | ✗ | g_option_context_add_group (context, priv->option_groups->pdata[i]); | |
811 | |||
812 | ✗ | if (priv->option_groups != NULL) | |
813 | { | ||
814 | ✗ | g_ptr_array_set_free_func (priv->option_groups, NULL); | |
815 | ✗ | g_ptr_array_set_size (priv->option_groups, 0); | |
816 | } | ||
817 | |||
818 | ✗ | g_autoptr(GError) child_error = NULL; | |
819 | |||
820 | ✗ | if (!g_option_context_parse (context, &argc, &argv, &child_error)) | |
821 | { | ||
822 | ✗ | g_set_error (error, GSS_SERVICE_ERROR, GSS_SERVICE_ERROR_INVALID_OPTIONS, | |
823 | ✗ | _("Option parsing failed: %s"), child_error->message); | |
824 | ✗ | gss_service_release (self); | |
825 | ✗ | return; | |
826 | } | ||
827 | |||
828 | /* Sort out the inactivity timeout. Zero is the default, so ignore that so | ||
829 | * that subclasses can set their own defaults at construction time. */ | ||
830 | ✗ | if (inactivity_timeout_ms < 0 || inactivity_timeout_ms > G_MAXUINT) | |
831 | { | ||
832 | ✗ | g_autofree gchar *inactivity_timeout_ms_str = NULL; | |
833 | ✗ | inactivity_timeout_ms_str = g_strdup_printf ("%" G_GINT64_FORMAT, inactivity_timeout_ms); | |
834 | |||
835 | ✗ | g_set_error (error, GSS_SERVICE_ERROR, GSS_SERVICE_ERROR_INVALID_OPTIONS, | |
836 | _("Invalid inactivity timeout %sms."), | ||
837 | inactivity_timeout_ms_str); | ||
838 | ✗ | gss_service_release (self); | |
839 | ✗ | return; | |
840 | } | ||
841 | ✗ | else if (inactivity_timeout_ms >= 0) | |
842 | { | ||
843 | ✗ | gss_service_set_inactivity_timeout (self, inactivity_timeout_ms); | |
844 | } | ||
845 | |||
846 | /* Connect to the bus. */ | ||
847 | ✗ | if (bus_address == NULL) | |
848 | { | ||
849 | ✗ | bus_address = g_dbus_address_get_for_bus_sync (priv->bus_type, | |
850 | priv->cancellable, | ||
851 | &child_error); | ||
852 | } | ||
853 | |||
854 | ✗ | if (child_error != NULL) | |
855 | { | ||
856 | ✗ | g_set_error (error, GSS_SERVICE_ERROR, GSS_SERVICE_ERROR_NAME_UNAVAILABLE, | |
857 | ✗ | _("D-Bus unavailable: %s"), child_error->message); | |
858 | ✗ | gss_service_release (self); | |
859 | ✗ | return; | |
860 | } | ||
861 | |||
862 | ✗ | g_autoptr(GAsyncResult) connection_result = NULL; | |
863 | ✗ | g_dbus_connection_new_for_address (bus_address, | |
864 | G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | | ||
865 | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, | ||
866 | NULL /* observer */, | ||
867 | priv->cancellable, | ||
868 | result_cb, | ||
869 | &connection_result); | ||
870 | |||
871 | /* Run the main loop until we get a connection or exit. */ | ||
872 | ✗ | while (connection_result == NULL) | |
873 | ✗ | g_main_context_iteration (NULL, TRUE); | |
874 | |||
875 | ✗ | priv->connection = g_dbus_connection_new_for_address_finish (connection_result, | |
876 | &child_error); | ||
877 | |||
878 | ✗ | if (priv->connection == NULL) | |
879 | { | ||
880 | ✗ | g_set_error (error, GSS_SERVICE_ERROR, GSS_SERVICE_ERROR_NAME_UNAVAILABLE, | |
881 | _("D-Bus bus ‘%s’ unavailable: %s"), | ||
882 | ✗ | bus_address, child_error->message); | |
883 | ✗ | gss_service_release (self); | |
884 | ✗ | return; | |
885 | } | ||
886 | |||
887 | /* Set up the debug controller. */ | ||
888 | ✗ | priv->debug_controller = G_DEBUG_CONTROLLER (g_debug_controller_dbus_new (priv->connection, NULL, &child_error)); | |
889 | ✗ | if (priv->debug_controller == NULL) | |
890 | { | ||
891 | ✗ | g_set_error (error, GSS_SERVICE_ERROR, GSS_SERVICE_ERROR_NAME_UNAVAILABLE, | |
892 | _("Could not register debug controller on bus: %s"), | ||
893 | ✗ | child_error->message); | |
894 | ✗ | gss_service_release (self); | |
895 | ✗ | return; | |
896 | } | ||
897 | |||
898 | ✗ | priv->debug_controller_authorize_id = g_signal_connect (priv->debug_controller, | |
899 | "authorize", | ||
900 | G_CALLBACK (debug_controller_authorize_cb), | ||
901 | self); | ||
902 | |||
903 | /* Start up. */ | ||
904 | ✗ | g_autoptr(GAsyncResult) startup_result = NULL; | |
905 | ✗ | g_assert (service_class->startup_async != NULL && | |
906 | service_class->startup_finish != NULL); | ||
907 | ✗ | service_class->startup_async (self, priv->cancellable, result_cb, | |
908 | &startup_result); | ||
909 | |||
910 | ✗ | while (startup_result == NULL) | |
911 | ✗ | g_main_context_iteration (NULL, TRUE); | |
912 | |||
913 | ✗ | service_class->startup_finish (self, startup_result, &child_error); | |
914 | |||
915 | /* If the service exited early, propagate that error rather than the one from | ||
916 | * startup_finish() which is likely just G_IO_ERROR_CANCELLED | ||
917 | */ | ||
918 | ✗ | if (!check_for_early_exit (self, error)) | |
919 | ✗ | return; | |
920 | |||
921 | ✗ | if (child_error != NULL) | |
922 | { | ||
923 | ✗ | g_propagate_error (error, g_steal_pointer (&child_error)); | |
924 | ✗ | gss_service_release (self); | |
925 | ✗ | return; | |
926 | } | ||
927 | |||
928 | /* Grab a well-known name. */ | ||
929 | ✗ | g_auto (BusNameId) bus_name_id = | |
930 | ✗ | g_bus_own_name_on_connection (priv->connection, | |
931 | ✗ | priv->service_id, | |
932 | G_BUS_NAME_OWNER_FLAGS_NONE, | ||
933 | name_acquired_cb, | ||
934 | name_lost_cb, | ||
935 | self, NULL); | ||
936 | |||
937 | /* Run the main loop until stopped from a callback with gss_service_exit(). */ | ||
938 | ✗ | while (priv->run_error == NULL && !priv->run_exited) | |
939 | ✗ | g_main_context_iteration (NULL, TRUE); | |
940 | |||
941 | ✗ | gss_service_hold (self); | |
942 | |||
943 | /* Notify systemd we’re shutting down. */ | ||
944 | ✗ | sd_notify (0, "STOPPING=1"); | |
945 | |||
946 | /* Debug. */ | ||
947 | ✗ | g_debug ("Shutting down: cancellable: %s, run_error: %s, run_exited: %s, " | |
948 | "run_exit_signal: %d", | ||
949 | g_cancellable_is_cancelled (priv->cancellable) ? "cancelled" : "no", | ||
950 | (priv->run_error != NULL) ? "set" : "unset", | ||
951 | priv->run_exited ? "yes" : "no", | ||
952 | priv->run_exit_signal); | ||
953 | |||
954 | /* Shut down. */ | ||
955 | ✗ | g_assert (service_class->shutdown != NULL); | |
956 | ✗ | service_class->shutdown (self); | |
957 | |||
958 | ✗ | gss_service_release (self); | |
959 | |||
960 | ✗ | if (priv->run_error != NULL) | |
961 | { | ||
962 | ✗ | g_propagate_error (error, priv->run_error); | |
963 | ✗ | priv->run_error = NULL; | |
964 | ✗ | return; | |
965 | } | ||
966 | } | ||
967 | |||
968 | /** | ||
969 | * gss_service_exit: | ||
970 | * @self: a #GssService | ||
971 | * @error: (nullable): error which caused the process to exit, or %NULL for a | ||
972 | * successful exit | ||
973 | * @signum: signal which caused the process to exit, or 0 for a successful exit | ||
974 | * | ||
975 | * Cause the service to exit from gss_service_run(). If @error is non-%NULL, the | ||
976 | * service will exit with the given error; otherwise it will exit successfully. | ||
977 | * If this is called multiple times, all errors except the first will be | ||
978 | * ignored, so it may be safely used for error handling in shutdown code. | ||
979 | * | ||
980 | * It is a programmer error to call this before startup_async() has been called | ||
981 | * on your service. | ||
982 | * | ||
983 | * Since: 0.1.0 | ||
984 | */ | ||
985 | void | ||
986 | ✗ | gss_service_exit (GssService *self, | |
987 | const GError *error, | ||
988 | int signum) | ||
989 | { | ||
990 | ✗ | g_autoptr(GError) allocated_error = NULL; | |
991 | |||
992 | ✗ | g_return_if_fail (GSS_IS_SERVICE (self)); | |
993 | ✗ | g_return_if_fail (error == NULL || signum == 0); | |
994 | |||
995 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
996 | |||
997 | ✗ | if (signum != 0) | |
998 | { | ||
999 | ✗ | g_assert (error == NULL); | |
1000 | |||
1001 | ✗ | g_set_error (&allocated_error, GSS_SERVICE_ERROR, | |
1002 | GSS_SERVICE_ERROR_SIGNALLED, | ||
1003 | _("Signalled with signal %d"), signum); | ||
1004 | ✗ | error = allocated_error; | |
1005 | } | ||
1006 | |||
1007 | ✗ | if (priv->run_error == NULL) | |
1008 | { | ||
1009 | ✗ | if (error != NULL) | |
1010 | ✗ | g_debug ("Exiting with error: %s", error->message); | |
1011 | else | ||
1012 | ✗ | g_debug ("Exiting with no error"); | |
1013 | |||
1014 | ✗ | if (error != NULL && priv->run_error == NULL) | |
1015 | ✗ | priv->run_error = g_error_copy (error); | |
1016 | } | ||
1017 | ✗ | else if (error != NULL) | |
1018 | { | ||
1019 | ✗ | g_debug ("Ignoring additional error: %s", error->message); | |
1020 | } | ||
1021 | |||
1022 | ✗ | priv->run_exited = TRUE; | |
1023 | ✗ | priv->run_exit_signal = signum; | |
1024 | ✗ | g_cancellable_cancel (priv->cancellable); | |
1025 | } | ||
1026 | |||
1027 | /** | ||
1028 | * gss_service_get_dbus_connection: | ||
1029 | * @self: a #GssService | ||
1030 | * | ||
1031 | * Get the #GDBusConnection used to export the service’s well-known name, as | ||
1032 | * specified in #GssService:bus-type. | ||
1033 | * | ||
1034 | * Returns: (transfer none): D-Bus connection | ||
1035 | * Since: 0.1.0 | ||
1036 | */ | ||
1037 | GDBusConnection * | ||
1038 | ✗ | gss_service_get_dbus_connection (GssService *self) | |
1039 | { | ||
1040 | ✗ | g_return_val_if_fail (GSS_IS_SERVICE (self), NULL); | |
1041 | |||
1042 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
1043 | ✗ | return priv->connection; | |
1044 | } | ||
1045 | |||
1046 | /** | ||
1047 | * gss_service_get_exit_signal: | ||
1048 | * @self: a #GssService | ||
1049 | * | ||
1050 | * Get the number of the signal which caused the #GssService to exit. | ||
1051 | * | ||
1052 | * Returns: exit signal number, or 0 if unset | ||
1053 | * Since: 0.1.0 | ||
1054 | */ | ||
1055 | int | ||
1056 | ✗ | gss_service_get_exit_signal (GssService *self) | |
1057 | { | ||
1058 | ✗ | g_return_val_if_fail (GSS_IS_SERVICE (self), 0); | |
1059 | |||
1060 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
1061 | ✗ | return priv->run_exit_signal; | |
1062 | } | ||
1063 | |||
1064 | /** | ||
1065 | * gss_service_get_inactivity_timeout: | ||
1066 | * @self: a #GssService | ||
1067 | * | ||
1068 | * Get the value of #GssService:inactivity-timeout. | ||
1069 | * | ||
1070 | * Returns: inactivity timeout, in milliseconds, or zero if inactivity is ignored | ||
1071 | * Since: 0.1.0 | ||
1072 | */ | ||
1073 | guint | ||
1074 | ✗ | gss_service_get_inactivity_timeout (GssService *self) | |
1075 | { | ||
1076 | ✗ | g_return_val_if_fail (GSS_IS_SERVICE (self), 0); | |
1077 | |||
1078 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
1079 | ✗ | return priv->inactivity_timeout_ms; | |
1080 | } | ||
1081 | |||
1082 | /** | ||
1083 | * gss_service_set_inactivity_timeout: | ||
1084 | * @self: a #GssService | ||
1085 | * @timeout_ms: inactivity timeout (in ms), or zero for no timeout | ||
1086 | * | ||
1087 | * Set the value of #GssService:inactivity-timeout. | ||
1088 | * | ||
1089 | * Since: 0.1.0 | ||
1090 | */ | ||
1091 | void | ||
1092 | ✗ | gss_service_set_inactivity_timeout (GssService *self, | |
1093 | guint timeout_ms) | ||
1094 | { | ||
1095 | ✗ | g_return_if_fail (GSS_IS_SERVICE (self)); | |
1096 | |||
1097 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
1098 | |||
1099 | ✗ | if (priv->inactivity_timeout_ms == timeout_ms) | |
1100 | ✗ | return; | |
1101 | |||
1102 | ✗ | priv->inactivity_timeout_ms = timeout_ms; | |
1103 | ✗ | g_object_notify (G_OBJECT (self), "inactivity-timeout"); | |
1104 | |||
1105 | ✗ | maybe_schedule_inactivity_timeout (self); | |
1106 | } | ||
1107 | |||
1108 | /** | ||
1109 | * gss_service_get_debug_controller: | ||
1110 | * @self: a #GssService | ||
1111 | * | ||
1112 | * Get the value of #GssService:debug-controller. | ||
1113 | * | ||
1114 | * Returns: (transfer none) (nullable): the service’s debug controller, or | ||
1115 | * %NULL if none is available | ||
1116 | * Since: 0.2.0 | ||
1117 | */ | ||
1118 | GDebugController * | ||
1119 | ✗ | gss_service_get_debug_controller (GssService *self) | |
1120 | { | ||
1121 | ✗ | g_return_val_if_fail (GSS_IS_SERVICE (self), NULL); | |
1122 | |||
1123 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
1124 | ✗ | return priv->debug_controller; | |
1125 | } | ||
1126 | |||
1127 | /** | ||
1128 | * gss_service_hold: | ||
1129 | * @self: a #GssService | ||
1130 | * | ||
1131 | * Increase the hold count of the service, and hence prevent it from | ||
1132 | * automatically exiting after the #GssService:inactivity-timeout period expires | ||
1133 | * with no activity. | ||
1134 | * | ||
1135 | * Call gss_service_release() to decrement the hold count. Calls to these two | ||
1136 | * methods must be paired; it is a programmer error not to. | ||
1137 | * | ||
1138 | * Since: 0.1.0 | ||
1139 | */ | ||
1140 | void | ||
1141 | ✗ | gss_service_hold (GssService *self) | |
1142 | { | ||
1143 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
1144 | |||
1145 | ✗ | g_return_if_fail (GSS_IS_SERVICE (self)); | |
1146 | ✗ | g_return_if_fail (priv->hold_count < G_MAXUINT); | |
1147 | |||
1148 | ✗ | priv->hold_count++; | |
1149 | ✗ | cancel_inactivity_timeout (self); | |
1150 | } | ||
1151 | |||
1152 | /** | ||
1153 | * gss_service_release: | ||
1154 | * @self: a #GssService | ||
1155 | * | ||
1156 | * Decrease the hold count of the service, and hence potentially (if the hold | ||
1157 | * count reaches zero) allow it to automatically exit after the | ||
1158 | * #GssService:inactivity-timeout period expires with no activity. | ||
1159 | * | ||
1160 | * Call gss_service_hold() to increment the hold count. Calls to these two | ||
1161 | * methods must be paired; it is a programmer error not to. | ||
1162 | * | ||
1163 | * Since: 0.1.0 | ||
1164 | */ | ||
1165 | void | ||
1166 | ✗ | gss_service_release (GssService *self) | |
1167 | { | ||
1168 | ✗ | GssServicePrivate *priv = gss_service_get_instance_private (self); | |
1169 | |||
1170 | ✗ | g_return_if_fail (GSS_IS_SERVICE (self)); | |
1171 | ✗ | g_return_if_fail (priv->hold_count > 0); | |
1172 | |||
1173 | ✗ | priv->hold_count--; | |
1174 | ✗ | maybe_schedule_inactivity_timeout (self); | |
1175 | } | ||
1176 |