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