1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Thomas H.P. Andersen
7 Copyright 2010 Lennart Poettering
8 Copyright 2011 Michal Schmidt
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.
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.
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/>.
31 #include "path-util.h"
32 #include "path-lookup.h"
35 #include "unit-name.h"
37 #include "exit-status.h"
43 typedef enum RunlevelType {
51 const RunlevelType type;
53 /* Standard SysV runlevels for start-up */
54 { "rc1.d", SPECIAL_RESCUE_TARGET, RUNLEVEL_UP },
55 { "rc2.d", SPECIAL_RUNLEVEL2_TARGET, RUNLEVEL_UP },
56 { "rc3.d", SPECIAL_RUNLEVEL3_TARGET, RUNLEVEL_UP },
57 { "rc4.d", SPECIAL_RUNLEVEL4_TARGET, RUNLEVEL_UP },
58 { "rc5.d", SPECIAL_RUNLEVEL5_TARGET, RUNLEVEL_UP },
60 /* Standard SysV runlevels for shutdown */
61 { "rc0.d", SPECIAL_POWEROFF_TARGET, RUNLEVEL_DOWN },
62 { "rc6.d", SPECIAL_REBOOT_TARGET, RUNLEVEL_DOWN }
64 /* Note that the order here matters, as we read the
65 directories in this order, and we want to make sure that
66 sysv_start_priority is known when we first load the
67 unit. And that value we only know from S links. Hence
68 UP must be read before DOWN */
71 typedef struct SysvStub {
75 int sysv_start_priority;
86 const char *arg_dest = "/tmp";
88 static int add_symlink(const char *service, const char *where) {
89 _cleanup_free_ char *from = NULL, *to = NULL;
95 from = strjoin(arg_dest, "/", service, NULL);
99 to = strjoin(arg_dest, "/", where, ".wants/", service, NULL);
103 mkdir_parents_label(to, 0755);
105 r = symlink(from, to);
115 static int generate_unit_file(SysvStub *s) {
117 _cleanup_fclose_ FILE *f = NULL;
118 _cleanup_free_ char *unit = NULL;
119 _cleanup_free_ char *before = NULL;
120 _cleanup_free_ char *after = NULL;
121 _cleanup_free_ char *wants = NULL;
122 _cleanup_free_ char *conflicts = NULL;
125 before = strv_join(s->before, " ");
129 after = strv_join(s->after, " ");
133 wants = strv_join(s->wants, " ");
137 conflicts = strv_join(s->conflicts, " ");
141 unit = strjoin(arg_dest, "/", s->name, NULL);
145 f = fopen(unit, "wxe");
147 log_error("Failed to create unit file %s: %m", unit);
152 "# Automatically generated by systemd-sysv-generator\n\n"
156 s->path, s->description);
158 if (!isempty(before))
159 fprintf(f, "Before=%s\n", before);
161 fprintf(f, "After=%s\n", after);
163 fprintf(f, "Wants=%s\n", wants);
164 if (!isempty(conflicts))
165 fprintf(f, "Conflicts=%s\n", conflicts);
175 "RemainAfterExit=%s\n",
176 yes_no(!s->pid_file));
178 if (s->sysv_start_priority > 0)
179 fprintf(f, "SysVStartPriority=%d\n", s->sysv_start_priority);
182 fprintf(f, "PIDFile=%s\n", s->pid_file);
185 "ExecStart=%s start\n"
186 "ExecStop=%s stop\n",
190 fprintf(f, "ExecReload=%s reload\n", s->path);
192 STRV_FOREACH(p, s->wanted_by) {
193 r = add_symlink(s->name, *p);
195 log_error_unit(s->name, "Failed to create 'Wants' symlink to %s: %s", *p, strerror(-r));
201 static bool usage_contains_reload(const char *line) {
202 return (strcasestr(line, "{reload|") ||
203 strcasestr(line, "{reload}") ||
204 strcasestr(line, "{reload\"") ||
205 strcasestr(line, "|reload|") ||
206 strcasestr(line, "|reload}") ||
207 strcasestr(line, "|reload\""));
210 static char *sysv_translate_name(const char *name) {
213 r = new(char, strlen(name) + strlen(".service") + 1);
217 if (endswith(name, ".sh"))
218 /* Drop .sh suffix */
219 strcpy(stpcpy(r, name) - 3, ".service");
221 /* Normal init script name */
222 strcpy(stpcpy(r, name), ".service");
227 static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
229 /* We silently ignore the $ prefix here. According to the LSB
230 * spec it simply indicates whether something is a
231 * standardized name or a distribution-specific one. Since we
232 * just follow what already exists and do not introduce new
233 * uses or names we don't care who introduced a new name. */
235 static const char * const table[] = {
236 /* LSB defined facilities */
238 "network", SPECIAL_NETWORK_ONLINE_TARGET,
239 "named", SPECIAL_NSS_LOOKUP_TARGET,
240 "portmap", SPECIAL_RPCBIND_TARGET,
241 "remote_fs", SPECIAL_REMOTE_FS_TARGET,
243 "time", SPECIAL_TIME_SYNC_TARGET,
253 n = *name == '$' ? name + 1 : name;
255 for (i = 0; i < ELEMENTSOF(table); i += 2) {
257 if (!streq(table[i], n))
263 r = strdup(table[i+1]);
270 /* If we don't know this name, fallback heuristics to figure
271 * out whether something is a target or a service alias. */
274 if (!unit_prefix_is_valid(n))
277 /* Facilities starting with $ are most likely targets */
278 r = unit_name_build(n, NULL, ".target");
279 } else if (filename && streq(name, filename))
280 /* Names equaling the file name of the services are redundant */
283 /* Everything else we assume to be normal service names */
284 r = sysv_translate_name(n);
295 static int load_sysv(SysvStub *s) {
296 _cleanup_fclose_ FILE *f;
306 _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
308 bool supports_reload = false;
312 f = fopen(s->path, "re");
314 return errno == ENOENT ? 0 : -errno;
317 char l[LINE_MAX], *t;
319 if (!fgets(l, sizeof(l), f)) {
323 log_error_unit(s->name,
324 "Failed to read configuration file '%s': %m",
333 /* Try to figure out whether this init script supports
334 * the reload operation. This heuristic looks for
335 * "Usage" lines which include the reload option. */
336 if ( state == USAGE_CONTINUATION ||
337 (state == NORMAL && strcasestr(t, "usage"))) {
338 if (usage_contains_reload(t)) {
339 supports_reload = true;
341 } else if (t[strlen(t)-1] == '\\')
342 state = USAGE_CONTINUATION;
350 if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
356 if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
362 t += strspn(t, WHITESPACE);
364 if (state == NORMAL) {
366 /* Try to parse Red Hat style description */
368 if (startswith_no_case(t, "description:")) {
370 size_t k = strlen(t);
374 if (t[k-1] == '\\') {
387 free(chkconfig_description);
388 chkconfig_description = d;
390 } else if (startswith_no_case(t, "pidfile:")) {
397 if (!path_is_absolute(fn)) {
398 log_error_unit(s->name,
399 "[%s:%u] PID file not absolute. Ignoring.",
412 } else if (state == DESCRIPTION) {
414 /* Try to parse Red Hat style description
417 size_t k = strlen(t);
429 if (chkconfig_description)
430 d = strjoin(chkconfig_description, " ", j, NULL);
437 free(chkconfig_description);
438 chkconfig_description = d;
441 } else if (state == LSB || state == LSB_DESCRIPTION) {
443 if (startswith_no_case(t, "Provides:")) {
444 const char *word, *state_;
449 FOREACH_WORD_QUOTED(word, z, t+9, state_) {
450 _cleanup_free_ char *n = NULL, *m = NULL;
452 n = strndup(word, z);
456 r = sysv_translate_facility(n, basename(s->path), &m);
464 if (unit_name_to_type(m) != UNIT_SERVICE) {
470 * indication that the
472 * now available. This
475 * targets do NOT pull
478 r = strv_extend(&s->before, m);
481 r = strv_extend(&s->wants, m);
484 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) {
485 r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET);
492 log_error_unit(s->name,
493 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
494 s->path, line, m, strerror(-r));
496 if (!isempty(state_))
497 log_error_unit(s->name,
498 "[%s:%u] Trailing garbage in Provides, ignoring.",
501 } else if (startswith_no_case(t, "Required-Start:") ||
502 startswith_no_case(t, "Should-Start:") ||
503 startswith_no_case(t, "X-Start-Before:") ||
504 startswith_no_case(t, "X-Start-After:")) {
505 const char *word, *state_;
510 FOREACH_WORD_QUOTED(word, z, strchr(t, ':')+1, state_) {
511 _cleanup_free_ char *n = NULL, *m = NULL;
514 n = strndup(word, z);
518 r = sysv_translate_facility(n, basename(s->path), &m);
520 log_error_unit(s->name,
521 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
522 s->path, line, n, strerror(-r));
529 is_before = startswith_no_case(t, "X-Start-Before:");
531 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
532 /* the network-online target is special, as it needs to be actively pulled in */
533 r = strv_extend(&s->after, m);
536 r = strv_extend(&s->wants, m);
542 r = strv_extend(&s->before, m);
547 r = strv_extend(&s->after, m);
554 log_error_unit(s->name,
555 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
556 s->path, line, m, strerror(-r));
558 if (!isempty(state_))
559 log_error_unit(s->name,
560 "[%s:%u] Trailing garbage in %*s, ignoring.",
562 (int)(strchr(t, ':') - t), t);
564 } else if (startswith_no_case(t, "Description:")) {
567 state = LSB_DESCRIPTION;
577 free(long_description);
578 long_description = d;
580 } else if (startswith_no_case(t, "Short-Description:")) {
593 free(short_description);
594 short_description = d;
596 } else if (state == LSB_DESCRIPTION) {
598 if (startswith(l, "#\t") || startswith(l, "# ")) {
605 if (long_description)
606 d = strjoin(long_description, " ", t, NULL);
613 free(long_description);
614 long_description = d;
623 s->reload = supports_reload;
625 /* We use the long description only if
626 * no short description is set. */
628 if (short_description)
629 description = short_description;
630 else if (chkconfig_description)
631 description = chkconfig_description;
632 else if (long_description)
633 description = long_description;
640 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
650 static int fix_order(SysvStub *s, Hashmap *all_services) {
657 if (s->sysv_start_priority < 0)
660 HASHMAP_FOREACH(other, all_services, j) {
664 if (other->sysv_start_priority < 0)
667 /* If both units have modern headers we don't care
668 * about the priorities */
669 if (s->has_lsb && other->has_lsb)
672 if (other->sysv_start_priority < s->sysv_start_priority) {
673 r = strv_extend(&s->after, other->name);
677 else if (other->sysv_start_priority > s->sysv_start_priority) {
678 r = strv_extend(&s->before, other->name);
685 /* FIXME: Maybe we should compare the name here lexicographically? */
691 static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
694 STRV_FOREACH(path, lp.sysvinit_path) {
695 _cleanup_closedir_ DIR *d = NULL;
701 log_warning("opendir(%s) failed: %m", *path);
705 while ((de = readdir(d))) {
708 _cleanup_free_ char *fpath = NULL, *name = NULL;
711 if (ignore_file(de->d_name))
714 fpath = strjoin(*path, "/", de->d_name, NULL);
718 if (stat(fpath, &st) < 0)
721 if (!(st.st_mode & S_IXUSR))
724 name = sysv_translate_name(de->d_name);
728 if (hashmap_contains(all_services, name))
731 service = new0(SysvStub, 1);
735 service->sysv_start_priority = -1;
736 service->name = name;
737 service->path = fpath;
739 r = hashmap_put(all_services, service->name, service);
750 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
753 _cleanup_closedir_ DIR *d = NULL;
754 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
757 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
758 _cleanup_set_free_ Set *shutdown_services = NULL;
761 STRV_FOREACH(p, lp.sysvrcnd_path)
762 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
766 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
776 log_warning("opendir(%s) failed: %m", path);
781 while ((de = readdir(d))) {
784 if (ignore_file(de->d_name))
787 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
790 if (strlen(de->d_name) < 4)
793 a = undecchar(de->d_name[1]);
794 b = undecchar(de->d_name[2]);
800 fpath = strjoin(*p, "/", de->d_name, NULL);
806 name = sysv_translate_name(de->d_name + 3);
812 service = hashmap_get(all_services, name);
814 log_warning("Could not find init script for %s", name);
818 if (de->d_name[0] == 'S') {
820 if (rcnd_table[i].type == RUNLEVEL_UP) {
821 service->sysv_start_priority =
822 MAX(a*10 + b, service->sysv_start_priority);
825 r = set_ensure_allocated(&runlevel_services[i], NULL);
829 r = set_put(runlevel_services[i], service);
833 } else if (de->d_name[0] == 'K' &&
834 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
836 r = set_ensure_allocated(&shutdown_services, NULL);
840 r = set_put(shutdown_services, service);
848 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
849 SET_FOREACH(service, runlevel_services[i], j) {
850 r = strv_extend(&service->before, rcnd_table[i].target);
853 r = strv_extend(&service->wanted_by, rcnd_table[i].target);
858 SET_FOREACH(service, shutdown_services, j) {
859 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
862 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
871 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
872 set_free(runlevel_services[i]);
877 int main(int argc, char *argv[]) {
880 Hashmap *all_services;
884 if (argc > 1 && argc != 4) {
885 log_error("This program takes three or no arguments.");
892 log_set_target(LOG_TARGET_SAFE);
893 log_parse_environment();
898 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
900 log_error("Failed to find lookup paths.");
904 all_services = hashmap_new(&string_hash_ops);
910 r = enumerate_sysv(lp, all_services);
912 log_error("Failed to generate units for all init scripts.");
916 r = set_dependencies_from_rcnd(lp, all_services);
918 log_error("Failed to read runlevels from rcnd links.");
922 HASHMAP_FOREACH(service, all_services, j) {
923 q = load_sysv(service);
927 q = fix_order(service, all_services);
931 q = generate_unit_file(service);