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