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