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 add_alias(const char *service, const char *alias) {
116 _cleanup_free_ char *link = NULL;
122 link = strjoin(arg_dest, "/", alias, NULL);
126 r = symlink(service, link);
136 static int generate_unit_file(SysvStub *s) {
138 _cleanup_fclose_ FILE *f = NULL;
139 _cleanup_free_ char *unit = NULL;
140 _cleanup_free_ char *before = NULL;
141 _cleanup_free_ char *after = NULL;
142 _cleanup_free_ char *wants = NULL;
143 _cleanup_free_ char *conflicts = NULL;
146 before = strv_join(s->before, " ");
150 after = strv_join(s->after, " ");
154 wants = strv_join(s->wants, " ");
158 conflicts = strv_join(s->conflicts, " ");
162 unit = strjoin(arg_dest, "/", s->name, NULL);
166 /* We might already have a symlink with the same name from a Provides:,
167 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
168 * so remove an existing link */
169 if (is_symlink(unit)) {
170 log_warning("Overwriting existing symlink %s with real service", unit);
174 f = fopen(unit, "wxe");
176 return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
179 "# Automatically generated by systemd-sysv-generator\n\n"
181 "Documentation=man:systemd-sysv-generator(8)\n"
184 s->path, s->description);
186 if (!isempty(before))
187 fprintf(f, "Before=%s\n", before);
189 fprintf(f, "After=%s\n", after);
191 fprintf(f, "Wants=%s\n", wants);
192 if (!isempty(conflicts))
193 fprintf(f, "Conflicts=%s\n", conflicts);
203 "RemainAfterExit=%s\n",
204 yes_no(!s->pid_file));
207 fprintf(f, "PIDFile=%s\n", s->pid_file);
210 "ExecStart=%s start\n"
211 "ExecStop=%s stop\n",
215 fprintf(f, "ExecReload=%s reload\n", s->path);
217 STRV_FOREACH(p, s->wanted_by) {
218 r = add_symlink(s->name, *p);
220 log_unit_error_errno(s->name, r, "Failed to create 'Wants' symlink to %s: %m", *p);
226 static bool usage_contains_reload(const char *line) {
227 return (strcasestr(line, "{reload|") ||
228 strcasestr(line, "{reload}") ||
229 strcasestr(line, "{reload\"") ||
230 strcasestr(line, "|reload|") ||
231 strcasestr(line, "|reload}") ||
232 strcasestr(line, "|reload\""));
235 static char *sysv_translate_name(const char *name) {
238 r = new(char, strlen(name) + strlen(".service") + 1);
242 if (endswith(name, ".sh"))
243 /* Drop .sh suffix */
244 strcpy(stpcpy(r, name) - 3, ".service");
246 /* Normal init script name */
247 strcpy(stpcpy(r, name), ".service");
252 static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
254 /* We silently ignore the $ prefix here. According to the LSB
255 * spec it simply indicates whether something is a
256 * standardized name or a distribution-specific one. Since we
257 * just follow what already exists and do not introduce new
258 * uses or names we don't care who introduced a new name. */
260 static const char * const table[] = {
261 /* LSB defined facilities */
263 "network", SPECIAL_NETWORK_ONLINE_TARGET,
264 "named", SPECIAL_NSS_LOOKUP_TARGET,
265 "portmap", SPECIAL_RPCBIND_TARGET,
266 "remote_fs", SPECIAL_REMOTE_FS_TARGET,
268 "time", SPECIAL_TIME_SYNC_TARGET,
271 char *filename_no_sh, *e, *r;
278 n = *name == '$' ? name + 1 : name;
280 for (i = 0; i < ELEMENTSOF(table); i += 2) {
282 if (!streq(table[i], n))
288 r = strdup(table[i+1]);
295 /* strip ".sh" suffix from file name for comparison */
296 filename_no_sh = strdupa(filename);
297 e = endswith(filename_no_sh, ".sh");
300 filename = filename_no_sh;
303 /* If we don't know this name, fallback heuristics to figure
304 * out whether something is a target or a service alias. */
307 if (!unit_prefix_is_valid(n))
310 /* Facilities starting with $ are most likely targets */
311 r = unit_name_build(n, NULL, ".target");
312 } else if (streq_ptr(n, filename))
313 /* Names equaling the file name of the services are redundant */
316 /* Everything else we assume to be normal service names */
317 r = sysv_translate_name(n);
327 static int load_sysv(SysvStub *s) {
328 _cleanup_fclose_ FILE *f;
338 _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
340 bool supports_reload = false;
344 f = fopen(s->path, "re");
346 return errno == ENOENT ? 0 : -errno;
348 log_debug("Loading SysV script %s", s->path);
351 char l[LINE_MAX], *t;
353 if (!fgets(l, sizeof(l), f)) {
357 log_unit_error(s->name,
358 "Failed to read configuration file '%s': %m",
367 /* Try to figure out whether this init script supports
368 * the reload operation. This heuristic looks for
369 * "Usage" lines which include the reload option. */
370 if ( state == USAGE_CONTINUATION ||
371 (state == NORMAL && strcasestr(t, "usage"))) {
372 if (usage_contains_reload(t)) {
373 supports_reload = true;
375 } else if (t[strlen(t)-1] == '\\')
376 state = USAGE_CONTINUATION;
384 if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
390 if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
396 t += strspn(t, WHITESPACE);
398 if (state == NORMAL) {
400 /* Try to parse Red Hat style description */
402 if (startswith_no_case(t, "description:")) {
404 size_t k = strlen(t);
408 if (t[k-1] == '\\') {
421 free(chkconfig_description);
422 chkconfig_description = d;
424 } else if (startswith_no_case(t, "pidfile:")) {
431 if (!path_is_absolute(fn)) {
432 log_unit_error(s->name,
433 "[%s:%u] PID file not absolute. Ignoring.",
446 } else if (state == DESCRIPTION) {
448 /* Try to parse Red Hat style description
451 size_t k = strlen(t);
463 if (chkconfig_description)
464 d = strjoin(chkconfig_description, " ", j, NULL);
471 free(chkconfig_description);
472 chkconfig_description = d;
475 } else if (state == LSB || state == LSB_DESCRIPTION) {
477 if (startswith_no_case(t, "Provides:")) {
478 const char *word, *state_;
483 FOREACH_WORD_QUOTED(word, z, t+9, state_) {
484 _cleanup_free_ char *n = NULL, *m = NULL;
486 n = strndup(word, z);
490 r = sysv_translate_facility(n, basename(s->path), &m);
496 if (unit_name_to_type(m) == UNIT_SERVICE) {
497 log_debug("Adding Provides: alias '%s' for '%s'", m, s->name);
498 r = add_alias(s->name, m);
505 * indication that the
507 * now available. This
510 * targets do NOT pull
513 r = strv_extend(&s->before, m);
516 r = strv_extend(&s->wants, m);
519 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) {
520 r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET);
527 log_unit_error(s->name,
528 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
529 s->path, line, m, strerror(-r));
531 if (!isempty(state_))
532 log_unit_error(s->name,
533 "[%s:%u] Trailing garbage in Provides, ignoring.",
536 } else if (startswith_no_case(t, "Required-Start:") ||
537 startswith_no_case(t, "Should-Start:") ||
538 startswith_no_case(t, "X-Start-Before:") ||
539 startswith_no_case(t, "X-Start-After:")) {
540 const char *word, *state_;
545 FOREACH_WORD_QUOTED(word, z, strchr(t, ':')+1, state_) {
546 _cleanup_free_ char *n = NULL, *m = NULL;
549 n = strndup(word, z);
553 r = sysv_translate_facility(n, basename(s->path), &m);
555 log_unit_error(s->name,
556 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
557 s->path, line, n, strerror(-r));
564 is_before = startswith_no_case(t, "X-Start-Before:");
566 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
567 /* the network-online target is special, as it needs to be actively pulled in */
568 r = strv_extend(&s->after, m);
571 r = strv_extend(&s->wants, m);
577 r = strv_extend(&s->before, m);
582 r = strv_extend(&s->after, m);
589 log_unit_error(s->name,
590 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
591 s->path, line, m, strerror(-r));
593 if (!isempty(state_))
594 log_unit_error(s->name,
595 "[%s:%u] Trailing garbage in %*s, ignoring.",
597 (int)(strchr(t, ':') - t), t);
599 } else if (startswith_no_case(t, "Description:")) {
602 state = LSB_DESCRIPTION;
612 free(long_description);
613 long_description = d;
615 } else if (startswith_no_case(t, "Short-Description:")) {
628 free(short_description);
629 short_description = d;
631 } else if (state == LSB_DESCRIPTION) {
633 if (startswith(l, "#\t") || startswith(l, "# ")) {
640 if (long_description)
641 d = strjoin(long_description, " ", t, NULL);
648 free(long_description);
649 long_description = d;
658 s->reload = supports_reload;
660 /* We use the long description only if
661 * no short description is set. */
663 if (short_description)
664 description = short_description;
665 else if (chkconfig_description)
666 description = chkconfig_description;
667 else if (long_description)
668 description = long_description;
675 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
685 static int fix_order(SysvStub *s, Hashmap *all_services) {
692 if (s->sysv_start_priority < 0)
695 HASHMAP_FOREACH(other, all_services, j) {
699 if (other->sysv_start_priority < 0)
702 /* If both units have modern headers we don't care
703 * about the priorities */
704 if (s->has_lsb && other->has_lsb)
707 if (other->sysv_start_priority < s->sysv_start_priority) {
708 r = strv_extend(&s->after, other->name);
712 else if (other->sysv_start_priority > s->sysv_start_priority) {
713 r = strv_extend(&s->before, other->name);
720 /* FIXME: Maybe we should compare the name here lexicographically? */
726 static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
729 STRV_FOREACH(path, lp.sysvinit_path) {
730 _cleanup_closedir_ DIR *d = NULL;
736 log_warning_errno(errno, "opendir(%s) failed: %m", *path);
740 while ((de = readdir(d))) {
741 _cleanup_free_ char *fpath = NULL, *name = NULL;
742 _cleanup_free_ SysvStub *service = NULL;
746 if (hidden_file(de->d_name))
749 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
750 log_warning_errno(errno, "stat() failed on %s/%s: %m", *path, de->d_name);
754 if (!(st.st_mode & S_IXUSR))
757 if (!S_ISREG(st.st_mode))
760 name = sysv_translate_name(de->d_name);
764 if (hashmap_contains(all_services, name))
767 fpath = strjoin(*path, "/", de->d_name, NULL);
771 if (unit_file_get_state(UNIT_FILE_SYSTEM, NULL, name) >= 0) {
772 log_debug("Native unit for %s already exists, skipping", name);
776 service = new0(SysvStub, 1);
780 service->sysv_start_priority = -1;
781 service->name = name;
782 service->path = fpath;
784 r = hashmap_put(all_services, service->name, service);
796 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
799 _cleanup_closedir_ DIR *d = NULL;
800 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
803 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
804 _cleanup_set_free_ Set *shutdown_services = NULL;
807 STRV_FOREACH(p, lp.sysvrcnd_path)
808 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
812 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
822 log_warning_errno(errno, "opendir(%s) failed: %m", path);
827 while ((de = readdir(d))) {
830 if (hidden_file(de->d_name))
833 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
836 if (strlen(de->d_name) < 4)
839 a = undecchar(de->d_name[1]);
840 b = undecchar(de->d_name[2]);
846 fpath = strjoin(*p, "/", de->d_name, NULL);
852 name = sysv_translate_name(de->d_name + 3);
858 service = hashmap_get(all_services, name);
860 log_debug("Ignoring %s symlink in %s, not generating %s.",
861 de->d_name, rcnd_table[i].path, name);
865 if (de->d_name[0] == 'S') {
867 if (rcnd_table[i].type == RUNLEVEL_UP) {
868 service->sysv_start_priority =
869 MAX(a*10 + b, service->sysv_start_priority);
872 r = set_ensure_allocated(&runlevel_services[i], NULL);
876 r = set_put(runlevel_services[i], service);
880 } else if (de->d_name[0] == 'K' &&
881 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
883 r = set_ensure_allocated(&shutdown_services, NULL);
887 r = set_put(shutdown_services, service);
895 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
896 SET_FOREACH(service, runlevel_services[i], j) {
897 r = strv_extend(&service->before, rcnd_table[i].target);
900 r = strv_extend(&service->wanted_by, rcnd_table[i].target);
905 SET_FOREACH(service, shutdown_services, j) {
906 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
909 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
918 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
919 set_free(runlevel_services[i]);
924 int main(int argc, char *argv[]) {
927 Hashmap *all_services;
931 if (argc > 1 && argc != 4) {
932 log_error("This program takes three or no arguments.");
939 log_set_target(LOG_TARGET_SAFE);
940 log_parse_environment();
945 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
947 log_error("Failed to find lookup paths.");
951 all_services = hashmap_new(&string_hash_ops);
957 r = enumerate_sysv(lp, all_services);
959 log_error("Failed to generate units for all init scripts.");
963 r = set_dependencies_from_rcnd(lp, all_services);
965 log_error("Failed to read runlevels from rcnd links.");
969 HASHMAP_FOREACH(service, all_services, j) {
970 q = load_sysv(service);
975 HASHMAP_FOREACH(service, all_services, j) {
976 q = fix_order(service, all_services);
980 q = generate_unit_file(service);