chiark / gitweb /
add test for garbage collector
[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                         log_debug("Garbage collecting job %s", name_id(j->name));
319
320                         transaction_delete_job(m, j);
321                         again = true;
322                         break;
323                 }
324
325         } while (again);
326 }
327
328 static int transaction_is_destructive(Manager *m, JobMode mode) {
329         void *state;
330         Job *j;
331
332         assert(m);
333
334         /* Checks whether applying this transaction means that
335          * existing jobs would be replaced */
336
337         HASHMAP_FOREACH(j, m->transaction_jobs, state)
338                 if (j->name->meta.job && j->name->meta.job != j)
339                         return -EEXIST;
340
341         return 0;
342 }
343
344 static int transaction_apply(Manager *m, JobMode mode) {
345         void *state;
346         Job *j;
347         int r;
348
349         HASHMAP_FOREACH(j, m->transaction_jobs, state) {
350                 if (j->linked)
351                         continue;
352
353                 if ((r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j)) < 0)
354                         goto rollback;
355         }
356
357         while ((j = hashmap_steal_first(m->transaction_jobs))) {
358                 if (j->linked)
359                         continue;
360
361                 if (j->name->meta.job)
362                         job_free(j->name->meta.job);
363
364                 j->name->meta.job = j;
365                 j->linked = true;
366
367                 /* We're fully installed. Now let's free data we don't
368                  * need anymore. */
369
370                 assert(!j->transaction_next);
371                 assert(!j->transaction_prev);
372
373                 while (j->subject_list)
374                         job_dependency_free(j->subject_list);
375                 while (j->object_list)
376                         job_dependency_free(j->object_list);
377         }
378
379         return 0;
380
381 rollback:
382
383         HASHMAP_FOREACH(j, m->transaction_jobs, state) {
384                 if (j->linked)
385                         continue;
386
387                 hashmap_remove(m->jobs, UINT32_TO_PTR(j->id));
388         }
389
390         return r;
391 }
392
393
394 static int transaction_activate(Manager *m, JobMode mode) {
395         int r;
396         unsigned generation = 1;
397
398         assert(m);
399
400         /* This applies the changes recorded in transaction_jobs to
401          * the actual list of jobs, if possible. */
402
403         /* First step: figure out which jobs matter */
404         transaction_find_jobs_that_matter_to_anchor(m, NULL, generation++);
405
406         /* Second step: let's merge entries we can merge */
407         if ((r = transaction_merge_jobs(m)) < 0)
408                 goto rollback;
409
410         /* Third step: verify order makes sense */
411         if ((r = transaction_verify_order(m, &generation)) < 0)
412                 goto rollback;
413
414         /* Third step: do garbage colletion */
415         transaction_collect_garbage(m);
416
417         /* Fourth step: check whether we can actually apply this */
418         if (mode == JOB_FAIL)
419                 if ((r = transaction_is_destructive(m, mode)) < 0)
420                         goto rollback;
421
422         /* Fifth step: apply changes */
423         if ((r = transaction_apply(m, mode)) < 0)
424                 goto rollback;
425
426         assert(hashmap_isempty(m->transaction_jobs));
427         assert(!m->transaction_anchor);
428
429         return 0;
430
431 rollback:
432         transaction_abort(m);
433         return r;
434 }
435
436 static Job* transaction_add_one_job(Manager *m, JobType type, Name *name, bool *is_new) {
437         Job *j, *f;
438         int r;
439
440         assert(m);
441         assert(name);
442
443         /* Looks for an axisting prospective job and returns that. If
444          * it doesn't exist it is created and added to the prospective
445          * jobs list. */
446
447         f = hashmap_get(m->transaction_jobs, name);
448
449         for (j = f; j; j = j->transaction_next) {
450                 assert(j->name == name);
451
452                 if (j->type == type) {
453                         if (is_new)
454                                 *is_new = false;
455                         return j;
456                 }
457         }
458
459         if (name->meta.job && name->meta.job->type == type)
460                 j = name->meta.job;
461         else if (!(j = job_new(m, type, name)))
462                 return NULL;
463
464         if ((r = hashmap_replace(m->transaction_jobs, name, j)) < 0) {
465                 job_free(j);
466                 return NULL;
467         }
468
469         j->transaction_next = f;
470
471         if (f)
472                 f->transaction_prev = j;
473
474         j->generation = 0;
475         j->marker = NULL;
476         j->matters_to_anchor = false;
477
478         if (is_new)
479                 *is_new = true;
480
481         return j;
482 }
483
484 void manager_transaction_unlink_job(Manager *m, Job *j) {
485         assert(m);
486         assert(j);
487
488         if (j->transaction_prev)
489                 j->transaction_prev->transaction_next = j->transaction_next;
490         else if (j->transaction_next)
491                 hashmap_replace(m->transaction_jobs, j->name, j->transaction_next);
492         else
493                 hashmap_remove_value(m->transaction_jobs, j->name, j);
494
495         if (j->transaction_next)
496                 j->transaction_next->transaction_prev = j->transaction_prev;
497
498         j->transaction_prev = j->transaction_next = NULL;
499
500         while (j->subject_list)
501                 job_dependency_free(j->subject_list);
502
503         while (j->object_list) {
504                 Job *other = j->object_list->matters ? j->object_list->subject : NULL;
505
506                 job_dependency_free(j->object_list);
507
508                 if (other) {
509                         log_debug("Deleting job %s as dependency of job %s", name_id(other->name), name_id(j->name));
510                         transaction_delete_job(m, other);
511                 }
512         }
513 }
514
515 static int transaction_add_job_and_dependencies(Manager *m, JobType type, Name *name, Job *by, bool matters, bool force, Job **_ret) {
516         Job *ret;
517         void *state;
518         Name *dep;
519         int r;
520         bool is_new;
521
522         assert(m);
523         assert(type < _JOB_TYPE_MAX);
524         assert(name);
525
526         if (name->meta.state != NAME_LOADED)
527                 return -EINVAL;
528
529         /* First add the job. */
530         if (!(ret = transaction_add_one_job(m, type, name, &is_new)))
531                 return -ENOMEM;
532
533         /* Then, add a link to the job. */
534         if (!job_dependency_new(by, ret, matters))
535                 return -ENOMEM;
536
537         if (is_new) {
538                 /* Finally, recursively add in all dependencies. */
539                 if (type == JOB_START || type == JOB_RELOAD_OR_START) {
540                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRES], state)
541                                 if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, force, NULL)) < 0)
542                                         goto fail;
543                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUIRES], state)
544                                 if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, !force, force, NULL)) < 0)
545                                         goto fail;
546                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_WANTS], state)
547                                 if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, force, NULL)) < 0)
548                                         goto fail;
549                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUISITE], state)
550                                 if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_STARTED, dep, ret, true, force, NULL)) < 0)
551                                         goto fail;
552                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUISITE], state)
553                                 if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_STARTED, dep, ret, !force, force, NULL)) < 0)
554                                         goto fail;
555                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_CONFLICTS], state)
556                                 if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, force, NULL)) < 0)
557                                         goto fail;
558
559                 } else if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) {
560
561                         SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRED_BY], state)
562                                 if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, force, NULL)) < 0)
563                                         goto fail;
564                 }
565
566                 /* JOB_VERIFY_STARTED, JOB_RELOAD require no dependency handling */
567         }
568
569         return 0;
570
571 fail:
572         return r;
573 }
574
575 int manager_add_job(Manager *m, JobType type, Name *name, JobMode mode, bool force, Job **_ret) {
576         int r;
577         Job *ret;
578
579         assert(m);
580         assert(type < _JOB_TYPE_MAX);
581         assert(name);
582         assert(mode < _JOB_MODE_MAX);
583
584         if ((r = transaction_add_job_and_dependencies(m, type, name, NULL, true, force, &ret))) {
585                 transaction_abort(m);
586                 return r;
587         }
588
589         if ((r = transaction_activate(m, mode)) < 0)
590                 return r;
591
592         if (_ret)
593                 *_ret = ret;
594
595         return 0;
596 }
597
598 Job *manager_get_job(Manager *m, uint32_t id) {
599         assert(m);
600
601         return hashmap_get(m->jobs, UINT32_TO_PTR(id));
602 }
603
604 Name *manager_get_name(Manager *m, const char *name) {
605         assert(m);
606         assert(name);
607
608         return hashmap_get(m->names, name);
609 }
610
611 static int dispatch_load_queue(Manager *m) {
612         Meta *meta;
613
614         assert(m);
615
616         /* Make sure we are not run recursively */
617         if (m->dispatching_load_queue)
618                 return 0;
619
620         m->dispatching_load_queue = true;
621
622         /* Dispatches the load queue. Takes a name from the queue and
623          * tries to load its data until the queue is empty */
624
625         while ((meta = m->load_queue)) {
626                 name_load(NAME(meta));
627                 LIST_REMOVE(Meta, m->load_queue, meta);
628         }
629
630         m->dispatching_load_queue = false;
631
632         return 0;
633 }
634
635 int manager_load_name(Manager *m, const char *name, Name **_ret) {
636         Name *ret;
637         NameType t;
638         int r;
639         char *n;
640
641         assert(m);
642         assert(name);
643         assert(_ret);
644
645         if (!name_is_valid(name))
646                 return -EINVAL;
647
648         /* This will load the service information files, but not actually
649          * start any services or anything */
650
651         if ((ret = manager_get_name(m, name)))
652                 goto finish;
653
654         if ((t = name_type_from_string(name)) == _NAME_TYPE_INVALID)
655                 return -EINVAL;
656
657         if (!(ret = name_new(m)))
658                 return -ENOMEM;
659
660         ret->meta.type = t;
661
662         if (!(n = strdup(name))) {
663                 name_free(ret);
664                 return -ENOMEM;
665         }
666
667         if (set_put(ret->meta.names, n) < 0) {
668                 name_free(ret);
669                 free(n);
670                 return -ENOMEM;
671         }
672
673         if ((r = name_link(ret)) < 0) {
674                 name_free(ret);
675                 return r;
676         }
677
678         /* At this point the new entry is created and linked. However,
679          * not loaded. Now load this entry and all its dependencies
680          * recursively */
681
682         dispatch_load_queue(m);
683
684 finish:
685
686         *_ret = ret;
687         return 0;
688 }
689
690 void manager_dump_jobs(Manager *s, FILE *f, const char *prefix) {
691         void *state;
692         Job *j;
693
694         assert(s);
695         assert(f);
696
697         HASHMAP_FOREACH(j, s->jobs, state)
698                 job_dump(j, f, prefix);
699 }
700
701 void manager_dump_names(Manager *s, FILE *f, const char *prefix) {
702         void *state;
703         Name *n;
704         const char *t;
705
706         assert(s);
707         assert(f);
708
709         HASHMAP_FOREACH_KEY(n, t, s->names, state)
710                 if (name_id(n) == t)
711                         name_dump(n, f, prefix);
712 }
713
714 void manager_clear_jobs(Manager *m) {
715         Job *j;
716
717         assert(m);
718
719         transaction_abort(m);
720
721         while ((j = hashmap_first(m->jobs)))
722                 job_free(j);
723 }