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