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