chiark / gitweb /
sysv-generator: there's really no need to invoke fstatat() multiple times on the...
[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                         if (hidden_file(de->d_name))
753                                 continue;
754
755                         if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
756                                 log_warning_errno(errno, "stat() failed on %s/%s: %m", *path, de->d_name);
757                                 continue;
758                         }
759
760                         if (!(st.st_mode & S_IXUSR))
761                                 continue;
762
763                         if (!S_ISREG(st.st_mode))
764                                 continue;
765
766                         name = sysv_translate_name(de->d_name);
767                         if (!name)
768                                 return log_oom();
769
770                         if (hashmap_contains(all_services, name))
771                                 continue;
772
773                         fpath = strjoin(*path, "/", de->d_name, NULL);
774                         if (!fpath)
775                                 return log_oom();
776
777                         service = new0(SysvStub, 1);
778                         if (!service)
779                                 return log_oom();
780
781                         service->sysv_start_priority = -1;
782                         service->name = name;
783                         service->path = fpath;
784
785                         r = hashmap_put(all_services, service->name, service);
786                         if (r < 0)
787                                 return log_oom();
788
789                         name = fpath = NULL;
790                         service = NULL;
791                 }
792         }
793
794         return 0;
795 }
796
797 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
798         char **p;
799         unsigned i;
800         _cleanup_closedir_ DIR *d = NULL;
801         _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
802         SysvStub *service;
803         Iterator j;
804         Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
805         _cleanup_set_free_ Set *shutdown_services = NULL;
806         int r = 0;
807
808         STRV_FOREACH(p, lp.sysvrcnd_path)
809                 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
810                         struct dirent *de;
811
812                         free(path);
813                         path = strjoin(*p, "/", rcnd_table[i].path, NULL);
814                         if (!path)
815                                 return -ENOMEM;
816
817                         if (d)
818                                 closedir(d);
819
820                         d = opendir(path);
821                         if (!d) {
822                                 if (errno != ENOENT)
823                                         log_warning_errno(errno, "opendir(%s) failed: %m", path);
824
825                                 continue;
826                         }
827
828                         while ((de = readdir(d))) {
829                                 int a, b;
830
831                                 if (hidden_file(de->d_name))
832                                         continue;
833
834                                 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
835                                         continue;
836
837                                 if (strlen(de->d_name) < 4)
838                                         continue;
839
840                                 a = undecchar(de->d_name[1]);
841                                 b = undecchar(de->d_name[2]);
842
843                                 if (a < 0 || b < 0)
844                                         continue;
845
846                                 free(fpath);
847                                 fpath = strjoin(*p, "/", de->d_name, NULL);
848                                 if (!fpath) {
849                                         r = -ENOMEM;
850                                         goto finish;
851                                 }
852
853                                 name = sysv_translate_name(de->d_name + 3);
854                                 if (!name) {
855                                         r = log_oom();
856                                         goto finish;
857                                 }
858
859                                 service = hashmap_get(all_services, name);
860                                 if (!service){
861                                         log_warning("Could not find init script for %s", name);
862                                         continue;
863                                 }
864
865                                 if (de->d_name[0] == 'S')  {
866
867                                         if (rcnd_table[i].type == RUNLEVEL_UP) {
868                                                 service->sysv_start_priority =
869                                                         MAX(a*10 + b, service->sysv_start_priority);
870                                         }
871
872                                         r = set_ensure_allocated(&runlevel_services[i], NULL);
873                                         if (r < 0)
874                                                 goto finish;
875
876                                         r = set_put(runlevel_services[i], service);
877                                         if (r < 0)
878                                                 goto finish;
879
880                                 } else if (de->d_name[0] == 'K' &&
881                                            (rcnd_table[i].type == RUNLEVEL_DOWN)) {
882
883                                         r = set_ensure_allocated(&shutdown_services, NULL);
884                                         if (r < 0)
885                                                 goto finish;
886
887                                         r = set_put(shutdown_services, service);
888                                         if (r < 0)
889                                                 goto finish;
890                                 }
891                         }
892                 }
893
894
895         for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
896                 SET_FOREACH(service, runlevel_services[i], j) {
897                         r = strv_extend(&service->before, rcnd_table[i].target);
898                         if (r < 0)
899                                 return log_oom();
900                         r = strv_extend(&service->wanted_by, rcnd_table[i].target);
901                         if (r < 0)
902                                 return log_oom();
903                 }
904
905         SET_FOREACH(service, shutdown_services, j) {
906                 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
907                 if (r < 0)
908                         return log_oom();
909                 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
910                 if (r < 0)
911                         return log_oom();
912         }
913
914         r = 0;
915
916 finish:
917
918         for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
919                 set_free(runlevel_services[i]);
920
921         return r;
922 }
923
924 int main(int argc, char *argv[]) {
925         int r, q;
926         LookupPaths lp;
927         Hashmap *all_services;
928         SysvStub *service;
929         Iterator j;
930
931         if (argc > 1 && argc != 4) {
932                 log_error("This program takes three or no arguments.");
933                 return EXIT_FAILURE;
934         }
935
936         if (argc > 1)
937                 arg_dest = argv[3];
938
939         log_set_target(LOG_TARGET_SAFE);
940         log_parse_environment();
941         log_open();
942
943         umask(0022);
944
945         r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
946         if (r < 0) {
947                 log_error("Failed to find lookup paths.");
948                 return EXIT_FAILURE;
949         }
950
951         all_services = hashmap_new(&string_hash_ops);
952         if (!all_services) {
953                 log_oom();
954                 return EXIT_FAILURE;
955         }
956
957         r = enumerate_sysv(lp, all_services);
958         if (r < 0) {
959                 log_error("Failed to generate units for all init scripts.");
960                 return EXIT_FAILURE;
961         }
962
963         r = set_dependencies_from_rcnd(lp, all_services);
964         if (r < 0) {
965                 log_error("Failed to read runlevels from rcnd links.");
966                 return EXIT_FAILURE;
967         }
968
969         HASHMAP_FOREACH(service, all_services, j) {
970                 q = load_sysv(service);
971                 if (q < 0)
972                         continue;
973         }
974
975         HASHMAP_FOREACH(service, all_services, j) {
976                 q = fix_order(service, all_services);
977                 if (q < 0)
978                         continue;
979
980                 q = generate_unit_file(service);
981                 if (q < 0)
982                         continue;
983         }
984
985         return EXIT_SUCCESS;
986 }