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,
280 _cleanup_free_ char *filename_no_sh = NULL;
285 n = *name == '$' ? name + 1 : name;
287 for (i = 0; i < ELEMENTSOF(table); i += 2) {
289 if (!streq(table[i], n))
295 r = strdup(table[i+1]);
302 /* strip ".sh" suffix from file name for comparison */
303 filename_no_sh = strdup(filename);
306 if (endswith(filename, ".sh"))
307 filename_no_sh[strlen(filename)-3] = '\0';
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_no_sh))
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);
505 if (unit_name_to_type(m) == UNIT_SERVICE) {
506 log_debug("Adding Provides: alias '%s' for '%s'", m, s->name);
507 r = add_alias(s->name, m);
514 * indication that the
516 * now available. This
519 * targets do NOT pull
522 r = strv_extend(&s->before, m);
525 r = strv_extend(&s->wants, m);
528 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) {
529 r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET);
536 log_unit_error(s->name,
537 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
538 s->path, line, m, strerror(-r));
540 if (!isempty(state_))
541 log_unit_error(s->name,
542 "[%s:%u] Trailing garbage in Provides, ignoring.",
545 } else if (startswith_no_case(t, "Required-Start:") ||
546 startswith_no_case(t, "Should-Start:") ||
547 startswith_no_case(t, "X-Start-Before:") ||
548 startswith_no_case(t, "X-Start-After:")) {
549 const char *word, *state_;
554 FOREACH_WORD_QUOTED(word, z, strchr(t, ':')+1, state_) {
555 _cleanup_free_ char *n = NULL, *m = NULL;
558 n = strndup(word, z);
562 r = sysv_translate_facility(n, basename(s->path), &m);
564 log_unit_error(s->name,
565 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
566 s->path, line, n, strerror(-r));
573 is_before = startswith_no_case(t, "X-Start-Before:");
575 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
576 /* the network-online target is special, as it needs to be actively pulled in */
577 r = strv_extend(&s->after, m);
580 r = strv_extend(&s->wants, m);
586 r = strv_extend(&s->before, m);
591 r = strv_extend(&s->after, m);
598 log_unit_error(s->name,
599 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
600 s->path, line, m, strerror(-r));
602 if (!isempty(state_))
603 log_unit_error(s->name,
604 "[%s:%u] Trailing garbage in %*s, ignoring.",
606 (int)(strchr(t, ':') - t), t);
608 } else if (startswith_no_case(t, "Description:")) {
611 state = LSB_DESCRIPTION;
621 free(long_description);
622 long_description = d;
624 } else if (startswith_no_case(t, "Short-Description:")) {
637 free(short_description);
638 short_description = d;
640 } else if (state == LSB_DESCRIPTION) {
642 if (startswith(l, "#\t") || startswith(l, "# ")) {
649 if (long_description)
650 d = strjoin(long_description, " ", t, NULL);
657 free(long_description);
658 long_description = d;
667 s->reload = supports_reload;
669 /* We use the long description only if
670 * no short description is set. */
672 if (short_description)
673 description = short_description;
674 else if (chkconfig_description)
675 description = chkconfig_description;
676 else if (long_description)
677 description = long_description;
684 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
694 static int fix_order(SysvStub *s, Hashmap *all_services) {
701 if (s->sysv_start_priority < 0)
704 HASHMAP_FOREACH(other, all_services, j) {
708 if (other->sysv_start_priority < 0)
711 /* If both units have modern headers we don't care
712 * about the priorities */
713 if (s->has_lsb && other->has_lsb)
716 if (other->sysv_start_priority < s->sysv_start_priority) {
717 r = strv_extend(&s->after, other->name);
721 else if (other->sysv_start_priority > s->sysv_start_priority) {
722 r = strv_extend(&s->before, other->name);
729 /* FIXME: Maybe we should compare the name here lexicographically? */
735 static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
738 STRV_FOREACH(path, lp.sysvinit_path) {
739 _cleanup_closedir_ DIR *d = NULL;
745 log_warning_errno(errno, "opendir(%s) failed: %m", *path);
749 while ((de = readdir(d))) {
750 _cleanup_free_ char *fpath = NULL, *name = NULL;
751 _cleanup_free_ SysvStub *service = NULL;
755 if (hidden_file(de->d_name))
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);