]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/sysv-generator/sysv-generator.c
import: drop unused definition
[thirdparty/systemd.git] / src / sysv-generator / sysv-generator.c
CommitLineData
95ed3294
TA
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
b5efdb8a 26#include "alloc-util.h"
c279613f 27#include "dirent-util.h"
3ffd4af2 28#include "fd-util.h"
c279613f 29#include "fileio.h"
07630cea 30#include "hashmap.h"
8fcde012 31#include "hexdecoct.h"
07630cea
LP
32#include "install.h"
33#include "log.h"
95ed3294 34#include "mkdir.h"
95ed3294 35#include "path-lookup.h"
07630cea 36#include "path-util.h"
f2341e0a 37#include "set.h"
07630cea 38#include "special.h"
8fcde012 39#include "stat-util.h"
07630cea
LP
40#include "string-util.h"
41#include "strv.h"
42#include "unit-name.h"
43#include "util.h"
95ed3294
TA
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 */
d5d8429a
LP
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 },
95ed3294
TA
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
dd422d1e 73static const char *arg_dest = "/tmp";
8fba1c8d 74
95ed3294
TA
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;
260ad50f 84 char **wanted_by;
95ed3294
TA
85 char **conflicts;
86 bool has_lsb;
87 bool reload;
c279613f 88 bool loaded;
95ed3294
TA
89} SysvStub;
90
8fba1c8d 91static void free_sysvstub(SysvStub *s) {
c279613f
LP
92 if (!s)
93 return;
94
8fba1c8d
ZJS
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}
95ed3294
TA
117
118static int add_symlink(const char *service, const char *where) {
c279613f 119 const char *from, *to;
95ed3294
TA
120 int r;
121
122 assert(service);
123 assert(where);
124
c279613f
LP
125 from = strjoina(arg_dest, "/", service);
126 to = strjoina(arg_dest, "/", where, ".wants/", service);
95ed3294
TA
127
128 mkdir_parents_label(to, 0755);
129
130 r = symlink(from, to);
131 if (r < 0) {
132 if (errno == EEXIST)
133 return 0;
c279613f 134
95ed3294
TA
135 return -errno;
136 }
137
138 return 1;
139}
140
b7e71846 141static int add_alias(const char *service, const char *alias) {
c279613f 142 const char *link;
b7e71846
MB
143 int r;
144
145 assert(service);
146 assert(alias);
147
c279613f 148 link = strjoina(arg_dest, "/", alias);
b7e71846
MB
149
150 r = symlink(service, link);
151 if (r < 0) {
152 if (errno == EEXIST)
153 return 0;
c279613f 154
b7e71846
MB
155 return -errno;
156 }
157
158 return 1;
159}
160
95ed3294 161static int generate_unit_file(SysvStub *s) {
95ed3294 162 _cleanup_fclose_ FILE *f = NULL;
c279613f
LP
163 const char *unit;
164 char **p;
95ed3294
TA
165 int r;
166
c279613f
LP
167 assert(s);
168
169 if (!s->loaded)
170 return 0;
171
172 unit = strjoina(arg_dest, "/", s->name);
173
77354c7e
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 */
4e558983 177 if (is_symlink(unit) > 0) {
c279613f 178 log_warning("Overwriting existing symlink %s with real service.", unit);
9993ef2e 179 (void) unlink(unit);
77354c7e
MP
180 }
181
95ed3294 182 f = fopen(unit, "wxe");
4a62c710
MS
183 if (!f)
184 return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
95ed3294
TA
185
186 fprintf(f,
187 "# Automatically generated by systemd-sysv-generator\n\n"
188 "[Unit]\n"
aad0a2c8 189 "Documentation=man:systemd-sysv-generator(8)\n"
c279613f
LP
190 "SourcePath=%s\n",
191 s->path);
192
193 if (s->description)
194 fprintf(f, "Description=%s\n", s->description);
95ed3294 195
c584ffc0
LN
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);
95ed3294
TA
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
95ed3294 216 if (s->pid_file)
d171ed1c 217 fprintf(f, "PIDFile=%s\n", s->pid_file);
95ed3294
TA
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
c279613f
LP
227 r = fflush_and_check(f);
228 if (r < 0)
229 return log_error_errno(r, "Failed to write unit %s: %m", unit);
230
260ad50f 231 STRV_FOREACH(p, s->wanted_by) {
95ed3294
TA
232 r = add_symlink(s->name, *p);
233 if (r < 0)
c279613f 234 log_warning_errno(r, "Failed to create 'Wants' symlink to %s, ignoring: %m", *p);
95ed3294
TA
235 }
236
c279613f 237 return 1;
95ed3294
TA
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) {
0b2ec8a3
DH
250 _cleanup_free_ char *c = NULL;
251 char *res;
95ed3294 252
264581a2
FS
253 c = strdup(name);
254 if (!c)
0b2ec8a3 255 return NULL;
95ed3294 256
0b2ec8a3
DH
257 res = endswith(c, ".sh");
258 if (res)
259 *res = 0;
95ed3294 260
0b2ec8a3
DH
261 if (unit_name_mangle(c, UNIT_NAME_NOGLOB, &res) < 0)
262 return NULL;
263
264 return res;
95ed3294
TA
265}
266
c279613f 267static int sysv_translate_facility(const char *name, const char *filename, char **ret) {
95ed3294
TA
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
c279613f 286 char *filename_no_sh, *e, *m;
95ed3294 287 const char *n;
4e488555 288 unsigned i;
c279613f 289 int r;
95ed3294
TA
290
291 assert(name);
c279613f
LP
292 assert(filename);
293 assert(ret);
95ed3294
TA
294
295 n = *name == '$' ? name + 1 : name;
296
297 for (i = 0; i < ELEMENTSOF(table); i += 2) {
95ed3294
TA
298 if (!streq(table[i], n))
299 continue;
300
301 if (!table[i+1])
302 return 0;
303
c279613f
LP
304 m = strdup(table[i+1]);
305 if (!m)
95ed3294
TA
306 return log_oom();
307
c279613f
LP
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;
95ed3294
TA
322 }
323
c279613f 324 /* Strip ".sh" suffix from file name for comparison */
4e488555 325 filename_no_sh = strdupa(filename);
40780877 326 e = endswith(filename_no_sh, ".sh");
3315f085 327 if (e) {
4e488555 328 *e = '\0';
3315f085
LP
329 filename = filename_no_sh;
330 }
29e0e6d8 331
c279613f
LP
332 /* Names equaling the file name of the services are redundant */
333 if (streq_ptr(n, filename))
95ed3294 334 return 0;
95ed3294 335
c279613f
LP
336 /* Everything else we assume to be normal service names */
337 m = sysv_translate_name(n);
338 if (!m)
339 return log_oom();
95ed3294 340
c279613f 341 *ret = m;
95ed3294
TA
342 return 1;
343}
344
1e2fee5f 345static int handle_provides(SysvStub *s, unsigned line, const char *full_text, const char *text) {
1e2fee5f
ZJS
346 int r;
347
c279613f
LP
348 assert(s);
349 assert(full_text);
350 assert(text);
1e2fee5f 351
c279613f
LP
352 for (;;) {
353 _cleanup_free_ char *word = NULL, *m = NULL;
1e2fee5f 354
c279613f 355 r = extract_first_word(&text, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX);
1e2fee5f 356 if (r < 0)
c279613f 357 return log_error_errno(r, "Failed to parse word from provides string: %m");
1e2fee5f 358 if (r == 0)
c279613f
LP
359 break;
360
361 r = sysv_translate_facility(word, basename(s->path), &m);
362 if (r <= 0) /* continue on error */
1e2fee5f
ZJS
363 continue;
364
c279613f
LP
365 switch (unit_name_to_type(m)) {
366
367 case UNIT_SERVICE:
1e2fee5f
ZJS
368 log_debug("Adding Provides: alias '%s' for '%s'", m, s->name);
369 r = add_alias(s->name, m);
e987f2a8 370 if (r < 0)
f2341e0a 371 log_warning_errno(r, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s->path, line, m);
c279613f
LP
372 break;
373
374 case UNIT_TARGET:
375
1e2fee5f
ZJS
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! */
c279613f 381
1e2fee5f
ZJS
382 r = strv_extend(&s->before, m);
383 if (r < 0)
384 return log_oom();
c279613f 385
1e2fee5f
ZJS
386 r = strv_extend(&s->wants, m);
387 if (r < 0)
388 return log_oom();
c279613f 389
1e2fee5f
ZJS
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 }
c279613f
LP
395
396 break;
397
398 case _UNIT_TYPE_INVALID:
2c09a745 399 log_warning("Unit name '%s' is invalid", m);
c279613f
LP
400 break;
401
402 default:
2c09a745 403 log_warning("Unknown unit type for unit '%s'", m);
c279613f 404 }
1e2fee5f 405 }
c279613f 406
1e2fee5f
ZJS
407 return 0;
408}
409
410static int handle_dependencies(SysvStub *s, unsigned line, const char *full_text, const char *text) {
1e2fee5f
ZJS
411 int r;
412
c279613f
LP
413 assert(s);
414 assert(full_text);
415 assert(text);
1e2fee5f 416
c279613f
LP
417 for (;;) {
418 _cleanup_free_ char *word = NULL, *m = NULL;
419 bool is_before;
1e2fee5f 420
c279613f
LP
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");
1e2fee5f 424 if (r == 0)
c279613f
LP
425 break;
426
427 r = sysv_translate_facility(word, basename(s->path), &m);
428 if (r <= 0) /* continue on error */
1e2fee5f
ZJS
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();
c279613f 438
1e2fee5f 439 r = strv_extend(&s->wants, m);
e987f2a8 440 } else
1e2fee5f 441 r = strv_extend(is_before ? &s->before : &s->after, m);
1e2fee5f 442 if (r < 0)
e987f2a8 443 return log_oom();
1e2fee5f 444 }
c279613f 445
1e2fee5f
ZJS
446 return 0;
447}
448
95ed3294
TA
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;
c279613f 463 char l[LINE_MAX];
95ed3294
TA
464
465 assert(s);
466
467 f = fopen(s->path, "re");
c279613f
LP
468 if (!f) {
469 if (errno == ENOENT)
470 return 0;
95ed3294 471
c279613f
LP
472 return log_error_errno(errno, "Failed to open %s: %m", s->path);
473 }
77354c7e 474
c279613f 475 log_debug("Loading SysV script %s", s->path);
95ed3294 476
c279613f
LP
477 FOREACH_LINE(l, f, goto fail) {
478 char *t;
95ed3294
TA
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
c279613f 521 size_t k;
859fdd1e 522 const char *j;
95ed3294 523
c279613f
LP
524 k = strlen(t);
525 if (k > 0 && t[k-1] == '\\') {
95ed3294
TA
526 state = DESCRIPTION;
527 t[k-1] = 0;
528 }
529
530 j = strstrip(t+12);
c279613f
LP
531 if (isempty(j))
532 j = NULL;
95ed3294 533
c279613f
LP
534 r = free_and_strdup(&chkconfig_description, j);
535 if (r < 0)
536 return log_oom();
95ed3294
TA
537
538 } else if (startswith_no_case(t, "pidfile:")) {
c279613f 539 const char *fn;
95ed3294
TA
540
541 state = NORMAL;
542
543 fn = strstrip(t+8);
544 if (!path_is_absolute(fn)) {
f2341e0a 545 log_error("[%s:%u] PID file not absolute. Ignoring.", s->path, line);
95ed3294
TA
546 continue;
547 }
548
c279613f
LP
549 r = free_and_strdup(&s->pid_file, fn);
550 if (r < 0)
551 return log_oom();
95ed3294
TA
552 }
553
554 } else if (state == DESCRIPTION) {
555
556 /* Try to parse Red Hat style description
557 * continuation */
558
c279613f 559 size_t k;
95ed3294
TA
560 char *j;
561
c279613f
LP
562 k = strlen(t);
563 if (k > 0 && t[k-1] == '\\')
95ed3294
TA
564 t[k-1] = 0;
565 else
566 state = NORMAL;
567
568 j = strstrip(t);
c279613f 569 if (!isempty(j)) {
95ed3294
TA
570 char *d = NULL;
571
572 if (chkconfig_description)
573 d = strjoin(chkconfig_description, " ", j, NULL);
574 else
575 d = strdup(j);
95ed3294 576 if (!d)
c279613f 577 return log_oom();
95ed3294
TA
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:")) {
95ed3294
TA
586 state = LSB;
587
1e2fee5f
ZJS
588 r = handle_provides(s, line, t, t + 9);
589 if (r < 0)
590 return r;
c279613f 591
95ed3294
TA
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:")) {
95ed3294
TA
596
597 state = LSB;
598
1e2fee5f
ZJS
599 r = handle_dependencies(s, line, t, strchr(t, ':') + 1);
600 if (r < 0)
601 return r;
95ed3294 602
95ed3294 603 } else if (startswith_no_case(t, "Description:")) {
c279613f 604 const char *j;
95ed3294
TA
605
606 state = LSB_DESCRIPTION;
607
608 j = strstrip(t+12);
c279613f
LP
609 if (isempty(j))
610 j = NULL;
95ed3294 611
c279613f
LP
612 r = free_and_strdup(&long_description, j);
613 if (r < 0)
614 return log_oom();
95ed3294
TA
615
616 } else if (startswith_no_case(t, "Short-Description:")) {
c279613f 617 const char *j;
95ed3294
TA
618
619 state = LSB;
620
621 j = strstrip(t+18);
c279613f
LP
622 if (isempty(j))
623 j = NULL;
95ed3294 624
c279613f
LP
625 r = free_and_strdup(&short_description, j);
626 if (r < 0)
627 return log_oom();
95ed3294
TA
628
629 } else if (state == LSB_DESCRIPTION) {
630
631 if (startswith(l, "#\t") || startswith(l, "# ")) {
c279613f 632 const char *j;
95ed3294
TA
633
634 j = strstrip(t);
c279613f 635 if (!isempty(j)) {
95ed3294
TA
636 char *d = NULL;
637
638 if (long_description)
639 d = strjoin(long_description, " ", t, NULL);
640 else
641 d = strdup(j);
95ed3294 642 if (!d)
c279613f 643 return log_oom();
95ed3294
TA
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)
c279613f 674 return log_oom();
95ed3294
TA
675
676 s->description = d;
677 }
678
c279613f 679 s->loaded = true;
95ed3294 680 return 0;
c279613f
LP
681
682fail:
683 return log_error_errno(errno, "Failed to read configuration file '%s': %m", s->path);
95ed3294
TA
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
c279613f
LP
693 if (!s->loaded)
694 return 0;
695
95ed3294
TA
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
c279613f
LP
703 if (!other->loaded)
704 continue;
705
95ed3294
TA
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();
c279613f
LP
718
719 } else if (other->sysv_start_priority > s->sysv_start_priority) {
95ed3294
TA
720 r = strv_extend(&s->before, other->name);
721 if (r < 0)
722 return log_oom();
c279613f 723 } else
95ed3294
TA
724 continue;
725
726 /* FIXME: Maybe we should compare the name here lexicographically? */
727 }
728
729 return 0;
730}
731
a8ffe6fb 732static int enumerate_sysv(const LookupPaths *lp, Hashmap *all_services) {
95ed3294 733 char **path;
0ec0deaa 734 int r;
95ed3294 735
c279613f
LP
736 assert(lp);
737 assert(all_services);
738
a8ffe6fb 739 STRV_FOREACH(path, lp->sysvinit_path) {
95ed3294
TA
740 _cleanup_closedir_ DIR *d = NULL;
741 struct dirent *de;
742
743 d = opendir(*path);
744 if (!d) {
745 if (errno != ENOENT)
c279613f 746 log_warning_errno(errno, "Opening %s failed, ignoring: %m", *path);
95ed3294
TA
747 continue;
748 }
749
c279613f 750 FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", *path)) {
95ed3294 751 _cleanup_free_ char *fpath = NULL, *name = NULL;
8fba1c8d 752 _cleanup_(free_sysvstubp) SysvStub *service = NULL;
805e5dda 753 struct stat st;
95ed3294 754
7b729f86 755 if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) {
c279613f 756 log_warning_errno(errno, "stat() failed on %s/%s, ignoring: %m", *path, de->d_name);
95ed3294 757 continue;
805e5dda 758 }
95ed3294
TA
759
760 if (!(st.st_mode & S_IXUSR))
761 continue;
762
805e5dda
LP
763 if (!S_ISREG(st.st_mode))
764 continue;
765
95ed3294
TA
766 name = sysv_translate_name(de->d_name);
767 if (!name)
768 return log_oom();
769
a986501b
LP
770 if (hashmap_contains(all_services, name))
771 continue;
772
0ec0deaa
LP
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);
f4f01ec1
MP
779 continue;
780 }
781
c279613f
LP
782 fpath = strjoin(*path, "/", de->d_name, NULL);
783 if (!fpath)
784 return log_oom();
785
95ed3294
TA
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;
c279613f 793 name = fpath = NULL;
95ed3294
TA
794
795 r = hashmap_put(all_services, service->name, service);
805e5dda 796 if (r < 0)
95ed3294
TA
797 return log_oom();
798
805e5dda 799 service = NULL;
95ed3294
TA
800 }
801 }
802
803 return 0;
804}
805
a8ffe6fb 806static int set_dependencies_from_rcnd(const LookupPaths *lp, Hashmap *all_services) {
95ed3294
TA
807 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
808 _cleanup_set_free_ Set *shutdown_services = NULL;
c279613f
LP
809 SysvStub *service;
810 unsigned i;
811 Iterator j;
812 char **p;
813 int r;
95ed3294 814
c279613f
LP
815 assert(lp);
816
817 STRV_FOREACH(p, lp->sysvrcnd_path) {
95ed3294 818 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
c279613f
LP
819
820 _cleanup_closedir_ DIR *d = NULL;
821 _cleanup_free_ char *path = NULL;
95ed3294
TA
822 struct dirent *de;
823
95ed3294 824 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
c279613f
LP
825 if (!path) {
826 r = log_oom();
827 goto finish;
828 }
95ed3294
TA
829
830 d = opendir(path);
831 if (!d) {
832 if (errno != ENOENT)
c279613f 833 log_warning_errno(errno, "Opening %s failed, ignoring: %m", path);
95ed3294
TA
834
835 continue;
836 }
837
c279613f
LP
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;
95ed3294
TA
840 int a, b;
841
95ed3294
TA
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
95ed3294
TA
854 fpath = strjoin(*p, "/", de->d_name, NULL);
855 if (!fpath) {
c279613f 856 r = log_oom();
95ed3294
TA
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
8c84621c 866 service = hashmap_get(all_services, name);
9ed794a3 867 if (!service) {
c279613f 868 log_debug("Ignoring %s symlink in %s, not generating %s.", de->d_name, rcnd_table[i].path, name);
95ed3294
TA
869 continue;
870 }
871
872 if (de->d_name[0] == 'S') {
873
c279613f
LP
874 if (rcnd_table[i].type == RUNLEVEL_UP)
875 service->sysv_start_priority = MAX(a*10 + b, service->sysv_start_priority);
95ed3294 876
d5099efc 877 r = set_ensure_allocated(&runlevel_services[i], NULL);
c279613f
LP
878 if (r < 0) {
879 log_oom();
95ed3294 880 goto finish;
c279613f 881 }
95ed3294
TA
882
883 r = set_put(runlevel_services[i], service);
c279613f
LP
884 if (r < 0) {
885 log_oom();
95ed3294 886 goto finish;
c279613f 887 }
95ed3294
TA
888
889 } else if (de->d_name[0] == 'K' &&
890 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
891
d5099efc 892 r = set_ensure_allocated(&shutdown_services, NULL);
c279613f
LP
893 if (r < 0) {
894 log_oom();
95ed3294 895 goto finish;
c279613f 896 }
95ed3294
TA
897
898 r = set_put(shutdown_services, service);
c279613f
LP
899 if (r < 0) {
900 log_oom();
95ed3294 901 goto finish;
c279613f 902 }
95ed3294
TA
903 }
904 }
905 }
c279613f 906 }
95ed3294
TA
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);
c279613f
LP
912 if (r < 0) {
913 log_oom();
914 goto finish;
915 }
260ad50f 916 r = strv_extend(&service->wanted_by, rcnd_table[i].target);
c279613f
LP
917 if (r < 0) {
918 log_oom();
919 goto finish;
920 }
95ed3294
TA
921 }
922
923 SET_FOREACH(service, shutdown_services, j) {
924 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
c279613f
LP
925 if (r < 0) {
926 log_oom();
927 goto finish;
928 }
95ed3294 929 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
c279613f
LP
930 if (r < 0) {
931 log_oom();
932 goto finish;
933 }
95ed3294
TA
934 }
935
936 r = 0;
937
938finish:
95ed3294
TA
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[]) {
5921fc3c 946 _cleanup_(free_sysvstub_hashmapp) Hashmap *all_services = NULL;
c279613f 947 _cleanup_lookup_paths_free_ LookupPaths lp = {};
95ed3294
TA
948 SysvStub *service;
949 Iterator j;
c279613f 950 int r;
95ed3294
TA
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
b2c23da8 966 r = lookup_paths_init(&lp, MANAGER_SYSTEM, true, NULL, NULL, NULL, NULL);
95ed3294 967 if (r < 0) {
c279613f
LP
968 log_error_errno(r, "Failed to find lookup paths: %m");
969 goto finish;
95ed3294
TA
970 }
971
d5099efc 972 all_services = hashmap_new(&string_hash_ops);
95ed3294 973 if (!all_services) {
c279613f
LP
974 r = log_oom();
975 goto finish;
95ed3294
TA
976 }
977
a8ffe6fb 978 r = enumerate_sysv(&lp, all_services);
c279613f
LP
979 if (r < 0)
980 goto finish;
95ed3294 981
a8ffe6fb 982 r = set_dependencies_from_rcnd(&lp, all_services);
c279613f
LP
983 if (r < 0)
984 goto finish;
95ed3294 985
c279613f
LP
986 HASHMAP_FOREACH(service, all_services, j)
987 (void) load_sysv(service);
b3fae863 988
95ed3294 989 HASHMAP_FOREACH(service, all_services, j) {
c279613f
LP
990 (void) fix_order(service, all_services);
991 (void) generate_unit_file(service);
95ed3294
TA
992 }
993
c279613f
LP
994 r = 0;
995
996finish:
997 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
95ed3294 998}