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;
151 before = strv_join(s->before, " ");
155 after = strv_join(s->after, " ");
159 wants = strv_join(s->wants, " ");
163 conflicts = strv_join(s->conflicts, " ");
167 unit = strjoin(arg_dest, "/", s->name, NULL);
171 /* We might already have a symlink with the same name from a Provides:,
172 * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
173 * so remove an existing link */
174 if (is_symlink(unit)) {
175 log_warning("Overwriting existing symlink %s with real service", unit);
179 f = fopen(unit, "wxe");
181 return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
184 "# Automatically generated by systemd-sysv-generator\n\n"
186 "Documentation=man:systemd-sysv-generator(8)\n"
189 s->path, s->description);
191 if (!isempty(before))
192 fprintf(f, "Before=%s\n", before);
194 fprintf(f, "After=%s\n", after);
196 fprintf(f, "Wants=%s\n", wants);
197 if (!isempty(conflicts))
198 fprintf(f, "Conflicts=%s\n", conflicts);
208 "RemainAfterExit=%s\n",
209 yes_no(!s->pid_file));
212 fprintf(f, "PIDFile=%s\n", s->pid_file);
215 "ExecStart=%s start\n"
216 "ExecStop=%s stop\n",
220 fprintf(f, "ExecReload=%s reload\n", s->path);
222 STRV_FOREACH(p, s->wanted_by) {
223 r = add_symlink(s->name, *p);
225 log_unit_error_errno(s->name, r, "Failed to create 'Wants' symlink to %s: %m", *p);
231 static bool usage_contains_reload(const char *line) {
232 return (strcasestr(line, "{reload|") ||
233 strcasestr(line, "{reload}") ||
234 strcasestr(line, "{reload\"") ||
235 strcasestr(line, "|reload|") ||
236 strcasestr(line, "|reload}") ||
237 strcasestr(line, "|reload\""));
240 static char *sysv_translate_name(const char *name) {
243 r = new(char, strlen(name) + strlen(".service") + 1);
247 if (endswith(name, ".sh"))
248 /* Drop .sh suffix */
249 strcpy(stpcpy(r, name) - 3, ".service");
251 /* Normal init script name */
252 strcpy(stpcpy(r, name), ".service");
257 static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
259 /* We silently ignore the $ prefix here. According to the LSB
260 * spec it simply indicates whether something is a
261 * standardized name or a distribution-specific one. Since we
262 * just follow what already exists and do not introduce new
263 * uses or names we don't care who introduced a new name. */
265 static const char * const table[] = {
266 /* LSB defined facilities */
268 "network", SPECIAL_NETWORK_ONLINE_TARGET,
269 "named", SPECIAL_NSS_LOOKUP_TARGET,
270 "portmap", SPECIAL_RPCBIND_TARGET,
271 "remote_fs", SPECIAL_REMOTE_FS_TARGET,
273 "time", SPECIAL_TIME_SYNC_TARGET,
276 char *filename_no_sh, *e, *r;
283 n = *name == '$' ? name + 1 : name;
285 for (i = 0; i < ELEMENTSOF(table); i += 2) {
287 if (!streq(table[i], n))
293 r = strdup(table[i+1]);
300 /* strip ".sh" suffix from file name for comparison */
301 filename_no_sh = strdupa(filename);
302 e = endswith(filename, ".sh");
305 filename = filename_no_sh;
308 /* If we don't know this name, fallback heuristics to figure
309 * out whether something is a target or a service alias. */
312 if (!unit_prefix_is_valid(n))
315 /* Facilities starting with $ are most likely targets */
316 r = unit_name_build(n, NULL, ".target");
317 } else if (filename && streq(name, filename))
318 /* Names equaling the file name of the services are redundant */
321 /* Everything else we assume to be normal service names */
322 r = sysv_translate_name(n);
333 static int load_sysv(SysvStub *s) {
334 _cleanup_fclose_ FILE *f;
344 _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
346 bool supports_reload = false;
350 f = fopen(s->path, "re");
352 return errno == ENOENT ? 0 : -errno;
354 log_debug("Loading SysV script %s", s->path);
357 char l[LINE_MAX], *t;
359 if (!fgets(l, sizeof(l), f)) {
363 log_unit_error(s->name,
364 "Failed to read configuration file '%s': %m",
373 /* Try to figure out whether this init script supports
374 * the reload operation. This heuristic looks for
375 * "Usage" lines which include the reload option. */
376 if ( state == USAGE_CONTINUATION ||
377 (state == NORMAL && strcasestr(t, "usage"))) {
378 if (usage_contains_reload(t)) {
379 supports_reload = true;
381 } else if (t[strlen(t)-1] == '\\')
382 state = USAGE_CONTINUATION;
390 if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
396 if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
402 t += strspn(t, WHITESPACE);
404 if (state == NORMAL) {
406 /* Try to parse Red Hat style description */
408 if (startswith_no_case(t, "description:")) {
410 size_t k = strlen(t);
414 if (t[k-1] == '\\') {
427 free(chkconfig_description);
428 chkconfig_description = d;
430 } else if (startswith_no_case(t, "pidfile:")) {
437 if (!path_is_absolute(fn)) {
438 log_unit_error(s->name,
439 "[%s:%u] PID file not absolute. Ignoring.",
452 } else if (state == DESCRIPTION) {
454 /* Try to parse Red Hat style description
457 size_t k = strlen(t);
469 if (chkconfig_description)
470 d = strjoin(chkconfig_description, " ", j, NULL);
477 free(chkconfig_description);
478 chkconfig_description = d;
481 } else if (state == LSB || state == LSB_DESCRIPTION) {
483 if (startswith_no_case(t, "Provides:")) {
484 const char *word, *state_;
489 FOREACH_WORD_QUOTED(word, z, t+9, state_) {
490 _cleanup_free_ char *n = NULL, *m = NULL;
492 n = strndup(word, z);
496 r = sysv_translate_facility(n, basename(s->path), &m);
502 if (unit_name_to_type(m) == UNIT_SERVICE) {
503 log_debug("Adding Provides: alias '%s' for '%s'", m, s->name);
504 r = add_alias(s->name, m);
511 * indication that the
513 * now available. This
516 * targets do NOT pull
519 r = strv_extend(&s->before, m);
522 r = strv_extend(&s->wants, m);
525 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) {
526 r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET);
533 log_unit_error(s->name,
534 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
535 s->path, line, m, strerror(-r));
537 if (!isempty(state_))
538 log_unit_error(s->name,
539 "[%s:%u] Trailing garbage in Provides, ignoring.",
542 } else if (startswith_no_case(t, "Required-Start:") ||
543 startswith_no_case(t, "Should-Start:") ||
544 startswith_no_case(t, "X-Start-Before:") ||
545 startswith_no_case(t, "X-Start-After:")) {
546 const char *word, *state_;
551 FOREACH_WORD_QUOTED(word, z, strchr(t, ':')+1, state_) {
552 _cleanup_free_ char *n = NULL, *m = NULL;
555 n = strndup(word, z);
559 r = sysv_translate_facility(n, basename(s->path), &m);
561 log_unit_error(s->name,
562 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
563 s->path, line, n, strerror(-r));
570 is_before = startswith_no_case(t, "X-Start-Before:");
572 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
573 /* the network-online target is special, as it needs to be actively pulled in */
574 r = strv_extend(&s->after, m);
577 r = strv_extend(&s->wants, m);
583 r = strv_extend(&s->before, m);
588 r = strv_extend(&s->after, m);
595 log_unit_error(s->name,
596 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
597 s->path, line, m, strerror(-r));
599 if (!isempty(state_))
600 log_unit_error(s->name,
601 "[%s:%u] Trailing garbage in %*s, ignoring.",
603 (int)(strchr(t, ':') - t), t);
605 } else if (startswith_no_case(t, "Description:")) {
608 state = LSB_DESCRIPTION;
618 free(long_description);
619 long_description = d;
621 } else if (startswith_no_case(t, "Short-Description:")) {
634 free(short_description);
635 short_description = d;
637 } else if (state == LSB_DESCRIPTION) {
639 if (startswith(l, "#\t") || startswith(l, "# ")) {
646 if (long_description)
647 d = strjoin(long_description, " ", t, NULL);
654 free(long_description);
655 long_description = d;
664 s->reload = supports_reload;
666 /* We use the long description only if
667 * no short description is set. */
669 if (short_description)
670 description = short_description;
671 else if (chkconfig_description)
672 description = chkconfig_description;
673 else if (long_description)
674 description = long_description;
681 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
691 static int fix_order(SysvStub *s, Hashmap *all_services) {
698 if (s->sysv_start_priority < 0)
701 HASHMAP_FOREACH(other, all_services, j) {
705 if (other->sysv_start_priority < 0)
708 /* If both units have modern headers we don't care
709 * about the priorities */
710 if (s->has_lsb && other->has_lsb)
713 if (other->sysv_start_priority < s->sysv_start_priority) {
714 r = strv_extend(&s->after, other->name);
718 else if (other->sysv_start_priority > s->sysv_start_priority) {
719 r = strv_extend(&s->before, other->name);
726 /* FIXME: Maybe we should compare the name here lexicographically? */
732 static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
735 STRV_FOREACH(path, lp.sysvinit_path) {
736 _cleanup_closedir_ DIR *d = NULL;
742 log_warning_errno(errno, "opendir(%s) failed: %m", *path);
746 while ((de = readdir(d))) {
747 _cleanup_free_ char *fpath = NULL, *name = NULL;
748 _cleanup_free_ SysvStub *service = NULL;
752 dirent_ensure_type(d, de);
754 if (!dirent_is_file(de))
757 if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) {
758 log_warning_errno(errno, "stat() failed on %s/%s: %m", *path, de->d_name);
762 if (!(st.st_mode & S_IXUSR))
765 if (!S_ISREG(st.st_mode))
768 name = sysv_translate_name(de->d_name);
772 fpath = strjoin(*path, "/", de->d_name, NULL);
776 if (hashmap_contains(all_services, name))
779 service = new0(SysvStub, 1);
783 service->sysv_start_priority = -1;
784 service->name = name;
785 service->path = fpath;
787 r = hashmap_put(all_services, service->name, service);
799 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
802 _cleanup_closedir_ DIR *d = NULL;
803 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
806 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
807 _cleanup_set_free_ Set *shutdown_services = NULL;
810 STRV_FOREACH(p, lp.sysvrcnd_path)
811 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
815 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
825 log_warning_errno(errno, "opendir(%s) failed: %m", path);
830 while ((de = readdir(d))) {
833 if (hidden_file(de->d_name))
836 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
839 if (strlen(de->d_name) < 4)
842 a = undecchar(de->d_name[1]);
843 b = undecchar(de->d_name[2]);
849 fpath = strjoin(*p, "/", de->d_name, NULL);
855 name = sysv_translate_name(de->d_name + 3);
861 service = hashmap_get(all_services, name);
863 log_warning("Could not find init script for %s", name);
867 if (de->d_name[0] == 'S') {
869 if (rcnd_table[i].type == RUNLEVEL_UP) {
870 service->sysv_start_priority =
871 MAX(a*10 + b, service->sysv_start_priority);
874 r = set_ensure_allocated(&runlevel_services[i], NULL);
878 r = set_put(runlevel_services[i], service);
882 } else if (de->d_name[0] == 'K' &&
883 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
885 r = set_ensure_allocated(&shutdown_services, NULL);
889 r = set_put(shutdown_services, service);
897 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
898 SET_FOREACH(service, runlevel_services[i], j) {
899 r = strv_extend(&service->before, rcnd_table[i].target);
902 r = strv_extend(&service->wanted_by, rcnd_table[i].target);
907 SET_FOREACH(service, shutdown_services, j) {
908 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
911 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
920 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
921 set_free(runlevel_services[i]);
926 int main(int argc, char *argv[]) {
929 Hashmap *all_services;
933 if (argc > 1 && argc != 4) {
934 log_error("This program takes three or no arguments.");
941 log_set_target(LOG_TARGET_SAFE);
942 log_parse_environment();
947 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
949 log_error("Failed to find lookup paths.");
953 all_services = hashmap_new(&string_hash_ops);
959 r = enumerate_sysv(lp, all_services);
961 log_error("Failed to generate units for all init scripts.");
965 r = set_dependencies_from_rcnd(lp, all_services);
967 log_error("Failed to read runlevels from rcnd links.");
971 HASHMAP_FOREACH(service, all_services, j) {
972 q = load_sysv(service);
977 HASHMAP_FOREACH(service, all_services, j) {
978 q = fix_order(service, all_services);
982 q = generate_unit_file(service);