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