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