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