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