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 dirent_ensure_type(d, de);
757 if (!dirent_is_file(de))
760 if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) {
761 log_warning_errno(errno, "stat() failed on %s/%s: %m", *path, de->d_name);
765 if (!(st.st_mode & S_IXUSR))
768 if (!S_ISREG(st.st_mode))
771 name = sysv_translate_name(de->d_name);
775 fpath = strjoin(*path, "/", de->d_name, NULL);
779 if (hashmap_contains(all_services, name))
782 service = new0(SysvStub, 1);
786 service->sysv_start_priority = -1;
787 service->name = name;
788 service->path = fpath;
790 r = hashmap_put(all_services, service->name, service);
802 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
805 _cleanup_closedir_ DIR *d = NULL;
806 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
809 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
810 _cleanup_set_free_ Set *shutdown_services = NULL;
813 STRV_FOREACH(p, lp.sysvrcnd_path)
814 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
818 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
828 log_warning_errno(errno, "opendir(%s) failed: %m", path);
833 while ((de = readdir(d))) {
836 if (hidden_file(de->d_name))
839 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
842 if (strlen(de->d_name) < 4)
845 a = undecchar(de->d_name[1]);
846 b = undecchar(de->d_name[2]);
852 fpath = strjoin(*p, "/", de->d_name, NULL);
858 name = sysv_translate_name(de->d_name + 3);
864 service = hashmap_get(all_services, name);
866 log_warning("Could not find init script for %s", name);
870 if (de->d_name[0] == 'S') {
872 if (rcnd_table[i].type == RUNLEVEL_UP) {
873 service->sysv_start_priority =
874 MAX(a*10 + b, service->sysv_start_priority);
877 r = set_ensure_allocated(&runlevel_services[i], NULL);
881 r = set_put(runlevel_services[i], service);
885 } else if (de->d_name[0] == 'K' &&
886 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
888 r = set_ensure_allocated(&shutdown_services, NULL);
892 r = set_put(shutdown_services, service);
900 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
901 SET_FOREACH(service, runlevel_services[i], j) {
902 r = strv_extend(&service->before, rcnd_table[i].target);
905 r = strv_extend(&service->wanted_by, rcnd_table[i].target);
910 SET_FOREACH(service, shutdown_services, j) {
911 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
914 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
923 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
924 set_free(runlevel_services[i]);
929 int main(int argc, char *argv[]) {
932 Hashmap *all_services;
936 if (argc > 1 && argc != 4) {
937 log_error("This program takes three or no arguments.");
944 log_set_target(LOG_TARGET_SAFE);
945 log_parse_environment();
950 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
952 log_error("Failed to find lookup paths.");
956 all_services = hashmap_new(&string_hash_ops);
962 r = enumerate_sysv(lp, all_services);
964 log_error("Failed to generate units for all init scripts.");
968 r = set_dependencies_from_rcnd(lp, all_services);
970 log_error("Failed to read runlevels from rcnd links.");
974 HASHMAP_FOREACH(service, all_services, j) {
975 q = load_sysv(service);
980 HASHMAP_FOREACH(service, all_services, j) {
981 q = fix_order(service, all_services);
985 q = generate_unit_file(service);