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:")) {
445 const char *word, *state_;
450 FOREACH_WORD_QUOTED(word, z, t+9, state_) {
451 _cleanup_free_ char *n = NULL, *m = NULL;
453 n = strndup(word, z);
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);
485 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) {
486 r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET);
493 log_error_unit(s->name,
494 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
495 s->path, line, m, strerror(-r));
497 if (!isempty(state_))
498 log_error_unit(s->name,
499 "[%s:%u] Trailing garbage in Provides, ignoring.",
502 } else if (startswith_no_case(t, "Required-Start:") ||
503 startswith_no_case(t, "Should-Start:") ||
504 startswith_no_case(t, "X-Start-Before:") ||
505 startswith_no_case(t, "X-Start-After:")) {
506 const char *word, *state_;
511 FOREACH_WORD_QUOTED(word, z, strchr(t, ':')+1, state_) {
512 _cleanup_free_ char *n = NULL, *m = NULL;
515 n = strndup(word, z);
519 r = sysv_translate_facility(n, basename(s->path), &m);
521 log_error_unit(s->name,
522 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
523 s->path, line, n, strerror(-r));
530 is_before = startswith_no_case(t, "X-Start-Before:");
532 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
533 /* the network-online target is special, as it needs to be actively pulled in */
534 r = strv_extend(&s->after, m);
537 r = strv_extend(&s->wants, m);
543 r = strv_extend(&s->before, m);
548 r = strv_extend(&s->after, m);
555 log_error_unit(s->name,
556 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
557 s->path, line, m, strerror(-r));
559 if (!isempty(state_))
560 log_error_unit(s->name,
561 "[%s:%u] Trailing garbage in %*s, ignoring.",
563 (int)(strchr(t, ':') - t), t);
565 } else if (startswith_no_case(t, "Description:")) {
568 state = LSB_DESCRIPTION;
578 free(long_description);
579 long_description = d;
581 } else if (startswith_no_case(t, "Short-Description:")) {
594 free(short_description);
595 short_description = d;
597 } else if (state == LSB_DESCRIPTION) {
599 if (startswith(l, "#\t") || startswith(l, "# ")) {
606 if (long_description)
607 d = strjoin(long_description, " ", t, NULL);
614 free(long_description);
615 long_description = d;
624 s->reload = supports_reload;
626 /* We use the long description only if
627 * no short description is set. */
629 if (short_description)
630 description = short_description;
631 else if (chkconfig_description)
632 description = chkconfig_description;
633 else if (long_description)
634 description = long_description;
641 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
651 static int fix_order(SysvStub *s, Hashmap *all_services) {
658 if (s->sysv_start_priority < 0)
661 HASHMAP_FOREACH(other, all_services, j) {
665 if (other->sysv_start_priority < 0)
668 /* If both units have modern headers we don't care
669 * about the priorities */
670 if (s->has_lsb && other->has_lsb)
673 if (other->sysv_start_priority < s->sysv_start_priority) {
674 r = strv_extend(&s->after, other->name);
678 else if (other->sysv_start_priority > s->sysv_start_priority) {
679 r = strv_extend(&s->before, other->name);
686 /* FIXME: Maybe we should compare the name here lexicographically? */
692 static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
695 STRV_FOREACH(path, lp.sysvinit_path) {
696 _cleanup_closedir_ DIR *d = NULL;
702 log_warning("opendir(%s) failed: %m", *path);
706 while ((de = readdir(d))) {
709 _cleanup_free_ char *fpath = NULL, *name = NULL;
712 if (ignore_file(de->d_name))
715 fpath = strjoin(*path, "/", de->d_name, NULL);
719 if (stat(fpath, &st) < 0)
722 if (!(st.st_mode & S_IXUSR))
725 name = sysv_translate_name(de->d_name);
729 if (hashmap_contains(all_services, name))
732 service = new0(SysvStub, 1);
736 service->sysv_start_priority = -1;
737 service->name = name;
738 service->path = fpath;
740 r = hashmap_put(all_services, service->name, service);
751 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
754 _cleanup_closedir_ DIR *d = NULL;
755 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
758 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
759 _cleanup_set_free_ Set *shutdown_services = NULL;
762 STRV_FOREACH(p, lp.sysvrcnd_path)
763 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
767 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
777 log_warning("opendir(%s) failed: %m", path);
782 while ((de = readdir(d))) {
785 if (ignore_file(de->d_name))
788 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
791 if (strlen(de->d_name) < 4)
794 a = undecchar(de->d_name[1]);
795 b = undecchar(de->d_name[2]);
801 fpath = strjoin(*p, "/", de->d_name, NULL);
807 name = sysv_translate_name(de->d_name + 3);
813 if (hashmap_contains(all_services, name))
814 service = hashmap_get(all_services, name);
816 log_warning("Could not find init script for %s", name);
820 if (de->d_name[0] == 'S') {
822 if (rcnd_table[i].type == RUNLEVEL_UP) {
823 service->sysv_start_priority =
824 MAX(a*10 + b, service->sysv_start_priority);
827 r = set_ensure_allocated(&runlevel_services[i],
828 trivial_hash_func, trivial_compare_func);
832 r = set_put(runlevel_services[i], service);
836 } else if (de->d_name[0] == 'K' &&
837 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
839 r = set_ensure_allocated(&shutdown_services,
840 trivial_hash_func, trivial_compare_func);
844 r = set_put(shutdown_services, service);
852 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
853 SET_FOREACH(service, runlevel_services[i], j) {
854 r = strv_extend(&service->before, rcnd_table[i].target);
857 r = strv_extend(&service->wanted_by, rcnd_table[i].target);
862 SET_FOREACH(service, shutdown_services, j) {
863 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
866 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
875 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
876 set_free(runlevel_services[i]);
881 int main(int argc, char *argv[]) {
884 Hashmap *all_services;
888 if (argc > 1 && argc != 4) {
889 log_error("This program takes three or no arguments.");
896 log_set_target(LOG_TARGET_SAFE);
897 log_parse_environment();
902 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
904 log_error("Failed to find lookup paths.");
908 all_services = hashmap_new(string_hash_func, string_compare_func);
914 r = enumerate_sysv(lp, all_services);
916 log_error("Failed to generate units for all init scripts.");
920 r = set_dependencies_from_rcnd(lp, all_services);
922 log_error("Failed to read runlevels from rcnd links.");
926 HASHMAP_FOREACH(service, all_services, j) {
927 q = load_sysv(service);
931 q = fix_order(service, all_services);
935 q = generate_unit_file(service);