]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/sysv-generator/sysv-generator.c
Move handling of sysv initscripts to a generator
[thirdparty/systemd.git] / src / sysv-generator / sysv-generator.c
CommitLineData
95ed3294
TA
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2014 Thomas H.P. Andersen
7 Copyright 2010 Lennart Poettering
8 Copyright 2011 Michal Schmidt
9
10 systemd is free software; you can redistribute it and/or modify it
11 under the terms of the GNU Lesser General Public License as published by
12 the Free Software Foundation; either version 2.1 of the License, or
13 (at your option) any later version.
14
15 systemd is distributed in the hope that it will be useful, but
16 WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 Lesser General Public License for more details.
19
20 You should have received a copy of the GNU Lesser General Public License
21 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22***/
23
24#include <errno.h>
25#include <stdio.h>
26#include <unistd.h>
27
28#include "util.h"
29#include "mkdir.h"
30#include "strv.h"
31#include "path-util.h"
32#include "path-lookup.h"
33#include "log.h"
34#include "strv.h"
35#include "unit.h"
36#include "unit-name.h"
37#include "special.h"
38#include "exit-status.h"
39#include "def.h"
40#include "env-util.h"
41#include "fileio.h"
42#include "hashmap.h"
43
44typedef enum RunlevelType {
45 RUNLEVEL_UP,
46 RUNLEVEL_DOWN
47} RunlevelType;
48
49static const struct {
50 const char *path;
51 const char *target;
52 const RunlevelType type;
53} rcnd_table[] = {
54 /* Standard SysV runlevels for start-up */
55 { "rc1.d", SPECIAL_RESCUE_TARGET, RUNLEVEL_UP },
56 { "rc2.d", SPECIAL_RUNLEVEL2_TARGET, RUNLEVEL_UP },
57 { "rc3.d", SPECIAL_RUNLEVEL3_TARGET, RUNLEVEL_UP },
58 { "rc4.d", SPECIAL_RUNLEVEL4_TARGET, RUNLEVEL_UP },
59 { "rc5.d", SPECIAL_RUNLEVEL5_TARGET, RUNLEVEL_UP },
60
61 /* Standard SysV runlevels for shutdown */
62 { "rc0.d", SPECIAL_POWEROFF_TARGET, RUNLEVEL_DOWN },
63 { "rc6.d", SPECIAL_REBOOT_TARGET, RUNLEVEL_DOWN }
64
65 /* Note that the order here matters, as we read the
66 directories in this order, and we want to make sure that
67 sysv_start_priority is known when we first load the
68 unit. And that value we only know from S links. Hence
69 UP must be read before DOWN */
70};
71
72typedef struct SysvStub {
73 char *name;
74 char *path;
75 char *description;
76 int sysv_start_priority;
77 char *pid_file;
78 char **before;
79 char **after;
80 char **wants;
81 char **conflicts;
82 bool has_lsb;
83 bool reload;
84} SysvStub;
85
86const char *arg_dest = "/tmp";
87
88static int add_symlink(const char *service, const char *where) {
89 _cleanup_free_ char *from = NULL, *to = NULL;
90 int r;
91
92 assert(service);
93 assert(where);
94
95 from = strjoin(arg_dest, "/", service, NULL);
96 if (!from)
97 return log_oom();
98
99 to = strjoin(arg_dest, "/", where, ".wants/", service, NULL);
100 if (!to)
101 return log_oom();
102
103 mkdir_parents_label(to, 0755);
104
105 r = symlink(from, to);
106 if (r < 0) {
107 if (errno == EEXIST)
108 return 0;
109 return -errno;
110 }
111
112 return 1;
113}
114
115static int generate_unit_file(SysvStub *s) {
116 char *unit;
117 char **p;
118 _cleanup_fclose_ FILE *f = NULL;
119 _cleanup_free_ char *before = NULL;
120 _cleanup_free_ char *after = NULL;
121 _cleanup_free_ char *conflicts = NULL;
122 int r;
123
124 before = strv_join(s->before, " ");
125 if (!before)
126 return log_oom();
127
128 after = strv_join(s->after, " ");
129 if (!after)
130 return log_oom();
131
132 conflicts = strv_join(s->conflicts, " ");
133 if (!conflicts)
134 return log_oom();
135
136 unit = strjoin(arg_dest, "/", s->name, NULL);
137 if (!unit)
138 return log_oom();
139
140 f = fopen(unit, "wxe");
141 if (!f) {
142 log_error("Failed to create unit file %s: %m", unit);
143 return -errno;
144 }
145
146 fprintf(f,
147 "# Automatically generated by systemd-sysv-generator\n\n"
148 "[Unit]\n"
149 "SourcePath=%s\n"
150 "Description=%s\n",
151 s->path, s->description);
152
153 if (!isempty(before))
154 fprintf(f, "Before=%s\n", before);
155 if (!isempty(after))
156 fprintf(f, "After=%s\n", after);
157 if (!isempty(conflicts))
158 fprintf(f, "Conflicts=%s\n", conflicts);
159
160 fprintf(f,
161 "\n[Service]\n"
162 "Type=forking\n"
163 "Restart=no\n"
164 "TimeoutSec=5min\n"
165 "IgnoreSIGPIPE=no\n"
166 "KillMode=process\n"
167 "GuessMainPID=no\n"
168 "RemainAfterExit=%s\n",
169 yes_no(!s->pid_file));
170
171 if (s->sysv_start_priority > 0)
172 fprintf(f, "SysVStartPriority=%d\n", s->sysv_start_priority);
173
174 if (s->pid_file)
175 fprintf(f, "PidFile=%s\n", s->pid_file);
176
177 fprintf(f,
178 "ExecStart=%s start\n"
179 "ExecStop=%s stop\n",
180 s->path, s->path);
181
182 if (s->reload)
183 fprintf(f, "ExecReload=%s reload\n", s->path);
184
185 STRV_FOREACH(p, s->wants) {
186 r = add_symlink(s->name, *p);
187 if (r < 0)
188 log_error_unit(s->name, "Failed to create 'Wants' symlink to %s: %s", *p, strerror(-r));
189 }
190
191 return 0;
192}
193
194static bool usage_contains_reload(const char *line) {
195 return (strcasestr(line, "{reload|") ||
196 strcasestr(line, "{reload}") ||
197 strcasestr(line, "{reload\"") ||
198 strcasestr(line, "|reload|") ||
199 strcasestr(line, "|reload}") ||
200 strcasestr(line, "|reload\""));
201}
202
203static char *sysv_translate_name(const char *name) {
204 char *r;
205
206 r = new(char, strlen(name) + strlen(".service") + 1);
207 if (!r)
208 return NULL;
209
210 if (endswith(name, ".sh"))
211 /* Drop .sh suffix */
212 strcpy(stpcpy(r, name) - 3, ".service");
213 else
214 /* Normal init script name */
215 strcpy(stpcpy(r, name), ".service");
216
217 return r;
218}
219
220static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
221
222 /* We silently ignore the $ prefix here. According to the LSB
223 * spec it simply indicates whether something is a
224 * standardized name or a distribution-specific one. Since we
225 * just follow what already exists and do not introduce new
226 * uses or names we don't care who introduced a new name. */
227
228 static const char * const table[] = {
229 /* LSB defined facilities */
230 "local_fs", NULL,
231 "network", SPECIAL_NETWORK_ONLINE_TARGET,
232 "named", SPECIAL_NSS_LOOKUP_TARGET,
233 "portmap", SPECIAL_RPCBIND_TARGET,
234 "remote_fs", SPECIAL_REMOTE_FS_TARGET,
235 "syslog", NULL,
236 "time", SPECIAL_TIME_SYNC_TARGET,
237 };
238
239 unsigned i;
240 char *r;
241 const char *n;
242
243 assert(name);
244 assert(_r);
245
246 n = *name == '$' ? name + 1 : name;
247
248 for (i = 0; i < ELEMENTSOF(table); i += 2) {
249
250 if (!streq(table[i], n))
251 continue;
252
253 if (!table[i+1])
254 return 0;
255
256 r = strdup(table[i+1]);
257 if (!r)
258 return log_oom();
259
260 goto finish;
261 }
262
263 /* If we don't know this name, fallback heuristics to figure
264 * out whether something is a target or a service alias. */
265
266 if (*name == '$') {
267 if (!unit_prefix_is_valid(n))
268 return -EINVAL;
269
270 /* Facilities starting with $ are most likely targets */
271 r = unit_name_build(n, NULL, ".target");
272 } else if (filename && streq(name, filename))
273 /* Names equaling the file name of the services are redundant */
274 return 0;
275 else
276 /* Everything else we assume to be normal service names */
277 r = sysv_translate_name(n);
278
279 if (!r)
280 return -ENOMEM;
281
282finish:
283 *_r = r;
284
285 return 1;
286}
287
288static int load_sysv(SysvStub *s) {
289 _cleanup_fclose_ FILE *f;
290 unsigned line = 0;
291 int r;
292 enum {
293 NORMAL,
294 DESCRIPTION,
295 LSB,
296 LSB_DESCRIPTION,
297 USAGE_CONTINUATION
298 } state = NORMAL;
299 _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
300 char *description;
301 bool supports_reload = false;
302
303 assert(s);
304
305 f = fopen(s->path, "re");
306 if (!f)
307 return errno == ENOENT ? 0 : -errno;
308
309 while (!feof(f)) {
310 char l[LINE_MAX], *t;
311
312 if (!fgets(l, sizeof(l), f)) {
313 if (feof(f))
314 break;
315
316 log_error_unit(s->name,
317 "Failed to read configuration file '%s': %m",
318 s->path);
319 return -errno;
320 }
321
322 line++;
323
324 t = strstrip(l);
325 if (*t != '#') {
326 /* Try to figure out whether this init script supports
327 * the reload operation. This heuristic looks for
328 * "Usage" lines which include the reload option. */
329 if ( state == USAGE_CONTINUATION ||
330 (state == NORMAL && strcasestr(t, "usage"))) {
331 if (usage_contains_reload(t)) {
332 supports_reload = true;
333 state = NORMAL;
334 } else if (t[strlen(t)-1] == '\\')
335 state = USAGE_CONTINUATION;
336 else
337 state = NORMAL;
338 }
339
340 continue;
341 }
342
343 if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
344 state = LSB;
345 s->has_lsb = true;
346 continue;
347 }
348
349 if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
350 state = NORMAL;
351 continue;
352 }
353
354 t++;
355 t += strspn(t, WHITESPACE);
356
357 if (state == NORMAL) {
358
359 /* Try to parse Red Hat style description */
360
361 if (startswith_no_case(t, "description:")) {
362
363 size_t k = strlen(t);
364 char *d;
365 const char *j;
366
367 if (t[k-1] == '\\') {
368 state = DESCRIPTION;
369 t[k-1] = 0;
370 }
371
372 j = strstrip(t+12);
373 if (j && *j) {
374 d = strdup(j);
375 if (!d)
376 return -ENOMEM;
377 } else
378 d = NULL;
379
380 free(chkconfig_description);
381 chkconfig_description = d;
382
383 } else if (startswith_no_case(t, "pidfile:")) {
384
385 char *fn;
386
387 state = NORMAL;
388
389 fn = strstrip(t+8);
390 if (!path_is_absolute(fn)) {
391 log_error_unit(s->name,
392 "[%s:%u] PID file not absolute. Ignoring.",
393 s->path, line);
394 continue;
395 }
396
397 fn = strdup(fn);
398 if (!fn)
399 return -ENOMEM;
400
401 free(s->pid_file);
402 s->pid_file = fn;
403 }
404
405 } else if (state == DESCRIPTION) {
406
407 /* Try to parse Red Hat style description
408 * continuation */
409
410 size_t k = strlen(t);
411 char *j;
412
413 if (t[k-1] == '\\')
414 t[k-1] = 0;
415 else
416 state = NORMAL;
417
418 j = strstrip(t);
419 if (j && *j) {
420 char *d = NULL;
421
422 if (chkconfig_description)
423 d = strjoin(chkconfig_description, " ", j, NULL);
424 else
425 d = strdup(j);
426
427 if (!d)
428 return -ENOMEM;
429
430 free(chkconfig_description);
431 chkconfig_description = d;
432 }
433
434 } else if (state == LSB || state == LSB_DESCRIPTION) {
435
436 if (startswith_no_case(t, "Provides:")) {
437 char *i, *w;
438 size_t z;
439
440 state = LSB;
441
442 FOREACH_WORD_QUOTED(w, z, t+9, i) {
443 _cleanup_free_ char *n = NULL, *m = NULL;
444
445 n = strndup(w, z);
446 if (!n)
447 return -ENOMEM;
448
449 r = sysv_translate_facility(n, basename(s->path), &m);
450
451 if (r < 0)
452 return r;
453
454 if (r == 0)
455 continue;
456
457 if (unit_name_to_type(m) != UNIT_SERVICE) {
458 /* NB: SysV targets
459 * which are provided
460 * by a service are
461 * pulled in by the
462 * services, as an
463 * indication that the
464 * generic service is
465 * now available. This
466 * is strictly
467 * one-way. The
468 * targets do NOT pull
469 * in the SysV
470 * services! */
471 r = strv_extend(&s->before, m);
472 if (r < 0)
473 return log_oom();
474 r = strv_extend(&s->wants, m);
475 if (r < 0)
476 return log_oom();
477 }
478
479 if (r < 0)
480 log_error_unit(s->name,
481 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
482 s->path, line, m, strerror(-r));
483 }
484
485 } else if (startswith_no_case(t, "Required-Start:") ||
486 startswith_no_case(t, "Should-Start:") ||
487 startswith_no_case(t, "X-Start-Before:") ||
488 startswith_no_case(t, "X-Start-After:")) {
489 char *i, *w;
490 size_t z;
491
492 state = LSB;
493
494 FOREACH_WORD_QUOTED(w, z, strchr(t, ':')+1, i) {
495 _cleanup_free_ char *n = NULL, *m = NULL;
496 bool is_before;
497
498 n = strndup(w, z);
499 if (!n)
500 return -ENOMEM;
501
502 r = sysv_translate_facility(n, basename(s->path), &m);
503 if (r < 0) {
504 log_error_unit(s->name,
505 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
506 s->path, line, n, strerror(-r));
507 continue;
508 }
509
510 if (r == 0)
511 continue;
512
513 is_before = startswith_no_case(t, "X-Start-Before:");
514
515 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
516 /* the network-online target is special, as it needs to be actively pulled in */
517 r = strv_extend(&s->after, m);
518 if (r < 0)
519 return log_oom();
520 r = strv_extend(&s->wants, m);
521 if (r < 0)
522 return log_oom();
523 }
524 else {
525 if (is_before) {
526 r = strv_extend(&s->before, m);
527 if (r < 0)
528 return log_oom();
529 }
530 else {
531 r = strv_extend(&s->after, m);
532 if (r < 0)
533 return log_oom();
534 }
535 }
536
537 if (r < 0)
538 log_error_unit(s->name,
539 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
540 s->path, line, m, strerror(-r));
541 }
542
543 } else if (startswith_no_case(t, "Description:")) {
544 char *d, *j;
545
546 state = LSB_DESCRIPTION;
547
548 j = strstrip(t+12);
549 if (j && *j) {
550 d = strdup(j);
551 if (!d)
552 return -ENOMEM;
553 } else
554 d = NULL;
555
556 free(long_description);
557 long_description = d;
558
559 } else if (startswith_no_case(t, "Short-Description:")) {
560 char *d, *j;
561
562 state = LSB;
563
564 j = strstrip(t+18);
565 if (j && *j) {
566 d = strdup(j);
567 if (!d)
568 return -ENOMEM;
569 } else
570 d = NULL;
571
572 free(short_description);
573 short_description = d;
574
575 } else if (state == LSB_DESCRIPTION) {
576
577 if (startswith(l, "#\t") || startswith(l, "# ")) {
578 char *j;
579
580 j = strstrip(t);
581 if (j && *j) {
582 char *d = NULL;
583
584 if (long_description)
585 d = strjoin(long_description, " ", t, NULL);
586 else
587 d = strdup(j);
588
589 if (!d)
590 return -ENOMEM;
591
592 free(long_description);
593 long_description = d;
594 }
595
596 } else
597 state = LSB;
598 }
599 }
600 }
601
602 s->reload = supports_reload;
603
604 /* We use the long description only if
605 * no short description is set. */
606
607 if (short_description)
608 description = short_description;
609 else if (chkconfig_description)
610 description = chkconfig_description;
611 else if (long_description)
612 description = long_description;
613 else
614 description = NULL;
615
616 if (description) {
617 char *d;
618
619 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
620 if (!d)
621 return -ENOMEM;
622
623 s->description = d;
624 }
625
626 return 0;
627}
628
629static int fix_order(SysvStub *s, Hashmap *all_services) {
630 SysvStub *other;
631 Iterator j;
632 int r;
633
634 assert(s);
635
636 if (s->sysv_start_priority < 0)
637 return 0;
638
639 HASHMAP_FOREACH(other, all_services, j) {
640 if (s == other)
641 continue;
642
643 if (other->sysv_start_priority < 0)
644 continue;
645
646 /* If both units have modern headers we don't care
647 * about the priorities */
648 if (s->has_lsb && other->has_lsb)
649 continue;
650
651 if (other->sysv_start_priority < s->sysv_start_priority) {
652 r = strv_extend(&s->after, other->name);
653 if (r < 0)
654 return log_oom();
655 }
656 else if (other->sysv_start_priority > s->sysv_start_priority) {
657 r = strv_extend(&s->before, other->name);
658 if (r < 0)
659 return log_oom();
660 }
661 else
662 continue;
663
664 /* FIXME: Maybe we should compare the name here lexicographically? */
665 }
666
667 return 0;
668}
669
670static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
671 char **path;
672
673 STRV_FOREACH(path, lp.sysvinit_path) {
674 _cleanup_closedir_ DIR *d = NULL;
675 struct dirent *de;
676
677 d = opendir(*path);
678 if (!d) {
679 if (errno != ENOENT)
680 log_warning("opendir(%s) failed: %m", *path);
681 continue;
682 }
683
684 while ((de = readdir(d))) {
685 SysvStub *service;
686 struct stat st;
687 _cleanup_free_ char *fpath = NULL, *name = NULL;
688 int r;
689
690 if (ignore_file(de->d_name))
691 continue;
692
693 fpath = strjoin(*path, "/", de->d_name, NULL);
694 if (!fpath)
695 return log_oom();
696
697 if (stat(fpath, &st) < 0)
698 continue;
699
700 if (!(st.st_mode & S_IXUSR))
701 continue;
702
703 name = sysv_translate_name(de->d_name);
704 if (!name)
705 return log_oom();
706
707 if (hashmap_contains(all_services, name))
708 continue;
709
710 service = new0(SysvStub, 1);
711 if (!service)
712 return log_oom();
713
714 service->sysv_start_priority = -1;
715 service->name = name;
716 service->path = fpath;
717
718 r = hashmap_put(all_services, service->name, service);
719 if (r < 0)
720 return log_oom();
721
722 name = fpath = NULL;
723 }
724 }
725
726 return 0;
727}
728
729static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
730 char **p;
731 unsigned i;
732 _cleanup_closedir_ DIR *d = NULL;
733 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
734 SysvStub *service;
735 Iterator j;
736 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
737 _cleanup_set_free_ Set *shutdown_services = NULL;
738 int r = 0;
739
740 STRV_FOREACH(p, lp.sysvrcnd_path)
741 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
742 struct dirent *de;
743
744 free(path);
745 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
746 if (!path)
747 return -ENOMEM;
748
749 if (d)
750 closedir(d);
751
752 d = opendir(path);
753 if (!d) {
754 if (errno != ENOENT)
755 log_warning("opendir(%s) failed: %m", path);
756
757 continue;
758 }
759
760 while ((de = readdir(d))) {
761 int a, b;
762
763 if (ignore_file(de->d_name))
764 continue;
765
766 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
767 continue;
768
769 if (strlen(de->d_name) < 4)
770 continue;
771
772 a = undecchar(de->d_name[1]);
773 b = undecchar(de->d_name[2]);
774
775 if (a < 0 || b < 0)
776 continue;
777
778 free(fpath);
779 fpath = strjoin(*p, "/", de->d_name, NULL);
780 if (!fpath) {
781 r = -ENOMEM;
782 goto finish;
783 }
784
785 name = sysv_translate_name(de->d_name + 3);
786 if (!name) {
787 r = log_oom();
788 goto finish;
789 }
790
791 if (hashmap_contains(all_services, name))
792 service = hashmap_get(all_services, name);
793 else {
794 log_warning("Could not find init scirpt for %s", name);
795 continue;
796 }
797
798 if (de->d_name[0] == 'S') {
799
800 if (rcnd_table[i].type == RUNLEVEL_UP) {
801 service->sysv_start_priority =
802 MAX(a*10 + b, service->sysv_start_priority);
803 }
804
805 r = set_ensure_allocated(&runlevel_services[i],
806 trivial_hash_func, trivial_compare_func);
807 if (r < 0)
808 goto finish;
809
810 r = set_put(runlevel_services[i], service);
811 if (r < 0)
812 goto finish;
813
814 } else if (de->d_name[0] == 'K' &&
815 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
816
817 r = set_ensure_allocated(&shutdown_services,
818 trivial_hash_func, trivial_compare_func);
819 if (r < 0)
820 goto finish;
821
822 r = set_put(shutdown_services, service);
823 if (r < 0)
824 goto finish;
825 }
826 }
827 }
828
829
830 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
831 SET_FOREACH(service, runlevel_services[i], j) {
832 r = strv_extend(&service->before, rcnd_table[i].target);
833 if (r < 0)
834 return log_oom();
835 r = strv_extend(&service->wants, rcnd_table[i].target);
836 if (r < 0)
837 return log_oom();
838 }
839
840 SET_FOREACH(service, shutdown_services, j) {
841 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
842 if (r < 0)
843 return log_oom();
844 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
845 if (r < 0)
846 return log_oom();
847 }
848
849 r = 0;
850
851finish:
852
853 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
854 set_free(runlevel_services[i]);
855
856 return r;
857}
858
859int main(int argc, char *argv[]) {
860 int r, q;
861 LookupPaths lp;
862 Hashmap *all_services;
863 SysvStub *service;
864 Iterator j;
865
866 if (argc > 1 && argc != 4) {
867 log_error("This program takes three or no arguments.");
868 return EXIT_FAILURE;
869 }
870
871 if (argc > 1)
872 arg_dest = argv[3];
873
874 log_set_target(LOG_TARGET_SAFE);
875 log_parse_environment();
876 log_open();
877
878 umask(0022);
879
880 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
881 if (r < 0) {
882 log_error("Failed to find lookup paths.");
883 return EXIT_FAILURE;
884 }
885
886 all_services = hashmap_new(string_hash_func, string_compare_func);
887 if (!all_services) {
888 log_oom();
889 return EXIT_FAILURE;
890 }
891
892 r = enumerate_sysv(lp, all_services);
893 if (r < 0) {
894 log_error("Failed to generate units for all init scripts.");
895 return EXIT_FAILURE;
896 }
897
898 r = set_dependencies_from_rcnd(lp, all_services);
899 if (r < 0) {
900 log_error("Failed to read runlevels from rcnd links.");
901 return EXIT_FAILURE;
902 }
903
904 HASHMAP_FOREACH(service, all_services, j) {
905 q = load_sysv(service);
906 if (q < 0)
907 continue;
908
909 q = fix_order(service, all_services);
910 if (q < 0)
911 continue;
912
913 q = generate_unit_file(service);
914 if (q < 0)
915 continue;
916 }
917
918 return EXIT_SUCCESS;
919}