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