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