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 generate_unit_file(SysvStub *s) {
117 _cleanup_fclose_ FILE *f = NULL;
118 _cleanup_free_ char *unit = NULL;
119 _cleanup_free_ char *before = NULL;
120 _cleanup_free_ char *after = NULL;
121 _cleanup_free_ char *wants = NULL;
122 _cleanup_free_ char *conflicts = NULL;
125 before = strv_join(s->before, " ");
129 after = strv_join(s->after, " ");
133 wants = strv_join(s->wants, " ");
137 conflicts = strv_join(s->conflicts, " ");
141 unit = strjoin(arg_dest, "/", s->name, NULL);
145 f = fopen(unit, "wxe");
147 return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
150 "# Automatically generated by systemd-sysv-generator\n\n"
152 "Documentation=man:systemd-sysv-generator(8)\n"
155 s->path, s->description);
157 if (!isempty(before))
158 fprintf(f, "Before=%s\n", before);
160 fprintf(f, "After=%s\n", after);
162 fprintf(f, "Wants=%s\n", wants);
163 if (!isempty(conflicts))
164 fprintf(f, "Conflicts=%s\n", conflicts);
174 "RemainAfterExit=%s\n",
175 yes_no(!s->pid_file));
178 fprintf(f, "PIDFile=%s\n", s->pid_file);
181 "ExecStart=%s start\n"
182 "ExecStop=%s stop\n",
186 fprintf(f, "ExecReload=%s reload\n", s->path);
188 STRV_FOREACH(p, s->wanted_by) {
189 r = add_symlink(s->name, *p);
191 log_unit_error_errno(s->name, r, "Failed to create 'Wants' symlink to %s: %m", *p);
197 static bool usage_contains_reload(const char *line) {
198 return (strcasestr(line, "{reload|") ||
199 strcasestr(line, "{reload}") ||
200 strcasestr(line, "{reload\"") ||
201 strcasestr(line, "|reload|") ||
202 strcasestr(line, "|reload}") ||
203 strcasestr(line, "|reload\""));
206 static char *sysv_translate_name(const char *name) {
209 r = new(char, strlen(name) + strlen(".service") + 1);
213 if (endswith(name, ".sh"))
214 /* Drop .sh suffix */
215 strcpy(stpcpy(r, name) - 3, ".service");
217 /* Normal init script name */
218 strcpy(stpcpy(r, name), ".service");
223 static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
225 /* We silently ignore the $ prefix here. According to the LSB
226 * spec it simply indicates whether something is a
227 * standardized name or a distribution-specific one. Since we
228 * just follow what already exists and do not introduce new
229 * uses or names we don't care who introduced a new name. */
231 static const char * const table[] = {
232 /* LSB defined facilities */
234 "network", SPECIAL_NETWORK_ONLINE_TARGET,
235 "named", SPECIAL_NSS_LOOKUP_TARGET,
236 "portmap", SPECIAL_RPCBIND_TARGET,
237 "remote_fs", SPECIAL_REMOTE_FS_TARGET,
239 "time", SPECIAL_TIME_SYNC_TARGET,
249 n = *name == '$' ? name + 1 : name;
251 for (i = 0; i < ELEMENTSOF(table); i += 2) {
253 if (!streq(table[i], n))
259 r = strdup(table[i+1]);
266 /* If we don't know this name, fallback heuristics to figure
267 * out whether something is a target or a service alias. */
270 if (!unit_prefix_is_valid(n))
273 /* Facilities starting with $ are most likely targets */
274 r = unit_name_build(n, NULL, ".target");
275 } else if (filename && streq(name, filename))
276 /* Names equaling the file name of the services are redundant */
279 /* Everything else we assume to be normal service names */
280 r = sysv_translate_name(n);
291 static int load_sysv(SysvStub *s) {
292 _cleanup_fclose_ FILE *f;
302 _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
304 bool supports_reload = false;
308 f = fopen(s->path, "re");
310 return errno == ENOENT ? 0 : -errno;
313 char l[LINE_MAX], *t;
315 if (!fgets(l, sizeof(l), f)) {
319 log_unit_error(s->name,
320 "Failed to read configuration file '%s': %m",
329 /* Try to figure out whether this init script supports
330 * the reload operation. This heuristic looks for
331 * "Usage" lines which include the reload option. */
332 if ( state == USAGE_CONTINUATION ||
333 (state == NORMAL && strcasestr(t, "usage"))) {
334 if (usage_contains_reload(t)) {
335 supports_reload = true;
337 } else if (t[strlen(t)-1] == '\\')
338 state = USAGE_CONTINUATION;
346 if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
352 if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
358 t += strspn(t, WHITESPACE);
360 if (state == NORMAL) {
362 /* Try to parse Red Hat style description */
364 if (startswith_no_case(t, "description:")) {
366 size_t k = strlen(t);
370 if (t[k-1] == '\\') {
383 free(chkconfig_description);
384 chkconfig_description = d;
386 } else if (startswith_no_case(t, "pidfile:")) {
393 if (!path_is_absolute(fn)) {
394 log_unit_error(s->name,
395 "[%s:%u] PID file not absolute. Ignoring.",
408 } else if (state == DESCRIPTION) {
410 /* Try to parse Red Hat style description
413 size_t k = strlen(t);
425 if (chkconfig_description)
426 d = strjoin(chkconfig_description, " ", j, NULL);
433 free(chkconfig_description);
434 chkconfig_description = d;
437 } else if (state == LSB || state == LSB_DESCRIPTION) {
439 if (startswith_no_case(t, "Provides:")) {
440 const char *word, *state_;
445 FOREACH_WORD_QUOTED(word, z, t+9, state_) {
446 _cleanup_free_ char *n = NULL, *m = NULL;
448 n = strndup(word, z);
452 r = sysv_translate_facility(n, basename(s->path), &m);
460 if (unit_name_to_type(m) != UNIT_SERVICE) {
466 * indication that the
468 * now available. This
471 * targets do NOT pull
474 r = strv_extend(&s->before, m);
477 r = strv_extend(&s->wants, m);
480 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) {
481 r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET);
488 log_unit_error(s->name,
489 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
490 s->path, line, m, strerror(-r));
492 if (!isempty(state_))
493 log_unit_error(s->name,
494 "[%s:%u] Trailing garbage in Provides, ignoring.",
497 } else if (startswith_no_case(t, "Required-Start:") ||
498 startswith_no_case(t, "Should-Start:") ||
499 startswith_no_case(t, "X-Start-Before:") ||
500 startswith_no_case(t, "X-Start-After:")) {
501 const char *word, *state_;
506 FOREACH_WORD_QUOTED(word, z, strchr(t, ':')+1, state_) {
507 _cleanup_free_ char *n = NULL, *m = NULL;
510 n = strndup(word, z);
514 r = sysv_translate_facility(n, basename(s->path), &m);
516 log_unit_error(s->name,
517 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
518 s->path, line, n, strerror(-r));
525 is_before = startswith_no_case(t, "X-Start-Before:");
527 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
528 /* the network-online target is special, as it needs to be actively pulled in */
529 r = strv_extend(&s->after, m);
532 r = strv_extend(&s->wants, m);
538 r = strv_extend(&s->before, m);
543 r = strv_extend(&s->after, m);
550 log_unit_error(s->name,
551 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
552 s->path, line, m, strerror(-r));
554 if (!isempty(state_))
555 log_unit_error(s->name,
556 "[%s:%u] Trailing garbage in %*s, ignoring.",
558 (int)(strchr(t, ':') - t), t);
560 } else if (startswith_no_case(t, "Description:")) {
563 state = LSB_DESCRIPTION;
573 free(long_description);
574 long_description = d;
576 } else if (startswith_no_case(t, "Short-Description:")) {
589 free(short_description);
590 short_description = d;
592 } else if (state == LSB_DESCRIPTION) {
594 if (startswith(l, "#\t") || startswith(l, "# ")) {
601 if (long_description)
602 d = strjoin(long_description, " ", t, NULL);
609 free(long_description);
610 long_description = d;
619 s->reload = supports_reload;
621 /* We use the long description only if
622 * no short description is set. */
624 if (short_description)
625 description = short_description;
626 else if (chkconfig_description)
627 description = chkconfig_description;
628 else if (long_description)
629 description = long_description;
636 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
646 static int fix_order(SysvStub *s, Hashmap *all_services) {
653 if (s->sysv_start_priority < 0)
656 HASHMAP_FOREACH(other, all_services, j) {
660 if (other->sysv_start_priority < 0)
663 /* If both units have modern headers we don't care
664 * about the priorities */
665 if (s->has_lsb && other->has_lsb)
668 if (other->sysv_start_priority < s->sysv_start_priority) {
669 r = strv_extend(&s->after, other->name);
673 else if (other->sysv_start_priority > s->sysv_start_priority) {
674 r = strv_extend(&s->before, other->name);
681 /* FIXME: Maybe we should compare the name here lexicographically? */
687 static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
690 STRV_FOREACH(path, lp.sysvinit_path) {
691 _cleanup_closedir_ DIR *d = NULL;
697 log_warning_errno(errno, "opendir(%s) failed: %m", *path);
701 while ((de = readdir(d))) {
704 _cleanup_free_ char *fpath = NULL, *name = NULL;
707 if (hidden_file(de->d_name))
710 fpath = strjoin(*path, "/", de->d_name, NULL);
714 if (stat(fpath, &st) < 0)
717 if (!(st.st_mode & S_IXUSR))
720 name = sysv_translate_name(de->d_name);
724 if (hashmap_contains(all_services, name))
727 service = new0(SysvStub, 1);
731 service->sysv_start_priority = -1;
732 service->name = name;
733 service->path = fpath;
735 r = hashmap_put(all_services, service->name, service);
746 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
749 _cleanup_closedir_ DIR *d = NULL;
750 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
753 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
754 _cleanup_set_free_ Set *shutdown_services = NULL;
757 STRV_FOREACH(p, lp.sysvrcnd_path)
758 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
762 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
772 log_warning_errno(errno, "opendir(%s) failed: %m", path);
777 while ((de = readdir(d))) {
780 if (hidden_file(de->d_name))
783 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
786 if (strlen(de->d_name) < 4)
789 a = undecchar(de->d_name[1]);
790 b = undecchar(de->d_name[2]);
796 fpath = strjoin(*p, "/", de->d_name, NULL);
802 name = sysv_translate_name(de->d_name + 3);
808 service = hashmap_get(all_services, name);
810 log_warning("Could not find init script for %s", name);
814 if (de->d_name[0] == 'S') {
816 if (rcnd_table[i].type == RUNLEVEL_UP) {
817 service->sysv_start_priority =
818 MAX(a*10 + b, service->sysv_start_priority);
821 r = set_ensure_allocated(&runlevel_services[i], NULL);
825 r = set_put(runlevel_services[i], service);
829 } else if (de->d_name[0] == 'K' &&
830 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
832 r = set_ensure_allocated(&shutdown_services, NULL);
836 r = set_put(shutdown_services, service);
844 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
845 SET_FOREACH(service, runlevel_services[i], j) {
846 r = strv_extend(&service->before, rcnd_table[i].target);
849 r = strv_extend(&service->wanted_by, rcnd_table[i].target);
854 SET_FOREACH(service, shutdown_services, j) {
855 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
858 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
867 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
868 set_free(runlevel_services[i]);
873 int main(int argc, char *argv[]) {
876 Hashmap *all_services;
880 if (argc > 1 && argc != 4) {
881 log_error("This program takes three or no arguments.");
888 log_set_target(LOG_TARGET_SAFE);
889 log_parse_environment();
894 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
896 log_error("Failed to find lookup paths.");
900 all_services = hashmap_new(&string_hash_ops);
906 r = enumerate_sysv(lp, all_services);
908 log_error("Failed to generate units for all init scripts.");
912 r = set_dependencies_from_rcnd(lp, all_services);
914 log_error("Failed to read runlevels from rcnd links.");
918 HASHMAP_FOREACH(service, all_services, j) {
919 q = load_sysv(service);
923 q = fix_order(service, all_services);
927 q = generate_unit_file(service);