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 if (streq(service, alias)) {
123 log_error("Ignoring creation of an alias %s for itself", service);
127 link = strjoin(arg_dest, "/", alias, NULL);
131 r = symlink(service, link);
141 static int generate_unit_file(SysvStub *s) {
143 _cleanup_fclose_ FILE *f = NULL;
144 _cleanup_free_ char *unit = NULL;
145 _cleanup_free_ char *before = NULL;
146 _cleanup_free_ char *after = NULL;
147 _cleanup_free_ char *wants = NULL;
148 _cleanup_free_ char *conflicts = NULL;
152 before = strv_join(s->before, " ");
156 after = strv_join(s->after, " ");
160 wants = strv_join(s->wants, " ");
164 conflicts = strv_join(s->conflicts, " ");
168 unit = strjoin(arg_dest, "/", s->name, NULL);
172 /* We might already have a symlink with the same name from a Provides:,
173 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
174 * so remove an existing link */
175 if (lstat(unit, &st) == 0 && S_ISLNK(st.st_mode)) {
176 log_warning("Overwriting existing symlink %s with real service", unit);
180 f = fopen(unit, "wxe");
182 return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
185 "# Automatically generated by systemd-sysv-generator\n\n"
187 "Documentation=man:systemd-sysv-generator(8)\n"
190 s->path, s->description);
192 if (!isempty(before))
193 fprintf(f, "Before=%s\n", before);
195 fprintf(f, "After=%s\n", after);
197 fprintf(f, "Wants=%s\n", wants);
198 if (!isempty(conflicts))
199 fprintf(f, "Conflicts=%s\n", conflicts);
209 "RemainAfterExit=%s\n",
210 yes_no(!s->pid_file));
213 fprintf(f, "PIDFile=%s\n", s->pid_file);
216 "ExecStart=%s start\n"
217 "ExecStop=%s stop\n",
221 fprintf(f, "ExecReload=%s reload\n", s->path);
223 STRV_FOREACH(p, s->wanted_by) {
224 r = add_symlink(s->name, *p);
226 log_unit_error_errno(s->name, r, "Failed to create 'Wants' symlink to %s: %m", *p);
232 static bool usage_contains_reload(const char *line) {
233 return (strcasestr(line, "{reload|") ||
234 strcasestr(line, "{reload}") ||
235 strcasestr(line, "{reload\"") ||
236 strcasestr(line, "|reload|") ||
237 strcasestr(line, "|reload}") ||
238 strcasestr(line, "|reload\""));
241 static char *sysv_translate_name(const char *name) {
244 r = new(char, strlen(name) + strlen(".service") + 1);
248 if (endswith(name, ".sh"))
249 /* Drop .sh suffix */
250 strcpy(stpcpy(r, name) - 3, ".service");
252 /* Normal init script name */
253 strcpy(stpcpy(r, name), ".service");
258 static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
260 /* We silently ignore the $ prefix here. According to the LSB
261 * spec it simply indicates whether something is a
262 * standardized name or a distribution-specific one. Since we
263 * just follow what already exists and do not introduce new
264 * uses or names we don't care who introduced a new name. */
266 static const char * const table[] = {
267 /* LSB defined facilities */
269 "network", SPECIAL_NETWORK_ONLINE_TARGET,
270 "named", SPECIAL_NSS_LOOKUP_TARGET,
271 "portmap", SPECIAL_RPCBIND_TARGET,
272 "remote_fs", SPECIAL_REMOTE_FS_TARGET,
274 "time", SPECIAL_TIME_SYNC_TARGET,
277 char *filename_no_sh, *e, *r;
284 n = *name == '$' ? name + 1 : name;
286 for (i = 0; i < ELEMENTSOF(table); i += 2) {
288 if (!streq(table[i], n))
294 r = strdup(table[i+1]);
301 /* strip ".sh" suffix from file name for comparison */
302 filename_no_sh = strdupa(filename);
303 e = endswith(filename, ".sh");
306 filename = filename_no_sh;
309 /* If we don't know this name, fallback heuristics to figure
310 * out whether something is a target or a service alias. */
313 if (!unit_prefix_is_valid(n))
316 /* Facilities starting with $ are most likely targets */
317 r = unit_name_build(n, NULL, ".target");
318 } else if (filename && streq(name, filename))
319 /* Names equaling the file name of the services are redundant */
322 /* Everything else we assume to be normal service names */
323 r = sysv_translate_name(n);
334 static int load_sysv(SysvStub *s) {
335 _cleanup_fclose_ FILE *f;
345 _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
347 bool supports_reload = false;
351 f = fopen(s->path, "re");
353 return errno == ENOENT ? 0 : -errno;
355 log_debug("Loading SysV script %s", s->path);
358 char l[LINE_MAX], *t;
360 if (!fgets(l, sizeof(l), f)) {
364 log_unit_error(s->name,
365 "Failed to read configuration file '%s': %m",
374 /* Try to figure out whether this init script supports
375 * the reload operation. This heuristic looks for
376 * "Usage" lines which include the reload option. */
377 if ( state == USAGE_CONTINUATION ||
378 (state == NORMAL && strcasestr(t, "usage"))) {
379 if (usage_contains_reload(t)) {
380 supports_reload = true;
382 } else if (t[strlen(t)-1] == '\\')
383 state = USAGE_CONTINUATION;
391 if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
397 if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
403 t += strspn(t, WHITESPACE);
405 if (state == NORMAL) {
407 /* Try to parse Red Hat style description */
409 if (startswith_no_case(t, "description:")) {
411 size_t k = strlen(t);
415 if (t[k-1] == '\\') {
428 free(chkconfig_description);
429 chkconfig_description = d;
431 } else if (startswith_no_case(t, "pidfile:")) {
438 if (!path_is_absolute(fn)) {
439 log_unit_error(s->name,
440 "[%s:%u] PID file not absolute. Ignoring.",
453 } else if (state == DESCRIPTION) {
455 /* Try to parse Red Hat style description
458 size_t k = strlen(t);
470 if (chkconfig_description)
471 d = strjoin(chkconfig_description, " ", j, NULL);
478 free(chkconfig_description);
479 chkconfig_description = d;
482 } else if (state == LSB || state == LSB_DESCRIPTION) {
484 if (startswith_no_case(t, "Provides:")) {
485 const char *word, *state_;
490 FOREACH_WORD_QUOTED(word, z, t+9, state_) {
491 _cleanup_free_ char *n = NULL, *m = NULL;
493 n = strndup(word, z);
497 r = sysv_translate_facility(n, basename(s->path), &m);
503 if (unit_name_to_type(m) == UNIT_SERVICE) {
504 log_debug("Adding Provides: alias '%s' for '%s'", m, s->name);
505 r = add_alias(s->name, m);
512 * indication that the
514 * now available. This
517 * targets do NOT pull
520 r = strv_extend(&s->before, m);
523 r = strv_extend(&s->wants, m);
526 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) {
527 r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET);
534 log_unit_error(s->name,
535 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
536 s->path, line, m, strerror(-r));
538 if (!isempty(state_))
539 log_unit_error(s->name,
540 "[%s:%u] Trailing garbage in Provides, ignoring.",
543 } else if (startswith_no_case(t, "Required-Start:") ||
544 startswith_no_case(t, "Should-Start:") ||
545 startswith_no_case(t, "X-Start-Before:") ||
546 startswith_no_case(t, "X-Start-After:")) {
547 const char *word, *state_;
552 FOREACH_WORD_QUOTED(word, z, strchr(t, ':')+1, state_) {
553 _cleanup_free_ char *n = NULL, *m = NULL;
556 n = strndup(word, z);
560 r = sysv_translate_facility(n, basename(s->path), &m);
562 log_unit_error(s->name,
563 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
564 s->path, line, n, strerror(-r));
571 is_before = startswith_no_case(t, "X-Start-Before:");
573 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
574 /* the network-online target is special, as it needs to be actively pulled in */
575 r = strv_extend(&s->after, m);
578 r = strv_extend(&s->wants, m);
584 r = strv_extend(&s->before, m);
589 r = strv_extend(&s->after, m);
596 log_unit_error(s->name,
597 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
598 s->path, line, m, strerror(-r));
600 if (!isempty(state_))
601 log_unit_error(s->name,
602 "[%s:%u] Trailing garbage in %*s, ignoring.",
604 (int)(strchr(t, ':') - t), t);
606 } else if (startswith_no_case(t, "Description:")) {
609 state = LSB_DESCRIPTION;
619 free(long_description);
620 long_description = d;
622 } else if (startswith_no_case(t, "Short-Description:")) {
635 free(short_description);
636 short_description = d;
638 } else if (state == LSB_DESCRIPTION) {
640 if (startswith(l, "#\t") || startswith(l, "# ")) {
647 if (long_description)
648 d = strjoin(long_description, " ", t, NULL);
655 free(long_description);
656 long_description = d;
665 s->reload = supports_reload;
667 /* We use the long description only if
668 * no short description is set. */
670 if (short_description)
671 description = short_description;
672 else if (chkconfig_description)
673 description = chkconfig_description;
674 else if (long_description)
675 description = long_description;
682 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
692 static int fix_order(SysvStub *s, Hashmap *all_services) {
699 if (s->sysv_start_priority < 0)
702 HASHMAP_FOREACH(other, all_services, j) {
706 if (other->sysv_start_priority < 0)
709 /* If both units have modern headers we don't care
710 * about the priorities */
711 if (s->has_lsb && other->has_lsb)
714 if (other->sysv_start_priority < s->sysv_start_priority) {
715 r = strv_extend(&s->after, other->name);
719 else if (other->sysv_start_priority > s->sysv_start_priority) {
720 r = strv_extend(&s->before, other->name);
727 /* FIXME: Maybe we should compare the name here lexicographically? */
733 static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
736 STRV_FOREACH(path, lp.sysvinit_path) {
737 _cleanup_closedir_ DIR *d = NULL;
743 log_warning_errno(errno, "opendir(%s) failed: %m", *path);
747 while ((de = readdir(d))) {
748 _cleanup_free_ char *fpath = NULL, *name = NULL;
749 _cleanup_free_ SysvStub *service = NULL;
753 dirent_ensure_type(d, de);
755 if (!dirent_is_file(de))
758 if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) {
759 log_warning_errno(errno, "stat() failed on %s/%s: %m", *path, de->d_name);
763 if (!(st.st_mode & S_IXUSR))
766 if (!S_ISREG(st.st_mode))
769 name = sysv_translate_name(de->d_name);
773 fpath = strjoin(*path, "/", de->d_name, NULL);
777 if (hashmap_contains(all_services, name))
780 service = new0(SysvStub, 1);
784 service->sysv_start_priority = -1;
785 service->name = name;
786 service->path = fpath;
788 r = hashmap_put(all_services, service->name, service);
800 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
803 _cleanup_closedir_ DIR *d = NULL;
804 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
807 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
808 _cleanup_set_free_ Set *shutdown_services = NULL;
811 STRV_FOREACH(p, lp.sysvrcnd_path)
812 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
816 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
826 log_warning_errno(errno, "opendir(%s) failed: %m", path);
831 while ((de = readdir(d))) {
834 if (hidden_file(de->d_name))
837 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
840 if (strlen(de->d_name) < 4)
843 a = undecchar(de->d_name[1]);
844 b = undecchar(de->d_name[2]);
850 fpath = strjoin(*p, "/", de->d_name, NULL);
856 name = sysv_translate_name(de->d_name + 3);
862 service = hashmap_get(all_services, name);
864 log_warning("Could not find init script for %s", name);
868 if (de->d_name[0] == 'S') {
870 if (rcnd_table[i].type == RUNLEVEL_UP) {
871 service->sysv_start_priority =
872 MAX(a*10 + b, service->sysv_start_priority);
875 r = set_ensure_allocated(&runlevel_services[i], NULL);
879 r = set_put(runlevel_services[i], service);
883 } else if (de->d_name[0] == 'K' &&
884 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
886 r = set_ensure_allocated(&shutdown_services, NULL);
890 r = set_put(shutdown_services, service);
898 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
899 SET_FOREACH(service, runlevel_services[i], j) {
900 r = strv_extend(&service->before, rcnd_table[i].target);
903 r = strv_extend(&service->wanted_by, rcnd_table[i].target);
908 SET_FOREACH(service, shutdown_services, j) {
909 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
912 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
921 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
922 set_free(runlevel_services[i]);
927 int main(int argc, char *argv[]) {
930 Hashmap *all_services;
934 if (argc > 1 && argc != 4) {
935 log_error("This program takes three or no arguments.");
942 log_set_target(LOG_TARGET_SAFE);
943 log_parse_environment();
948 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
950 log_error("Failed to find lookup paths.");
954 all_services = hashmap_new(&string_hash_ops);
960 r = enumerate_sysv(lp, all_services);
962 log_error("Failed to generate units for all init scripts.");
966 r = set_dependencies_from_rcnd(lp, all_services);
968 log_error("Failed to read runlevels from rcnd links.");
972 HASHMAP_FOREACH(service, all_services, j) {
973 q = load_sysv(service);
978 HASHMAP_FOREACH(service, all_services, j) {
979 q = fix_order(service, all_services);
983 q = generate_unit_file(service);