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