chiark / gitweb /
c71bceb4846ecb1ba5276c360d717af259af1b27
[elogind.git] / manager.c
1 /*-*- Mode: C; c-basic-offset: 8 -*-*/
2
3 #include <assert.h>
4 #include <errno.h>
5 #include <string.h>
6
7 #include "manager.h"
8 #include "hashmap.h"
9 #include "macro.h"
10 #include "strv.h"
11 #include "log.h"
12
13 Manager* manager_new(void) {
14         Manager *m;
15
16         if (!(m = new0(Manager, 1)))
17                 return NULL;
18
19         if (!(m->names = hashmap_new(string_hash_func, string_compare_func)))
20                 goto fail;
21
22         if (!(m->jobs = hashmap_new(trivial_hash_func, trivial_compare_func)))
23                 goto fail;
24
25         if (!(m->transaction_jobs = hashmap_new(trivial_hash_func, trivial_compare_func)))
26                 goto fail;
27
28         return m;
29
30 fail:
31         manager_free(m);
32         return NULL;
33 }
34
35 void manager_free(Manager *m) {
36         Name *n;
37         Job *j;
38
39         assert(m);
40
41         while ((n = hashmap_first(m->names)))
42                 name_free(n);
43
44         while ((j = hashmap_steal_first(m->transaction_jobs)))
45                 job_free(j);
46
47         hashmap_free(m->names);
48         hashmap_free(m->jobs);
49         hashmap_free(m->transaction_jobs);
50
51         free(m);
52 }
53
54 static void transaction_delete_job(Manager *m, Job *j) {
55         assert(m);
56         assert(j);
57
58         manager_transaction_unlink_job(m, j);
59
60         if (!j->linked)
61                 job_free(j);
62 }
63
64 static void transaction_abort(Manager *m) {
65         Job *j;
66
67         assert(m);
68
69         while ((j = hashmap_first(m->transaction_jobs)))
70                 if (j->linked)
71                         transaction_delete_job(m, j);
72                 else
73                         job_free(j);
74
75         assert(hashmap_isempty(m->transaction_jobs));
76         assert(!m->transaction_anchor);
77 }
78
79 static void transaction_find_jobs_that_matter_to_anchor(Manager *m, Job *j, unsigned generation) {
80         JobDependency *l;
81
82         assert(m);
83
84         for (l = j ? j->subject_list : m->transaction_anchor; l; l = l->subject_next) {
85
86                 /* This link does not matter */
87                 if (!l->matters)
88                         continue;
89
90                 /* This name has already been marked */
91                 if (l->object->generation == generation)
92                         continue;
93
94                 l->object->matters_to_anchor = true;
95                 l->object->generation = generation;
96
97                 transaction_find_jobs_that_matter_to_anchor(m, l->object, generation);
98         }
99 }
100
101 static bool types_match(JobType a, JobType b, JobType c, JobType d) {
102         return
103                 (a == c && b == d) ||
104                 (a == d && b == c);
105 }
106
107 static int types_merge(JobType *a, JobType b) {
108         if (*a == b)
109                 return 0;
110
111         if (types_match(*a, b, JOB_START, JOB_VERIFY_STARTED))
112                 *a = JOB_START;
113         else if (types_match(*a, b, JOB_START, JOB_RELOAD) ||
114                  types_match(*a, b, JOB_START, JOB_RELOAD_OR_START) ||
115                  types_match(*a, b, JOB_VERIFY_STARTED, JOB_RELOAD_OR_START) ||
116                  types_match(*a, b, JOB_RELOAD, JOB_RELOAD_OR_START))
117                 *a = JOB_RELOAD_OR_START;
118         else if (types_match(*a, b, JOB_START, JOB_RESTART) ||
119                  types_match(*a, b, JOB_START, JOB_TRY_RESTART) ||
120                  types_match(*a, b, JOB_VERIFY_STARTED, JOB_RESTART) ||
121                  types_match(*a, b, JOB_RELOAD, JOB_RESTART) ||
122                  types_match(*a, b, JOB_RELOAD_OR_START, JOB_RESTART) ||
123                  types_match(*a, b, JOB_RELOAD_OR_START, JOB_TRY_RESTART) ||
124                  types_match(*a, b, JOB_RESTART, JOB_TRY_RESTART))
125                 *a = JOB_RESTART;
126         else if (types_match(*a, b, JOB_VERIFY_STARTED, JOB_RELOAD))
127                 *a = JOB_RELOAD;
128         else if (types_match(*a, b, JOB_VERIFY_STARTED, JOB_TRY_RESTART) ||
129                  types_match(*a, b, JOB_RELOAD, JOB_TRY_RESTART))
130                 *a = JOB_TRY_RESTART;
131
132         return -EEXIST;
133 }
134
135 static void transaction_merge_and_delete_job(Manager *m, Job *j, Job *other, JobType t) {
136         JobDependency *l, *last;
137
138         assert(j);
139         assert(other);
140         assert(j->name == other->name);
141         assert(!j->linked);
142
143         j->type = t;
144         j->state = JOB_WAITING;
145
146         j->matters_to_anchor = j->matters_to_anchor || other->matters_to_anchor;
147
148         /* Patch us in as new owner of the JobDependency objects */
149         last = NULL;
150         for (l = other->subject_list; l; l = l->subject_next) {
151                 assert(l->subject == other);
152                 l->subject = j;
153                 last = l;
154         }
155
156         /* Merge both lists */
157         if (last) {
158                 last->subject_next = j->subject_list;
159                 if (j->subject_list)
160                         j->subject_list->subject_prev = last;
161                 j->subject_list = other->subject_list;
162         }
163
164         /* Patch us in as new owner of the JobDependency objects */
165         last = NULL;
166         for (l = other->object_list; l; l = l->object_next) {
167                 assert(l->object == other);
168                 l->object = j;
169                 last = l;
170         }
171
172         /* Merge both lists */
173         if (last) {
174                 last->object_next = j->object_list;
175                 if (j->object_list)
176                         j->object_list->object_prev = last;
177                 j->object_list = other->object_list;
178         }
179
180         /* Kill the other job */
181         other->subject_list = NULL;
182         other->object_list = NULL;
183         transaction_delete_job(m, other);
184 }
185
186 static int transaction_merge_jobs(Manager *m) {
187         Job *j;
188         void *state;
189         int r;
190
191         assert(m);
192
193         HASHMAP_FOREACH(j, m->transaction_jobs, state) {
194                 JobType t = j->type;
195                 Job *k;
196
197                 for (k = j->transaction_next; k; k = k->transaction_next)
198                         if ((r = types_merge(&t, k->type)) < 0)
199                                 return r;
200
201                 while ((k = j->transaction_next)) {
202                         if (j->linked) {
203                                 transaction_merge_and_delete_job(m, k, j, t);
204                                 j = k;
205                         } else
206                                 transaction_merge_and_delete_job(m, j, k, t);
207                 }
208
209                 assert(!j->transaction_next);
210                 assert(!j->transaction_prev);
211         }
212
213         return 0;
214 }
215
216 static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned generation) {
217         void *state;
218         Name *n;
219         int r;
220
221         assert(m);
222         assert(j);
223
224         /* Did we find a cycle? */
225         if (j->marker && j->generation == generation) {
226                 Job *k;
227
228                 /* So, we already have been here. We have a
229                  * cycle. Let's try to break it. We go backwards in our
230                  * path and try to find a suitable job to remove. */
231
232                 for (k = from; k; k = (k->generation == generation ? k->marker : NULL)) {
233                         if (!k->matters_to_anchor) {
234                                 log_debug("Breaking order cycle by deleting job %s", name_id(k->name));
235                                 transaction_delete_job(m, k);
236                                 return -EAGAIN;
237                         }
238
239                         /* Check if this in fact was the beginning of
240                          * the cycle */
241                         if (k == j)
242                                 break;
243                 }
244
245                 return -ELOOP;
246         }
247
248         j->marker = from;
249         j->generation = generation;
250
251         /* We assume that the the dependencies are both-ways, and
252          * hence can ignore NAME_AFTER */
253
254         SET_FOREACH(n, j->name->meta.dependencies[NAME_BEFORE], state) {
255                 Job *o;
256
257                 if (!(o = hashmap_get(m->transaction_jobs, n)))
258                         if (!(o = n->meta.job))
259                                 continue;
260
261                 if ((r = transaction_verify_order_one(m, o, j, generation)) < 0)
262                         return r;
263         }
264
265         return 0;
266 }
267
268 static int transaction_verify_order(Manager *m, unsigned *generation) {
269         bool again;
270         assert(m);
271         assert(generation);
272
273         do {
274                 Job *j;
275                 int r;
276                 void *state;
277
278                 again = false;
279
280                 HASHMAP_FOREACH(j, m->transaction_jobs, state) {
281
282                         /* Assume merged */
283                         assert(!j->transaction_next);
284                         assert(!j->transaction_prev);
285
286                         if ((r = transaction_verify_order_one(m, j, NULL, (*generation)++)) < 0)  {
287
288                                 /* There was a cycleq, but it was fixed,
289                                  * we need to restart our algorithm */
290                                 if (r == -EAGAIN) {
291                                         again = true;
292                                         break;
293                                 }
294
295                                 return r;
296                         }
297                 }
298         } while (again);
299
300         return 0;
301 }
302
303 static void transaction_collect_garbage(Manager *m) {
304         bool again;
305
306         assert(m);
307
308         do {
309                 void *state;
310                 Job *j;
311
312                 again = false;
313
314                 HASHMAP_FOREACH(j, m->transaction_jobs, state) {
315                         if (j->object_list)
316                                 continue;
317
318                         transaction_delete_job(m, j);
319                         again = true;
320                         break;
321                 }
322
323         } while (again);
324 }
325
326 static int transaction_is_destructive(Manager *m, JobMode mode) {
327         void *state;
328         Job *j;
329
330         assert(m);
331
332         /* Checks whether applying this transaction means that
333          * existing jobs would be replaced */
334
335         HASHMAP_FOREACH(j, m->transaction_jobs, state)
336                 if (j->name->meta.job && j->name->meta.job != j)
337                         return -EEXIST;
338
339         return 0;
340 }
341
342 static int transaction_apply(Manager *m, JobMode mode) {
343         void *state;
344         Job *j;
345         int r;
346
347         HASHMAP_FOREACH(j, m->transaction_jobs, state) {
348                 if (j->linked)
349                         continue;
350
351                 if ((r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j)) < 0)
352                         goto rollback;
353         }
354
355         while ((j = hashmap_steal_first(m->transaction_jobs))) {
356                 if (j->linked)
357                         continue;
358
359                 if (j->name->meta.job)
360                         job_free(j->name->meta.job);
361
362                 j->name->meta.job = j;
363                 j->linked = true;
364
365                 /* We're fully installed. Now let's free data we don't
366                  * need anymore. */
367
368                 assert(!j->transaction_next);
369                 assert(!j->transaction_prev);
370
371                 while (j->subject_list)
372                         job_dependency_free(j->subject_list);
373                 while (j->object_list)
374                         job_dependency_free(j->object_list);
375         }
376
377         return 0;
378
379 rollback:
380
381         HASHMAP_FOREACH(j, m->transaction_jobs, state) {
382                 if (j->linked)
383                         continue;
384
385                 hashmap_remove(m->jobs, UINT32_TO_PTR(j->id));
386         }
387
388         return r;
389 }
390
391
392 static int transaction_activate(Manager *m, JobMode mode) {
393         int r;
394         unsigned generation = 1;
395
396         assert(m);
397
398         /* This applies the changes recorded in transaction_jobs to
399          * the actual list of jobs, if possible. */
400
401         /* First step: figure out which jobs matter */
402         transaction_find_jobs_that_matter_to_anchor(m, NULL, generation++);
403
404         /* Second step: let's merge entries we can merge */
405         if ((r = transaction_merge_jobs(m)) < 0)
406                 goto rollback;
407
408         /* Third step: verify order makes sense */
409         if ((r = transaction_verify_order(m, &generation)) < 0)
410                 goto rollback;
411
412         /* Third step: do garbage colletion */
413         transaction_collect_garbage(m);
414
415         /* Fourth step: check whether we can actually apply this */
416         if (mode == JOB_FAIL)
417                 if ((r = transaction_is_destructive(m, mode)) < 0)
418                         goto rollback;
419
420         /* Fifth step: apply changes */
421         if ((r = transaction_apply(m, mode)) < 0)
422                 goto rollback;
423
424         assert(hashmap_isempty(m->transaction_jobs));
425         assert(!m->transaction_anchor);
426
427         return 0;
428
429 rollback:
430         transaction_abort(m);
431         return r;
432 }
433
434 static Job* transaction_add_one_job(Manager *m, JobType type, Name *name, bool *is_new) {
435         Job *j, *f;
436         int r;
437
438         assert(m);
439         assert(name);
440
441         /* Looks for an axisting prospective job and returns that. If
442          * it doesn't exist it is created and added to the prospective
443          * jobs list. */
444
445         f = hashmap_get(m->transaction_jobs, name);
446
447         for (j = f; j; j = j->transaction_next) {
448                 assert(j->name == name);
449
450                 if (j->type == type) {
451                         if (is_new)
452                                 *is_new = false;
453                         return j;
454                 }
455         }
456
457         if (name->meta.job && name->meta.job->type == type)
458                 j = name->meta.job;
459         else if (!(j = job_new(m, type, name)))
460                 return NULL;
461
462         if ((r = hashmap_replace(m->transaction_jobs, name, j)) < 0) {
463                 job_free(j);
464                 return NULL;
465         }
466
467         j->transaction_next = f;
468
469         if (f)
470                 f->transaction_prev = j;
471
472         j->generation = 0;
473         j->marker = NULL;
474         j->matters_to_anchor = false;
475
476         if (is_new)
477                 *is_new = true;
478
479         return j;
480 }
481
482 void manager_transaction_unlink_job(Manager *m, Job *j) {
483         assert(m);
484         assert(j);
485
486         if (j->transaction_prev)
487                 j->transaction_prev->transaction_next = j->transaction_next;
488         else if (j->transaction_next)
489                 hashmap_replace(m->transaction_jobs, j->name, j->transaction_next);
490         else
491                 hashmap_remove_value(m->transaction_jobs, j->name, j);
492
493         if (j->transaction_next)
494                 j->transaction_next->transaction_prev = j->transaction_prev;
495
496         j->transaction_prev = j->transaction_next = NULL;
497
498         while (j->subject_list)
499                 job_dependency_free(j->subject_list);
500
501         while (j->object_list) {
502                 Job *other = j->object_list->matters ? j->object_list->subject : NULL;
503
504                 job_dependency_free(j->object_list);
505
506                 if (other) {
507                         log_debug("Deleting job %s dependency of job %s", name_id(other->name), name_id(j->name));
508                         transaction_delete_job(m, other);
509                 }
510         }
511 }
512
513 static int transaction_add_job_and_dependencies(Manager *m, JobType type, Name *name, Job *by, bool matters, bool force, Job **_ret) {
514         Job *ret;
515         void *state;
516         Name *dep;
517         int r;
518         bool is_new;
519
520         assert(m);
521         assert(type < _JOB_TYPE_MAX);
522         assert(name);
523
524         if (name->meta.state != NAME_LOADED)
525                 return -EINVAL;
526
527         /* First add the job. */
528         if (!(ret = transaction_add_one_job(m, type, name, &is_new)))
529                 return -ENOMEM;
530
531         /* Then, add a link to the job. */
532         if (!job_dependency_new(by, ret, matters))
533                 return -ENOMEM;
534
535         if (is_new) {
536                 /* Finally, recursively add in all dependencies. */
537                 if (type == JOB_START || type == JOB_RELOAD_OR_START) {
538                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRES], state)
539                                 if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, force, NULL)) < 0)
540                                         goto fail;
541                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUIRES], state)
542                                 if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, !force, force, NULL)) < 0)
543                                         goto fail;
544                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_WANTS], state)
545                                 if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, force, NULL)) < 0)
546                                         goto fail;
547                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUISITE], state)
548                                 if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_STARTED, dep, ret, true, force, NULL)) < 0)
549                                         goto fail;
550                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUISITE], state)
551                                 if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_STARTED, dep, ret, !force, force, NULL)) < 0)
552                                         goto fail;
553                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_CONFLICTS], state)
554                                 if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, force, NULL)) < 0)
555                                         goto fail;
556
557                 } else if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) {
558
559                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRED_BY], state)
560                                 if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, force, NULL)) < 0)
561                                         goto fail;
562                 }
563
564                 /* JOB_VERIFY_STARTED, JOB_RELOAD require no dependency handling */
565         }
566
567         return 0;
568
569 fail:
570         return r;
571 }
572
573 int manager_add_job(Manager *m, JobType type, Name *name, JobMode mode, bool force, Job **_ret) {
574         int r;
575         Job *ret;
576
577         assert(m);
578         assert(type < _JOB_TYPE_MAX);
579         assert(name);
580         assert(mode < _JOB_MODE_MAX);
581
582         if ((r = transaction_add_job_and_dependencies(m, type, name, NULL, true, force, &ret))) {
583                 transaction_abort(m);
584                 return r;
585         }
586
587         if ((r = transaction_activate(m, mode)) < 0)
588                 return r;
589
590         if (_ret)
591                 *_ret = ret;
592
593         return 0;
594 }
595
596 Job *manager_get_job(Manager *m, uint32_t id) {
597         assert(m);
598
599         return hashmap_get(m->jobs, UINT32_TO_PTR(id));
600 }
601
602 Name *manager_get_name(Manager *m, const char *name) {
603         assert(m);
604         assert(name);
605
606         return hashmap_get(m->names, name);
607 }
608
609 static int dispatch_load_queue(Manager *m) {
610         Meta *meta;
611
612         assert(m);
613
614         /* Make sure we are not run recursively */
615         if (m->dispatching_load_queue)
616                 return 0;
617
618         m->dispatching_load_queue = true;
619
620         /* Dispatches the load queue. Takes a name from the queue and
621          * tries to load its data until the queue is empty */
622
623         while ((meta = m->load_queue)) {
624                 name_load(NAME(meta));
625                 LIST_REMOVE(Meta, m->load_queue, meta);
626         }
627
628         m->dispatching_load_queue = false;
629
630         return 0;
631 }
632
633 int manager_load_name(Manager *m, const char *name, Name **_ret) {
634         Name *ret;
635         NameType t;
636         int r;
637         char *n;
638
639         assert(m);
640         assert(name);
641         assert(_ret);
642
643         if (!name_is_valid(name))
644                 return -EINVAL;
645
646         /* This will load the service information files, but not actually
647          * start any services or anything */
648
649         if ((ret = manager_get_name(m, name)))
650                 goto finish;
651
652         if ((t = name_type_from_string(name)) == _NAME_TYPE_INVALID)
653                 return -EINVAL;
654
655         if (!(ret = name_new(m)))
656                 return -ENOMEM;
657
658         ret->meta.type = t;
659
660         if (!(n = strdup(name))) {
661                 name_free(ret);
662                 return -ENOMEM;
663         }
664
665         if (set_put(ret->meta.names, n) < 0) {
666                 name_free(ret);
667                 free(n);
668                 return -ENOMEM;
669         }
670
671         if ((r = name_link(ret)) < 0) {
672                 name_free(ret);
673                 return r;
674         }
675
676         /* At this point the new entry is created and linked. However,
677          * not loaded. Now load this entry and all its dependencies
678          * recursively */
679
680         dispatch_load_queue(m);
681
682 finish:
683
684         *_ret = ret;
685         return 0;
686 }
687
688 void manager_dump_jobs(Manager *s, FILE *f, const char *prefix) {
689         void *state;
690         Job *j;
691
692         assert(s);
693         assert(f);
694
695         HASHMAP_FOREACH(j, s->jobs, state)
696                 job_dump(j, f, prefix);
697 }
698
699 void manager_dump_names(Manager *s, FILE *f, const char *prefix) {
700         void *state;
701         Name *n;
702         const char *t;
703
704         assert(s);
705         assert(f);
706
707         HASHMAP_FOREACH_KEY(n, t, s->names, state)
708                 if (name_id(n) == t)
709                         name_dump(n, f, prefix);
710 }
711
712 void manager_clear_jobs(Manager *m) {
713         Job *j;
714
715         assert(m);
716
717         transaction_abort(m);
718
719         while ((j = hashmap_first(m->jobs)))
720                 job_free(j);
721 }