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"
36 #include "unit-name.h"
38 #include "exit-status.h"
44 typedef enum RunlevelType {
52 const RunlevelType type;
54 /* Standard SysV runlevels for start-up */
55 { "rc1.d", SPECIAL_RESCUE_TARGET, RUNLEVEL_UP },
56 { "rc2.d", SPECIAL_RUNLEVEL2_TARGET, RUNLEVEL_UP },
57 { "rc3.d", SPECIAL_RUNLEVEL3_TARGET, RUNLEVEL_UP },
58 { "rc4.d", SPECIAL_RUNLEVEL4_TARGET, RUNLEVEL_UP },
59 { "rc5.d", SPECIAL_RUNLEVEL5_TARGET, RUNLEVEL_UP },
61 /* Standard SysV runlevels for shutdown */
62 { "rc0.d", SPECIAL_POWEROFF_TARGET, RUNLEVEL_DOWN },
63 { "rc6.d", SPECIAL_REBOOT_TARGET, RUNLEVEL_DOWN }
65 /* Note that the order here matters, as we read the
66 directories in this order, and we want to make sure that
67 sysv_start_priority is known when we first load the
68 unit. And that value we only know from S links. Hence
69 UP must be read before DOWN */
72 typedef struct SysvStub {
76 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 generate_unit_file(SysvStub *s) {
118 _cleanup_fclose_ FILE *f = NULL;
119 _cleanup_free_ char *before = NULL;
120 _cleanup_free_ char *after = NULL;
121 _cleanup_free_ char *conflicts = NULL;
124 before = strv_join(s->before, " ");
128 after = strv_join(s->after, " ");
132 conflicts = strv_join(s->conflicts, " ");
136 unit = strjoin(arg_dest, "/", s->name, NULL);
140 f = fopen(unit, "wxe");
142 log_error("Failed to create unit file %s: %m", unit);
147 "# Automatically generated by systemd-sysv-generator\n\n"
151 s->path, s->description);
153 if (!isempty(before))
154 fprintf(f, "Before=%s\n", before);
156 fprintf(f, "After=%s\n", after);
157 if (!isempty(conflicts))
158 fprintf(f, "Conflicts=%s\n", conflicts);
168 "RemainAfterExit=%s\n",
169 yes_no(!s->pid_file));
171 if (s->sysv_start_priority > 0)
172 fprintf(f, "SysVStartPriority=%d\n", s->sysv_start_priority);
175 fprintf(f, "PidFile=%s\n", s->pid_file);
178 "ExecStart=%s start\n"
179 "ExecStop=%s stop\n",
183 fprintf(f, "ExecReload=%s reload\n", s->path);
185 STRV_FOREACH(p, s->wants) {
186 r = add_symlink(s->name, *p);
188 log_error_unit(s->name, "Failed to create 'Wants' symlink to %s: %s", *p, strerror(-r));
194 static bool usage_contains_reload(const char *line) {
195 return (strcasestr(line, "{reload|") ||
196 strcasestr(line, "{reload}") ||
197 strcasestr(line, "{reload\"") ||
198 strcasestr(line, "|reload|") ||
199 strcasestr(line, "|reload}") ||
200 strcasestr(line, "|reload\""));
203 static char *sysv_translate_name(const char *name) {
206 r = new(char, strlen(name) + strlen(".service") + 1);
210 if (endswith(name, ".sh"))
211 /* Drop .sh suffix */
212 strcpy(stpcpy(r, name) - 3, ".service");
214 /* Normal init script name */
215 strcpy(stpcpy(r, name), ".service");
220 static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
222 /* We silently ignore the $ prefix here. According to the LSB
223 * spec it simply indicates whether something is a
224 * standardized name or a distribution-specific one. Since we
225 * just follow what already exists and do not introduce new
226 * uses or names we don't care who introduced a new name. */
228 static const char * const table[] = {
229 /* LSB defined facilities */
231 "network", SPECIAL_NETWORK_ONLINE_TARGET,
232 "named", SPECIAL_NSS_LOOKUP_TARGET,
233 "portmap", SPECIAL_RPCBIND_TARGET,
234 "remote_fs", SPECIAL_REMOTE_FS_TARGET,
236 "time", SPECIAL_TIME_SYNC_TARGET,
246 n = *name == '$' ? name + 1 : name;
248 for (i = 0; i < ELEMENTSOF(table); i += 2) {
250 if (!streq(table[i], n))
256 r = strdup(table[i+1]);
263 /* If we don't know this name, fallback heuristics to figure
264 * out whether something is a target or a service alias. */
267 if (!unit_prefix_is_valid(n))
270 /* Facilities starting with $ are most likely targets */
271 r = unit_name_build(n, NULL, ".target");
272 } else if (filename && streq(name, filename))
273 /* Names equaling the file name of the services are redundant */
276 /* Everything else we assume to be normal service names */
277 r = sysv_translate_name(n);
288 static int load_sysv(SysvStub *s) {
289 _cleanup_fclose_ FILE *f;
299 _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
301 bool supports_reload = false;
305 f = fopen(s->path, "re");
307 return errno == ENOENT ? 0 : -errno;
310 char l[LINE_MAX], *t;
312 if (!fgets(l, sizeof(l), f)) {
316 log_error_unit(s->name,
317 "Failed to read configuration file '%s': %m",
326 /* Try to figure out whether this init script supports
327 * the reload operation. This heuristic looks for
328 * "Usage" lines which include the reload option. */
329 if ( state == USAGE_CONTINUATION ||
330 (state == NORMAL && strcasestr(t, "usage"))) {
331 if (usage_contains_reload(t)) {
332 supports_reload = true;
334 } else if (t[strlen(t)-1] == '\\')
335 state = USAGE_CONTINUATION;
343 if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
349 if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
355 t += strspn(t, WHITESPACE);
357 if (state == NORMAL) {
359 /* Try to parse Red Hat style description */
361 if (startswith_no_case(t, "description:")) {
363 size_t k = strlen(t);
367 if (t[k-1] == '\\') {
380 free(chkconfig_description);
381 chkconfig_description = d;
383 } else if (startswith_no_case(t, "pidfile:")) {
390 if (!path_is_absolute(fn)) {
391 log_error_unit(s->name,
392 "[%s:%u] PID file not absolute. Ignoring.",
405 } else if (state == DESCRIPTION) {
407 /* Try to parse Red Hat style description
410 size_t k = strlen(t);
422 if (chkconfig_description)
423 d = strjoin(chkconfig_description, " ", j, NULL);
430 free(chkconfig_description);
431 chkconfig_description = d;
434 } else if (state == LSB || state == LSB_DESCRIPTION) {
436 if (startswith_no_case(t, "Provides:")) {
442 FOREACH_WORD_QUOTED(w, z, t+9, i) {
443 _cleanup_free_ char *n = NULL, *m = NULL;
449 r = sysv_translate_facility(n, basename(s->path), &m);
457 if (unit_name_to_type(m) != UNIT_SERVICE) {
463 * indication that the
465 * now available. This
468 * targets do NOT pull
471 r = strv_extend(&s->before, m);
474 r = strv_extend(&s->wants, m);
480 log_error_unit(s->name,
481 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
482 s->path, line, m, strerror(-r));
485 } else if (startswith_no_case(t, "Required-Start:") ||
486 startswith_no_case(t, "Should-Start:") ||
487 startswith_no_case(t, "X-Start-Before:") ||
488 startswith_no_case(t, "X-Start-After:")) {
494 FOREACH_WORD_QUOTED(w, z, strchr(t, ':')+1, i) {
495 _cleanup_free_ char *n = NULL, *m = NULL;
502 r = sysv_translate_facility(n, basename(s->path), &m);
504 log_error_unit(s->name,
505 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
506 s->path, line, n, strerror(-r));
513 is_before = startswith_no_case(t, "X-Start-Before:");
515 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
516 /* the network-online target is special, as it needs to be actively pulled in */
517 r = strv_extend(&s->after, m);
520 r = strv_extend(&s->wants, m);
526 r = strv_extend(&s->before, m);
531 r = strv_extend(&s->after, m);
538 log_error_unit(s->name,
539 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
540 s->path, line, m, strerror(-r));
543 } else if (startswith_no_case(t, "Description:")) {
546 state = LSB_DESCRIPTION;
556 free(long_description);
557 long_description = d;
559 } else if (startswith_no_case(t, "Short-Description:")) {
572 free(short_description);
573 short_description = d;
575 } else if (state == LSB_DESCRIPTION) {
577 if (startswith(l, "#\t") || startswith(l, "# ")) {
584 if (long_description)
585 d = strjoin(long_description, " ", t, NULL);
592 free(long_description);
593 long_description = d;
602 s->reload = supports_reload;
604 /* We use the long description only if
605 * no short description is set. */
607 if (short_description)
608 description = short_description;
609 else if (chkconfig_description)
610 description = chkconfig_description;
611 else if (long_description)
612 description = long_description;
619 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
629 static int fix_order(SysvStub *s, Hashmap *all_services) {
636 if (s->sysv_start_priority < 0)
639 HASHMAP_FOREACH(other, all_services, j) {
643 if (other->sysv_start_priority < 0)
646 /* If both units have modern headers we don't care
647 * about the priorities */
648 if (s->has_lsb && other->has_lsb)
651 if (other->sysv_start_priority < s->sysv_start_priority) {
652 r = strv_extend(&s->after, other->name);
656 else if (other->sysv_start_priority > s->sysv_start_priority) {
657 r = strv_extend(&s->before, other->name);
664 /* FIXME: Maybe we should compare the name here lexicographically? */
670 static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
673 STRV_FOREACH(path, lp.sysvinit_path) {
674 _cleanup_closedir_ DIR *d = NULL;
680 log_warning("opendir(%s) failed: %m", *path);
684 while ((de = readdir(d))) {
687 _cleanup_free_ char *fpath = NULL, *name = NULL;
690 if (ignore_file(de->d_name))
693 fpath = strjoin(*path, "/", de->d_name, NULL);
697 if (stat(fpath, &st) < 0)
700 if (!(st.st_mode & S_IXUSR))
703 name = sysv_translate_name(de->d_name);
707 if (hashmap_contains(all_services, name))
710 service = new0(SysvStub, 1);
714 service->sysv_start_priority = -1;
715 service->name = name;
716 service->path = fpath;
718 r = hashmap_put(all_services, service->name, service);
729 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
732 _cleanup_closedir_ DIR *d = NULL;
733 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
736 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
737 _cleanup_set_free_ Set *shutdown_services = NULL;
740 STRV_FOREACH(p, lp.sysvrcnd_path)
741 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
745 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
755 log_warning("opendir(%s) failed: %m", path);
760 while ((de = readdir(d))) {
763 if (ignore_file(de->d_name))
766 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
769 if (strlen(de->d_name) < 4)
772 a = undecchar(de->d_name[1]);
773 b = undecchar(de->d_name[2]);
779 fpath = strjoin(*p, "/", de->d_name, NULL);
785 name = sysv_translate_name(de->d_name + 3);
791 if (hashmap_contains(all_services, name))
792 service = hashmap_get(all_services, name);
794 log_warning("Could not find init scirpt for %s", name);
798 if (de->d_name[0] == 'S') {
800 if (rcnd_table[i].type == RUNLEVEL_UP) {
801 service->sysv_start_priority =
802 MAX(a*10 + b, service->sysv_start_priority);
805 r = set_ensure_allocated(&runlevel_services[i],
806 trivial_hash_func, trivial_compare_func);
810 r = set_put(runlevel_services[i], service);
814 } else if (de->d_name[0] == 'K' &&
815 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
817 r = set_ensure_allocated(&shutdown_services,
818 trivial_hash_func, trivial_compare_func);
822 r = set_put(shutdown_services, service);
830 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
831 SET_FOREACH(service, runlevel_services[i], j) {
832 r = strv_extend(&service->before, rcnd_table[i].target);
835 r = strv_extend(&service->wants, rcnd_table[i].target);
840 SET_FOREACH(service, shutdown_services, j) {
841 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
844 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
853 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
854 set_free(runlevel_services[i]);
859 int main(int argc, char *argv[]) {
862 Hashmap *all_services;
866 if (argc > 1 && argc != 4) {
867 log_error("This program takes three or no arguments.");
874 log_set_target(LOG_TARGET_SAFE);
875 log_parse_environment();
880 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
882 log_error("Failed to find lookup paths.");
886 all_services = hashmap_new(string_hash_func, string_compare_func);
892 r = enumerate_sysv(lp, all_services);
894 log_error("Failed to generate units for all init scripts.");
898 r = set_dependencies_from_rcnd(lp, all_services);
900 log_error("Failed to read runlevels from rcnd links.");
904 HASHMAP_FOREACH(service, all_services, j) {
905 q = load_sysv(service);
909 q = fix_order(service, all_services);
913 q = generate_unit_file(service);