chiark / gitweb /
Move handling of sysv initscripts to a generator
[elogind.git] / src / sysv-generator / sysv-generator.c
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
44 typedef enum RunlevelType {
45         RUNLEVEL_UP,
46         RUNLEVEL_DOWN
47 } RunlevelType;
48
49 static 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
72 typedef 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
86 const char *arg_dest = "/tmp";
87
88 static 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
115 static 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
194 static 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
203 static 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
220 static 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
282 finish:
283         *_r = r;
284
285         return 1;
286 }
287
288 static 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
629 static 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
670 static 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
729 static 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
851 finish:
852
853         for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
854                 set_free(runlevel_services[i]);
855
856         return r;
857 }
858
859 int 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 }