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_no_sh, ".sh");
306 /* If we don't know this name, fallback heuristics to figure
307 * out whether something is a target or a service alias. */
310 if (!unit_prefix_is_valid(n))
313 /* Facilities starting with $ are most likely targets */
314 r = unit_name_build(n, NULL, ".target");
315 } else if (filename && streq(name, filename_no_sh))
316 /* Names equaling the file name of the services are redundant */
319 /* Everything else we assume to be normal service names */
320 r = sysv_translate_name(n);
331 static int load_sysv(SysvStub *s) {
332 _cleanup_fclose_ FILE *f;
342 _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
344 bool supports_reload = false;
348 f = fopen(s->path, "re");
350 return errno == ENOENT ? 0 : -errno;
352 log_debug("Loading SysV script %s", s->path);
355 char l[LINE_MAX], *t;
357 if (!fgets(l, sizeof(l), f)) {
361 log_unit_error(s->name,
362 "Failed to read configuration file '%s': %m",
371 /* Try to figure out whether this init script supports
372 * the reload operation. This heuristic looks for
373 * "Usage" lines which include the reload option. */
374 if ( state == USAGE_CONTINUATION ||
375 (state == NORMAL && strcasestr(t, "usage"))) {
376 if (usage_contains_reload(t)) {
377 supports_reload = true;
379 } else if (t[strlen(t)-1] == '\\')
380 state = USAGE_CONTINUATION;
388 if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
394 if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
400 t += strspn(t, WHITESPACE);
402 if (state == NORMAL) {
404 /* Try to parse Red Hat style description */
406 if (startswith_no_case(t, "description:")) {
408 size_t k = strlen(t);
412 if (t[k-1] == '\\') {
425 free(chkconfig_description);
426 chkconfig_description = d;
428 } else if (startswith_no_case(t, "pidfile:")) {
435 if (!path_is_absolute(fn)) {
436 log_unit_error(s->name,
437 "[%s:%u] PID file not absolute. Ignoring.",
450 } else if (state == DESCRIPTION) {
452 /* Try to parse Red Hat style description
455 size_t k = strlen(t);
467 if (chkconfig_description)
468 d = strjoin(chkconfig_description, " ", j, NULL);
475 free(chkconfig_description);
476 chkconfig_description = d;
479 } else if (state == LSB || state == LSB_DESCRIPTION) {
481 if (startswith_no_case(t, "Provides:")) {
482 const char *word, *state_;
487 FOREACH_WORD_QUOTED(word, z, t+9, state_) {
488 _cleanup_free_ char *n = NULL, *m = NULL;
490 n = strndup(word, z);
494 r = sysv_translate_facility(n, basename(s->path), &m);
500 if (unit_name_to_type(m) == UNIT_SERVICE) {
501 log_debug("Adding Provides: alias '%s' for '%s'", m, s->name);
502 r = add_alias(s->name, m);
509 * indication that the
511 * now available. This
514 * targets do NOT pull
517 r = strv_extend(&s->before, m);
520 r = strv_extend(&s->wants, m);
523 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) {
524 r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET);
531 log_unit_error(s->name,
532 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
533 s->path, line, m, strerror(-r));
535 if (!isempty(state_))
536 log_unit_error(s->name,
537 "[%s:%u] Trailing garbage in Provides, ignoring.",
540 } else if (startswith_no_case(t, "Required-Start:") ||
541 startswith_no_case(t, "Should-Start:") ||
542 startswith_no_case(t, "X-Start-Before:") ||
543 startswith_no_case(t, "X-Start-After:")) {
544 const char *word, *state_;
549 FOREACH_WORD_QUOTED(word, z, strchr(t, ':')+1, state_) {
550 _cleanup_free_ char *n = NULL, *m = NULL;
553 n = strndup(word, z);
557 r = sysv_translate_facility(n, basename(s->path), &m);
559 log_unit_error(s->name,
560 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
561 s->path, line, n, strerror(-r));
568 is_before = startswith_no_case(t, "X-Start-Before:");
570 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
571 /* the network-online target is special, as it needs to be actively pulled in */
572 r = strv_extend(&s->after, m);
575 r = strv_extend(&s->wants, m);
581 r = strv_extend(&s->before, m);
586 r = strv_extend(&s->after, m);
593 log_unit_error(s->name,
594 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
595 s->path, line, m, strerror(-r));
597 if (!isempty(state_))
598 log_unit_error(s->name,
599 "[%s:%u] Trailing garbage in %*s, ignoring.",
601 (int)(strchr(t, ':') - t), t);
603 } else if (startswith_no_case(t, "Description:")) {
606 state = LSB_DESCRIPTION;
616 free(long_description);
617 long_description = d;
619 } else if (startswith_no_case(t, "Short-Description:")) {
632 free(short_description);
633 short_description = d;
635 } else if (state == LSB_DESCRIPTION) {
637 if (startswith(l, "#\t") || startswith(l, "# ")) {
644 if (long_description)
645 d = strjoin(long_description, " ", t, NULL);
652 free(long_description);
653 long_description = d;
662 s->reload = supports_reload;
664 /* We use the long description only if
665 * no short description is set. */
667 if (short_description)
668 description = short_description;
669 else if (chkconfig_description)
670 description = chkconfig_description;
671 else if (long_description)
672 description = long_description;
679 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
689 static int fix_order(SysvStub *s, Hashmap *all_services) {
696 if (s->sysv_start_priority < 0)
699 HASHMAP_FOREACH(other, all_services, j) {
703 if (other->sysv_start_priority < 0)
706 /* If both units have modern headers we don't care
707 * about the priorities */
708 if (s->has_lsb && other->has_lsb)
711 if (other->sysv_start_priority < s->sysv_start_priority) {
712 r = strv_extend(&s->after, other->name);
716 else if (other->sysv_start_priority > s->sysv_start_priority) {
717 r = strv_extend(&s->before, other->name);
724 /* FIXME: Maybe we should compare the name here lexicographically? */
730 static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
733 STRV_FOREACH(path, lp.sysvinit_path) {
734 _cleanup_closedir_ DIR *d = NULL;
740 log_warning_errno(errno, "opendir(%s) failed: %m", *path);
744 while ((de = readdir(d))) {
745 _cleanup_free_ char *fpath = NULL, *name = NULL;
746 _cleanup_free_ SysvStub *service = NULL;
750 if (hidden_file(de->d_name))
753 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
754 log_warning_errno(errno, "stat() failed on %s/%s: %m", *path, de->d_name);
758 if (!(st.st_mode & S_IXUSR))
761 if (!S_ISREG(st.st_mode))
764 name = sysv_translate_name(de->d_name);
768 if (hashmap_contains(all_services, name))
771 fpath = strjoin(*path, "/", de->d_name, NULL);
775 service = new0(SysvStub, 1);
779 service->sysv_start_priority = -1;
780 service->name = name;
781 service->path = fpath;
783 r = hashmap_put(all_services, service->name, service);
795 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
798 _cleanup_closedir_ DIR *d = NULL;
799 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
802 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
803 _cleanup_set_free_ Set *shutdown_services = NULL;
806 STRV_FOREACH(p, lp.sysvrcnd_path)
807 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
811 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
821 log_warning_errno(errno, "opendir(%s) failed: %m", path);
826 while ((de = readdir(d))) {
829 if (hidden_file(de->d_name))
832 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
835 if (strlen(de->d_name) < 4)
838 a = undecchar(de->d_name[1]);
839 b = undecchar(de->d_name[2]);
845 fpath = strjoin(*p, "/", de->d_name, NULL);
851 name = sysv_translate_name(de->d_name + 3);
857 service = hashmap_get(all_services, name);
859 log_warning("Could not find init script for %s", name);
863 if (de->d_name[0] == 'S') {
865 if (rcnd_table[i].type == RUNLEVEL_UP) {
866 service->sysv_start_priority =
867 MAX(a*10 + b, service->sysv_start_priority);
870 r = set_ensure_allocated(&runlevel_services[i], NULL);
874 r = set_put(runlevel_services[i], service);
878 } else if (de->d_name[0] == 'K' &&
879 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
881 r = set_ensure_allocated(&shutdown_services, NULL);
885 r = set_put(shutdown_services, service);
893 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
894 SET_FOREACH(service, runlevel_services[i], j) {
895 r = strv_extend(&service->before, rcnd_table[i].target);
898 r = strv_extend(&service->wanted_by, rcnd_table[i].target);
903 SET_FOREACH(service, shutdown_services, j) {
904 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
907 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
916 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
917 set_free(runlevel_services[i]);
922 int main(int argc, char *argv[]) {
925 Hashmap *all_services;
929 if (argc > 1 && argc != 4) {
930 log_error("This program takes three or no arguments.");
937 log_set_target(LOG_TARGET_SAFE);
938 log_parse_environment();
943 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
945 log_error("Failed to find lookup paths.");
949 all_services = hashmap_new(&string_hash_ops);
955 r = enumerate_sysv(lp, all_services);
957 log_error("Failed to generate units for all init scripts.");
961 r = set_dependencies_from_rcnd(lp, all_services);
963 log_error("Failed to read runlevels from rcnd links.");
967 HASHMAP_FOREACH(service, all_services, j) {
968 q = load_sysv(service);
973 HASHMAP_FOREACH(service, all_services, j) {
974 q = fix_order(service, all_services);
978 q = generate_unit_file(service);