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"
36 #include "unit-name.h"
38 #include "exit-status.h"
44 typedef enum RunlevelType {
52 const RunlevelType type;
54 /* Standard SysV runlevels for start-up */
55 { "rc1.d", SPECIAL_RESCUE_TARGET, RUNLEVEL_UP },
56 { "rc2.d", SPECIAL_RUNLEVEL2_TARGET, RUNLEVEL_UP },
57 { "rc3.d", SPECIAL_RUNLEVEL3_TARGET, RUNLEVEL_UP },
58 { "rc4.d", SPECIAL_RUNLEVEL4_TARGET, RUNLEVEL_UP },
59 { "rc5.d", SPECIAL_RUNLEVEL5_TARGET, RUNLEVEL_UP },
61 /* Standard SysV runlevels for shutdown */
62 { "rc0.d", SPECIAL_POWEROFF_TARGET, RUNLEVEL_DOWN },
63 { "rc6.d", SPECIAL_REBOOT_TARGET, RUNLEVEL_DOWN }
65 /* Note that the order here matters, as we read the
66 directories in this order, and we want to make sure that
67 sysv_start_priority is known when we first load the
68 unit. And that value we only know from S links. Hence
69 UP must be read before DOWN */
72 typedef struct SysvStub {
76 int sysv_start_priority;
87 const char *arg_dest = "/tmp";
89 static int add_symlink(const char *service, const char *where) {
90 _cleanup_free_ char *from = NULL, *to = NULL;
96 from = strjoin(arg_dest, "/", service, NULL);
100 to = strjoin(arg_dest, "/", where, ".wants/", service, NULL);
104 mkdir_parents_label(to, 0755);
106 r = symlink(from, to);
116 static int generate_unit_file(SysvStub *s) {
119 _cleanup_fclose_ FILE *f = NULL;
120 _cleanup_free_ char *before = NULL;
121 _cleanup_free_ char *after = NULL;
122 _cleanup_free_ char *wants = NULL;
123 _cleanup_free_ char *conflicts = NULL;
126 before = strv_join(s->before, " ");
130 after = strv_join(s->after, " ");
134 wants = strv_join(s->wants, " ");
138 conflicts = strv_join(s->conflicts, " ");
142 unit = strjoin(arg_dest, "/", s->name, NULL);
146 f = fopen(unit, "wxe");
148 log_error("Failed to create unit file %s: %m", unit);
153 "# Automatically generated by systemd-sysv-generator\n\n"
157 s->path, s->description);
159 if (!isempty(before))
160 fprintf(f, "Before=%s\n", before);
162 fprintf(f, "After=%s\n", after);
164 fprintf(f, "Wants=%s\n", wants);
165 if (!isempty(conflicts))
166 fprintf(f, "Conflicts=%s\n", conflicts);
176 "RemainAfterExit=%s\n",
177 yes_no(!s->pid_file));
179 if (s->sysv_start_priority > 0)
180 fprintf(f, "SysVStartPriority=%d\n", s->sysv_start_priority);
183 fprintf(f, "PIDFile=%s\n", s->pid_file);
186 "ExecStart=%s start\n"
187 "ExecStop=%s stop\n",
191 fprintf(f, "ExecReload=%s reload\n", s->path);
193 STRV_FOREACH(p, s->wanted_by) {
194 r = add_symlink(s->name, *p);
196 log_error_unit(s->name, "Failed to create 'Wants' symlink to %s: %s", *p, strerror(-r));
202 static 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\""));
211 static char *sysv_translate_name(const char *name) {
214 r = new(char, strlen(name) + strlen(".service") + 1);
218 if (endswith(name, ".sh"))
219 /* Drop .sh suffix */
220 strcpy(stpcpy(r, name) - 3, ".service");
222 /* Normal init script name */
223 strcpy(stpcpy(r, name), ".service");
228 static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
230 /* We silently ignore the $ prefix here. According to the LSB
231 * spec it simply indicates whether something is a
232 * standardized name or a distribution-specific one. Since we
233 * just follow what already exists and do not introduce new
234 * uses or names we don't care who introduced a new name. */
236 static const char * const table[] = {
237 /* LSB defined facilities */
239 "network", SPECIAL_NETWORK_ONLINE_TARGET,
240 "named", SPECIAL_NSS_LOOKUP_TARGET,
241 "portmap", SPECIAL_RPCBIND_TARGET,
242 "remote_fs", SPECIAL_REMOTE_FS_TARGET,
244 "time", SPECIAL_TIME_SYNC_TARGET,
254 n = *name == '$' ? name + 1 : name;
256 for (i = 0; i < ELEMENTSOF(table); i += 2) {
258 if (!streq(table[i], n))
264 r = strdup(table[i+1]);
271 /* If we don't know this name, fallback heuristics to figure
272 * out whether something is a target or a service alias. */
275 if (!unit_prefix_is_valid(n))
278 /* Facilities starting with $ are most likely targets */
279 r = unit_name_build(n, NULL, ".target");
280 } else if (filename && streq(name, filename))
281 /* Names equaling the file name of the services are redundant */
284 /* Everything else we assume to be normal service names */
285 r = sysv_translate_name(n);
296 static int load_sysv(SysvStub *s) {
297 _cleanup_fclose_ FILE *f;
307 _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
309 bool supports_reload = false;
313 f = fopen(s->path, "re");
315 return errno == ENOENT ? 0 : -errno;
318 char l[LINE_MAX], *t;
320 if (!fgets(l, sizeof(l), f)) {
324 log_error_unit(s->name,
325 "Failed to read configuration file '%s': %m",
334 /* Try to figure out whether this init script supports
335 * the reload operation. This heuristic looks for
336 * "Usage" lines which include the reload option. */
337 if ( state == USAGE_CONTINUATION ||
338 (state == NORMAL && strcasestr(t, "usage"))) {
339 if (usage_contains_reload(t)) {
340 supports_reload = true;
342 } else if (t[strlen(t)-1] == '\\')
343 state = USAGE_CONTINUATION;
351 if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
357 if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
363 t += strspn(t, WHITESPACE);
365 if (state == NORMAL) {
367 /* Try to parse Red Hat style description */
369 if (startswith_no_case(t, "description:")) {
371 size_t k = strlen(t);
375 if (t[k-1] == '\\') {
388 free(chkconfig_description);
389 chkconfig_description = d;
391 } else if (startswith_no_case(t, "pidfile:")) {
398 if (!path_is_absolute(fn)) {
399 log_error_unit(s->name,
400 "[%s:%u] PID file not absolute. Ignoring.",
413 } else if (state == DESCRIPTION) {
415 /* Try to parse Red Hat style description
418 size_t k = strlen(t);
430 if (chkconfig_description)
431 d = strjoin(chkconfig_description, " ", j, NULL);
438 free(chkconfig_description);
439 chkconfig_description = d;
442 } else if (state == LSB || state == LSB_DESCRIPTION) {
444 if (startswith_no_case(t, "Provides:")) {
450 FOREACH_WORD_QUOTED(w, z, t+9, i) {
451 _cleanup_free_ char *n = NULL, *m = NULL;
457 r = sysv_translate_facility(n, basename(s->path), &m);
465 if (unit_name_to_type(m) != UNIT_SERVICE) {
471 * indication that the
473 * now available. This
476 * targets do NOT pull
479 r = strv_extend(&s->before, m);
482 r = strv_extend(&s->wants, m);
488 log_error_unit(s->name,
489 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
490 s->path, line, m, strerror(-r));
493 } else if (startswith_no_case(t, "Required-Start:") ||
494 startswith_no_case(t, "Should-Start:") ||
495 startswith_no_case(t, "X-Start-Before:") ||
496 startswith_no_case(t, "X-Start-After:")) {
502 FOREACH_WORD_QUOTED(w, z, strchr(t, ':')+1, i) {
503 _cleanup_free_ char *n = NULL, *m = NULL;
510 r = sysv_translate_facility(n, basename(s->path), &m);
512 log_error_unit(s->name,
513 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
514 s->path, line, n, strerror(-r));
521 is_before = startswith_no_case(t, "X-Start-Before:");
523 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
524 /* the network-online target is special, as it needs to be actively pulled in */
525 r = strv_extend(&s->after, m);
528 r = strv_extend(&s->wants, m);
534 r = strv_extend(&s->before, m);
539 r = strv_extend(&s->after, m);
546 log_error_unit(s->name,
547 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
548 s->path, line, m, strerror(-r));
551 } else if (startswith_no_case(t, "Description:")) {
554 state = LSB_DESCRIPTION;
564 free(long_description);
565 long_description = d;
567 } else if (startswith_no_case(t, "Short-Description:")) {
580 free(short_description);
581 short_description = d;
583 } else if (state == LSB_DESCRIPTION) {
585 if (startswith(l, "#\t") || startswith(l, "# ")) {
592 if (long_description)
593 d = strjoin(long_description, " ", t, NULL);
600 free(long_description);
601 long_description = d;
610 s->reload = supports_reload;
612 /* We use the long description only if
613 * no short description is set. */
615 if (short_description)
616 description = short_description;
617 else if (chkconfig_description)
618 description = chkconfig_description;
619 else if (long_description)
620 description = long_description;
627 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
637 static int fix_order(SysvStub *s, Hashmap *all_services) {
644 if (s->sysv_start_priority < 0)
647 HASHMAP_FOREACH(other, all_services, j) {
651 if (other->sysv_start_priority < 0)
654 /* If both units have modern headers we don't care
655 * about the priorities */
656 if (s->has_lsb && other->has_lsb)
659 if (other->sysv_start_priority < s->sysv_start_priority) {
660 r = strv_extend(&s->after, other->name);
664 else if (other->sysv_start_priority > s->sysv_start_priority) {
665 r = strv_extend(&s->before, other->name);
672 /* FIXME: Maybe we should compare the name here lexicographically? */
678 static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
681 STRV_FOREACH(path, lp.sysvinit_path) {
682 _cleanup_closedir_ DIR *d = NULL;
688 log_warning("opendir(%s) failed: %m", *path);
692 while ((de = readdir(d))) {
695 _cleanup_free_ char *fpath = NULL, *name = NULL;
698 if (ignore_file(de->d_name))
701 fpath = strjoin(*path, "/", de->d_name, NULL);
705 if (stat(fpath, &st) < 0)
708 if (!(st.st_mode & S_IXUSR))
711 name = sysv_translate_name(de->d_name);
715 if (hashmap_contains(all_services, name))
718 service = new0(SysvStub, 1);
722 service->sysv_start_priority = -1;
723 service->name = name;
724 service->path = fpath;
726 r = hashmap_put(all_services, service->name, service);
737 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
740 _cleanup_closedir_ DIR *d = NULL;
741 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
744 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
745 _cleanup_set_free_ Set *shutdown_services = NULL;
748 STRV_FOREACH(p, lp.sysvrcnd_path)
749 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
753 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
763 log_warning("opendir(%s) failed: %m", path);
768 while ((de = readdir(d))) {
771 if (ignore_file(de->d_name))
774 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
777 if (strlen(de->d_name) < 4)
780 a = undecchar(de->d_name[1]);
781 b = undecchar(de->d_name[2]);
787 fpath = strjoin(*p, "/", de->d_name, NULL);
793 name = sysv_translate_name(de->d_name + 3);
799 if (hashmap_contains(all_services, name))
800 service = hashmap_get(all_services, name);
802 log_warning("Could not find init script for %s", name);
806 if (de->d_name[0] == 'S') {
808 if (rcnd_table[i].type == RUNLEVEL_UP) {
809 service->sysv_start_priority =
810 MAX(a*10 + b, service->sysv_start_priority);
813 r = set_ensure_allocated(&runlevel_services[i],
814 trivial_hash_func, trivial_compare_func);
818 r = set_put(runlevel_services[i], service);
822 } else if (de->d_name[0] == 'K' &&
823 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
825 r = set_ensure_allocated(&shutdown_services,
826 trivial_hash_func, trivial_compare_func);
830 r = set_put(shutdown_services, service);
838 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
839 SET_FOREACH(service, runlevel_services[i], j) {
840 r = strv_extend(&service->before, rcnd_table[i].target);
843 r = strv_extend(&service->wanted_by, rcnd_table[i].target);
848 SET_FOREACH(service, shutdown_services, j) {
849 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
852 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
861 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
862 set_free(runlevel_services[i]);
867 int main(int argc, char *argv[]) {
870 Hashmap *all_services;
874 if (argc > 1 && argc != 4) {
875 log_error("This program takes three or no arguments.");
882 log_set_target(LOG_TARGET_SAFE);
883 log_parse_environment();
888 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
890 log_error("Failed to find lookup paths.");
894 all_services = hashmap_new(string_hash_func, string_compare_func);
900 r = enumerate_sysv(lp, all_services);
902 log_error("Failed to generate units for all init scripts.");
906 r = set_dependencies_from_rcnd(lp, all_services);
908 log_error("Failed to read runlevels from rcnd links.");
912 HASHMAP_FOREACH(service, all_services, j) {
913 q = load_sysv(service);
917 q = fix_order(service, all_services);
921 q = generate_unit_file(service);