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