b2ee7adb35e6c90f058b7b47e712626140927235
[profile/ivi/node-startup-controller.git] / node-startup-controller / job-manager.c
1 /* vi:set et ai sw=2 sts=2 ts=2: */
2 /* -
3  * Copyright (c) 2012 GENIVI.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9
10 #ifdef HAVE_CONFIG_H
11 #include <config.h>
12 #endif
13
14 #include <glib-object.h>
15 #include <gio/gio.h>
16
17 #include <node-startup-controller/job-manager.h>
18 #include <node-startup-controller/systemd-manager-dbus.h>
19
20
21
22 /**
23  * SECTION: job-manager 
24  * @title: JobManager
25  * @short_description: Manages systemd jobs.
26  * @stability: Internal
27  * 
28  * The Job Manager simplifies starting and stopping systemd units by handling all the
29  * JobRemoved signals internally, so units can be started and stopped using
30  * job_manager_start() and job_manager_stop().
31  */
32
33
34
35 typedef struct _JobManagerJob JobManagerJob;
36
37
38
39 /* property identifiers */
40 enum
41 {
42   PROP_0,
43   PROP_CONNECTION,
44   PROP_SYSTEMD_MANAGER,
45 };
46
47
48
49 static void           job_manager_constructed      (GObject           *object);
50 static void           job_manager_finalize         (GObject           *object);
51 static void           job_manager_get_property     (GObject           *object,
52                                                     guint              prop_id,
53                                                     GValue            *value,
54                                                     GParamSpec        *pspec);
55 static void           job_manager_set_property     (GObject           *object,
56                                                     guint              prop_id,
57                                                     const GValue      *value,
58                                                     GParamSpec        *pspec);
59 static void           job_manager_start_unit_reply (GObject           *object,
60                                                     GAsyncResult      *result,
61                                                     gpointer           user_data);
62 static void           job_manager_stop_unit_reply  (GObject           *object,
63                                                     GAsyncResult      *result,
64                                                     gpointer           user_data);
65 static void           job_manager_job_removed      (SystemdManager    *systemd_manager,
66                                                     guint              id,
67                                                     const gchar       *job_name,
68                                                     const gchar       *unit,
69                                                     const gchar       *result,
70                                                     JobManager        *job_manager);
71 static JobManagerJob *job_manager_job_new          (JobManager        *manager,
72                                                     const gchar       *unit,
73                                                     GCancellable      *cancellable,
74                                                     JobManagerCallback callback,
75                                                     gpointer           user_data);
76 static void           job_manager_job_unref        (JobManagerJob     *job);
77 static void           job_manager_remember_job     (JobManager        *manager,
78                                                     const gchar       *job_name,
79                                                     JobManagerJob     *job);
80 static void           job_manager_forget_job       (JobManager        *manager,
81                                                     const gchar       *job_name);
82
83
84
85 struct _JobManagerClass
86 {
87   GObjectClass __parent__;
88 };
89
90 struct _JobManager
91 {
92   GObject __parent__;
93
94   GDBusConnection *connection;
95   SystemdManager  *systemd_manager;
96
97   GHashTable      *jobs;
98 };
99
100 struct _JobManagerJob
101 {
102   JobManager        *manager;
103   gchar             *unit;
104   GCancellable      *cancellable;
105   JobManagerCallback callback;
106   gpointer           user_data;
107 };
108
109
110
111 G_DEFINE_TYPE (JobManager, job_manager, G_TYPE_OBJECT);
112
113
114
115 static void
116 job_manager_class_init (JobManagerClass *klass)
117 {
118   GObjectClass *gobject_class;
119
120   gobject_class = G_OBJECT_CLASS (klass);
121   gobject_class->finalize = job_manager_finalize;
122   gobject_class->constructed = job_manager_constructed;
123   gobject_class->get_property = job_manager_get_property;
124   gobject_class->set_property = job_manager_set_property;
125
126   g_object_class_install_property (gobject_class,
127                                    PROP_CONNECTION,
128                                    g_param_spec_object ("connection",
129                                                         "connection",
130                                                         "connection",
131                                                         G_TYPE_DBUS_CONNECTION,
132                                                         G_PARAM_READWRITE |
133                                                         G_PARAM_CONSTRUCT_ONLY |
134                                                         G_PARAM_STATIC_STRINGS));
135
136   g_object_class_install_property (gobject_class,
137                                    PROP_SYSTEMD_MANAGER,
138                                    g_param_spec_object ("systemd-manager",
139                                                         "systemd-manager",
140                                                         "systemd-manager",
141                                                         TYPE_SYSTEMD_MANAGER,
142                                                         G_PARAM_READWRITE |
143                                                         G_PARAM_CONSTRUCT_ONLY |
144                                                         G_PARAM_STATIC_STRINGS));
145 }
146
147
148
149 static void
150 job_manager_init (JobManager *manager)
151 {
152   /* create a mapping of systemd job names to job objects; we will use this
153    * to remember jobs that we started */
154   manager->jobs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
155                                         (GDestroyNotify) job_manager_job_unref);
156
157 }
158
159
160
161 static void
162 job_manager_finalize (GObject *object)
163 {
164   JobManager *manager = JOB_MANAGER (object);
165
166   /* release all the jobs we have remembered */
167   g_hash_table_unref (manager->jobs);
168
169   /* release the D-Bus connection */
170   g_object_unref (manager->connection);
171
172   /* release the systemd manager */
173   g_signal_handlers_disconnect_matched (manager->systemd_manager,
174                                         G_SIGNAL_MATCH_DATA,
175                                         0, 0, NULL, NULL, manager);
176   g_object_unref (manager->systemd_manager);
177
178   /* chain up to finalize parent class */
179   (*G_OBJECT_CLASS (job_manager_parent_class)->finalize) (object);
180 }
181
182
183
184 static void
185 job_manager_constructed (GObject *object)
186 {
187   JobManager *manager = JOB_MANAGER (object);
188
189   /* connect to systemd's "JobRemoved" signal so that we are notified
190    * whenever a job is finished */
191   g_signal_connect (manager->systemd_manager, "job-removed",
192                     G_CALLBACK (job_manager_job_removed), manager);
193 }
194
195
196
197 static void
198 job_manager_get_property (GObject    *object,
199                           guint       prop_id,
200                           GValue     *value,
201                           GParamSpec *pspec)
202 {
203   JobManager *manager = JOB_MANAGER (object);
204
205   switch (prop_id)
206     {
207     case PROP_CONNECTION:
208       g_value_set_object (value, manager->connection);
209       break;
210     case PROP_SYSTEMD_MANAGER:
211       g_value_set_object (value, manager->systemd_manager);
212       break;
213     default:
214       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
215       break;
216     }
217 }
218
219
220
221 static void
222 job_manager_set_property (GObject      *object,
223                           guint         prop_id,
224                           const GValue *value,
225                           GParamSpec   *pspec)
226 {
227   JobManager *manager = JOB_MANAGER (object);
228
229   switch (prop_id)
230     {
231     case PROP_CONNECTION:
232       manager->connection = g_value_dup_object (value);
233       break;
234     case PROP_SYSTEMD_MANAGER:
235       manager->systemd_manager = g_value_dup_object (value);
236       break;
237     default:
238       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
239       break;
240     }
241 }
242
243
244
245 static void
246 job_manager_start_unit_reply (GObject       *object,
247                               GAsyncResult *result,
248                               gpointer      user_data)
249 {
250   JobManagerJob *job = user_data;
251   GError        *error = NULL;
252   gchar         *job_name = NULL;
253
254   g_return_if_fail (IS_SYSTEMD_MANAGER (object));
255   g_return_if_fail (G_IS_ASYNC_RESULT (result));
256   g_return_if_fail (user_data != NULL);
257
258   /* finish the start unit call */
259   if (!systemd_manager_call_start_unit_finish (job->manager->systemd_manager,
260                                                &job_name, result, &error))
261     {
262       /* there was an error. notify the caller */
263       job->callback (job->manager, job->unit, "failed", error, job->user_data);
264       g_error_free (error);
265       g_free (job_name);
266
267       /* finish the job immediately */
268       job_manager_job_unref (job);
269     }
270   else
271     {
272       /* remember the job so that we can finish it in the "job-removed" signal handler.
273        * the service takes ownership of the job so we don't need to unref it here */
274       job_manager_remember_job (job->manager, job_name, job);
275     }
276 }
277
278
279
280 static void
281 job_manager_stop_unit_reply (GObject       *object,
282                              GAsyncResult *result,
283                              gpointer      user_data)
284 {
285   JobManagerJob *job = user_data;
286   GError        *error = NULL;
287   gchar         *job_name = NULL;
288
289   g_return_if_fail (IS_SYSTEMD_MANAGER (object));
290   g_return_if_fail (G_IS_ASYNC_RESULT (result));
291   g_return_if_fail (user_data != NULL);
292
293   /* finish the stop unit call */
294   if (!systemd_manager_call_stop_unit_finish (job->manager->systemd_manager,
295                                               &job_name, result, &error))
296     {
297       /* there was an error. notify the caller */
298       job->callback (job->manager, job->unit, "failed", error, job->user_data);
299       g_error_free (error);
300       g_free (job_name);
301
302       /* finish the job immediately */
303       job_manager_job_unref (job);
304     }
305   else
306     {
307       /* remember the job so that we can finish it in the "job-removed" signal handler.
308        * the service takes ownership of the job so we don't need to unref it here */
309       job_manager_remember_job (job->manager, job_name, job);
310     }
311 }
312
313
314
315 static void
316 job_manager_job_removed (SystemdManager *systemd_manager,
317                          guint           id,
318                          const gchar    *job_name,
319                          const gchar    *unit,
320                          const gchar    *result,
321                          JobManager     *job_manager)
322 {
323   JobManagerJob *job;
324
325   g_return_if_fail (IS_SYSTEMD_MANAGER (systemd_manager));
326   g_return_if_fail (job_name != NULL && *job_name != '\0');
327   g_return_if_fail (result != NULL && *result != '\0');
328   g_return_if_fail (unit != NULL && *unit != '\0');
329   g_return_if_fail (IS_JOB_MANAGER (job_manager));
330
331   /* look up the remembered job for this job name */
332   job = g_hash_table_lookup (job_manager->jobs, job_name);
333
334   /* if no job is found, ignore this job-removed signal */
335   if (job == NULL)
336     return;
337
338   /* finish the job by notifying the caller */
339   job->callback (job_manager, job->unit, result, NULL, job->user_data);
340
341   /* forget about this job */
342   job_manager_forget_job (job_manager, job_name);
343 }
344
345
346
347 static JobManagerJob *
348 job_manager_job_new (JobManager        *manager,
349                      const gchar       *unit,
350                      GCancellable      *cancellable,
351                      JobManagerCallback callback,
352                      gpointer           user_data)
353 {
354   JobManagerJob *job;
355
356   g_return_val_if_fail (IS_JOB_MANAGER (manager), NULL);
357   g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
358
359   /* allocate a new job struct */
360   job = g_slice_new0 (JobManagerJob);
361   job->manager = g_object_ref (manager);
362   job->unit = g_strdup(unit);
363   if (cancellable != NULL)
364     job->cancellable = g_object_ref (cancellable);
365   job->callback = callback;
366   job->user_data = user_data;
367
368   return job;
369 }
370
371
372 static void
373 job_manager_job_unref (JobManagerJob *job)
374 {
375   if (job == NULL)
376     return;
377
378   /* release all memory and references held by job */
379   if (job->cancellable != NULL)
380     g_object_unref (job->cancellable);
381   g_free (job->unit);
382   g_object_unref (job->manager);
383   g_slice_free (JobManagerJob, job);
384 }
385
386
387
388 static void
389 job_manager_remember_job (JobManager    *manager,
390                           const char    *job_name,
391                           JobManagerJob *job)
392 {
393   JobManagerJob *existing_job;
394
395   g_return_if_fail (IS_JOB_MANAGER (manager));
396   g_return_if_fail (job_name != NULL && *job_name != '\0');
397   g_return_if_fail (job != NULL);
398
399   /* if the job is already being remembered, there is a programming error that should be
400    * notified */
401   existing_job = g_hash_table_lookup (manager->jobs, job_name);
402   if (existing_job != NULL)
403     {
404       g_critical ("Trying to remember the same job twice.");
405       return;
406     }
407   /* associate the job name with the job */
408   g_hash_table_insert (manager->jobs, g_strdup (job_name), job);
409 }
410
411
412
413 static void
414 job_manager_forget_job (JobManager  *manager,
415                         const gchar *job_name)
416 {
417   g_return_if_fail (IS_JOB_MANAGER (manager));
418   g_return_if_fail (job_name != NULL && *job_name != '\0');
419
420   g_hash_table_remove (manager->jobs, job_name);
421 }
422
423
424 /**
425  * job_manager_new:
426  * @connection: A connection to the system bus. 
427  * @systemd_manager: An interface to the systemd manager created with 
428  * systemd_manager_proxy_new_for_bus_sync()
429  * 
430  * Creates a new JobManager object.
431  * 
432  * Returns: A new instance of the #JobManager.
433  */
434 JobManager *
435 job_manager_new (GDBusConnection *connection,
436                  SystemdManager  *systemd_manager)
437 {
438   g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
439   g_return_val_if_fail (IS_SYSTEMD_MANAGER (systemd_manager), NULL);
440
441   return g_object_new (TYPE_JOB_MANAGER,
442                        "connection", connection,
443                        "systemd-manager", systemd_manager,
444                        NULL);
445 }
446
447
448
449 /**
450  * job_manager_start:
451  * @unit: The name of the systemd unit to start.
452  * @callback: a #JobManagerCallback that is called after the job is started.
453  * @user_data: userdata that is available in the #JobManagerCallback.
454  * 
455  * Asynchronously starts @unit, and calls @callback with @user_data when it is finished.
456  */
457 void
458 job_manager_start (JobManager        *manager,
459                    const gchar       *unit,
460                    GCancellable      *cancellable,
461                    JobManagerCallback callback,
462                    gpointer           user_data)
463 {
464   JobManagerJob *job;
465
466   g_return_if_fail (IS_JOB_MANAGER (manager));
467   g_return_if_fail (unit != NULL);
468   g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
469   g_return_if_fail (callback != NULL);
470
471   /* create a new job object */
472   job = job_manager_job_new (manager, unit, cancellable, callback, user_data);
473
474   /* ask systemd to start the unit asynchronously */
475   systemd_manager_call_start_unit (manager->systemd_manager, unit, "fail", cancellable,
476                                    job_manager_start_unit_reply, job);
477 }
478
479
480
481 /**
482  * job_manager_stop:
483  * @unit: The name of the systemd unit to stop.
484  * @callback: a #JobManagerCallback that is called after the job is stopped.
485  * @user_data: userdata that is available in the #JobManagerCallback.
486  * 
487  * Asynchronously stops @unit, and calls @callback with @user_data when it is finished.
488  */
489 void
490 job_manager_stop (JobManager        *manager,
491                   const gchar       *unit,
492                   GCancellable      *cancellable,
493                   JobManagerCallback callback,
494                   gpointer           user_data)
495 {
496   JobManagerJob *job;
497
498   g_return_if_fail (IS_JOB_MANAGER (manager));
499   g_return_if_fail (unit != NULL);
500   g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
501   g_return_if_fail (callback != NULL);
502
503   /* create a new job object */
504   job = job_manager_job_new (manager, unit, cancellable, callback, user_data);
505
506   /* ask systemd to stop the unit asynchronously */
507   systemd_manager_call_stop_unit (manager->systemd_manager, unit, "fail", cancellable,
508                                   job_manager_stop_unit_reply, job);
509 }