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