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 link = strjoin(arg_dest, "/", alias, NULL);
126 r = symlink(service, link);
136 static int generate_unit_file(SysvStub *s) {
138 _cleanup_fclose_ FILE *f = NULL;
139 _cleanup_free_ char *unit = NULL;
140 _cleanup_free_ char *before = NULL;
141 _cleanup_free_ char *after = NULL;
142 _cleanup_free_ char *wants = NULL;
143 _cleanup_free_ char *conflicts = NULL;
146 before = strv_join(s->before, " ");
150 after = strv_join(s->after, " ");
154 wants = strv_join(s->wants, " ");
158 conflicts = strv_join(s->conflicts, " ");
162 unit = strjoin(arg_dest, "/", s->name, NULL);
166 f = fopen(unit, "wxe");
168 return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
171 "# Automatically generated by systemd-sysv-generator\n\n"
173 "Documentation=man:systemd-sysv-generator(8)\n"
176 s->path, s->description);
178 if (!isempty(before))
179 fprintf(f, "Before=%s\n", before);
181 fprintf(f, "After=%s\n", after);
183 fprintf(f, "Wants=%s\n", wants);
184 if (!isempty(conflicts))
185 fprintf(f, "Conflicts=%s\n", conflicts);
195 "RemainAfterExit=%s\n",
196 yes_no(!s->pid_file));
199 fprintf(f, "PIDFile=%s\n", s->pid_file);
202 "ExecStart=%s start\n"
203 "ExecStop=%s stop\n",
207 fprintf(f, "ExecReload=%s reload\n", s->path);
209 STRV_FOREACH(p, s->wanted_by) {
210 r = add_symlink(s->name, *p);
212 log_unit_error_errno(s->name, r, "Failed to create 'Wants' symlink to %s: %m", *p);
218 static bool usage_contains_reload(const char *line) {
219 return (strcasestr(line, "{reload|") ||
220 strcasestr(line, "{reload}") ||
221 strcasestr(line, "{reload\"") ||
222 strcasestr(line, "|reload|") ||
223 strcasestr(line, "|reload}") ||
224 strcasestr(line, "|reload\""));
227 static char *sysv_translate_name(const char *name) {
230 r = new(char, strlen(name) + strlen(".service") + 1);
234 if (endswith(name, ".sh"))
235 /* Drop .sh suffix */
236 strcpy(stpcpy(r, name) - 3, ".service");
238 /* Normal init script name */
239 strcpy(stpcpy(r, name), ".service");
244 static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
246 /* We silently ignore the $ prefix here. According to the LSB
247 * spec it simply indicates whether something is a
248 * standardized name or a distribution-specific one. Since we
249 * just follow what already exists and do not introduce new
250 * uses or names we don't care who introduced a new name. */
252 static const char * const table[] = {
253 /* LSB defined facilities */
255 "network", SPECIAL_NETWORK_ONLINE_TARGET,
256 "named", SPECIAL_NSS_LOOKUP_TARGET,
257 "portmap", SPECIAL_RPCBIND_TARGET,
258 "remote_fs", SPECIAL_REMOTE_FS_TARGET,
260 "time", SPECIAL_TIME_SYNC_TARGET,
270 n = *name == '$' ? name + 1 : name;
272 for (i = 0; i < ELEMENTSOF(table); i += 2) {
274 if (!streq(table[i], n))
280 r = strdup(table[i+1]);
287 /* If we don't know this name, fallback heuristics to figure
288 * out whether something is a target or a service alias. */
291 if (!unit_prefix_is_valid(n))
294 /* Facilities starting with $ are most likely targets */
295 r = unit_name_build(n, NULL, ".target");
296 } else if (filename && streq(name, filename))
297 /* Names equaling the file name of the services are redundant */
300 /* Everything else we assume to be normal service names */
301 r = sysv_translate_name(n);
312 static int load_sysv(SysvStub *s) {
313 _cleanup_fclose_ FILE *f;
323 _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
325 bool supports_reload = false;
329 f = fopen(s->path, "re");
331 return errno == ENOENT ? 0 : -errno;
334 char l[LINE_MAX], *t;
336 if (!fgets(l, sizeof(l), f)) {
340 log_unit_error(s->name,
341 "Failed to read configuration file '%s': %m",
350 /* Try to figure out whether this init script supports
351 * the reload operation. This heuristic looks for
352 * "Usage" lines which include the reload option. */
353 if ( state == USAGE_CONTINUATION ||
354 (state == NORMAL && strcasestr(t, "usage"))) {
355 if (usage_contains_reload(t)) {
356 supports_reload = true;
358 } else if (t[strlen(t)-1] == '\\')
359 state = USAGE_CONTINUATION;
367 if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
373 if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
379 t += strspn(t, WHITESPACE);
381 if (state == NORMAL) {
383 /* Try to parse Red Hat style description */
385 if (startswith_no_case(t, "description:")) {
387 size_t k = strlen(t);
391 if (t[k-1] == '\\') {
404 free(chkconfig_description);
405 chkconfig_description = d;
407 } else if (startswith_no_case(t, "pidfile:")) {
414 if (!path_is_absolute(fn)) {
415 log_unit_error(s->name,
416 "[%s:%u] PID file not absolute. Ignoring.",
429 } else if (state == DESCRIPTION) {
431 /* Try to parse Red Hat style description
434 size_t k = strlen(t);
446 if (chkconfig_description)
447 d = strjoin(chkconfig_description, " ", j, NULL);
454 free(chkconfig_description);
455 chkconfig_description = d;
458 } else if (state == LSB || state == LSB_DESCRIPTION) {
460 if (startswith_no_case(t, "Provides:")) {
461 const char *word, *state_;
466 FOREACH_WORD_QUOTED(word, z, t+9, state_) {
467 _cleanup_free_ char *n = NULL, *m = NULL;
469 n = strndup(word, z);
473 r = sysv_translate_facility(n, basename(s->path), &m);
481 if (unit_name_to_type(m) == UNIT_SERVICE) {
482 r = add_alias(s->name, m);
489 * indication that the
491 * now available. This
494 * targets do NOT pull
497 r = strv_extend(&s->before, m);
500 r = strv_extend(&s->wants, m);
503 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) {
504 r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET);
511 log_unit_error(s->name,
512 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
513 s->path, line, m, strerror(-r));
515 if (!isempty(state_))
516 log_unit_error(s->name,
517 "[%s:%u] Trailing garbage in Provides, ignoring.",
520 } else if (startswith_no_case(t, "Required-Start:") ||
521 startswith_no_case(t, "Should-Start:") ||
522 startswith_no_case(t, "X-Start-Before:") ||
523 startswith_no_case(t, "X-Start-After:")) {
524 const char *word, *state_;
529 FOREACH_WORD_QUOTED(word, z, strchr(t, ':')+1, state_) {
530 _cleanup_free_ char *n = NULL, *m = NULL;
533 n = strndup(word, z);
537 r = sysv_translate_facility(n, basename(s->path), &m);
539 log_unit_error(s->name,
540 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
541 s->path, line, n, strerror(-r));
548 is_before = startswith_no_case(t, "X-Start-Before:");
550 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
551 /* the network-online target is special, as it needs to be actively pulled in */
552 r = strv_extend(&s->after, m);
555 r = strv_extend(&s->wants, m);
561 r = strv_extend(&s->before, m);
566 r = strv_extend(&s->after, m);
573 log_unit_error(s->name,
574 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
575 s->path, line, m, strerror(-r));
577 if (!isempty(state_))
578 log_unit_error(s->name,
579 "[%s:%u] Trailing garbage in %*s, ignoring.",
581 (int)(strchr(t, ':') - t), t);
583 } else if (startswith_no_case(t, "Description:")) {
586 state = LSB_DESCRIPTION;
596 free(long_description);
597 long_description = d;
599 } else if (startswith_no_case(t, "Short-Description:")) {
612 free(short_description);
613 short_description = d;
615 } else if (state == LSB_DESCRIPTION) {
617 if (startswith(l, "#\t") || startswith(l, "# ")) {
624 if (long_description)
625 d = strjoin(long_description, " ", t, NULL);
632 free(long_description);
633 long_description = d;
642 s->reload = supports_reload;
644 /* We use the long description only if
645 * no short description is set. */
647 if (short_description)
648 description = short_description;
649 else if (chkconfig_description)
650 description = chkconfig_description;
651 else if (long_description)
652 description = long_description;
659 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
669 static int fix_order(SysvStub *s, Hashmap *all_services) {
676 if (s->sysv_start_priority < 0)
679 HASHMAP_FOREACH(other, all_services, j) {
683 if (other->sysv_start_priority < 0)
686 /* If both units have modern headers we don't care
687 * about the priorities */
688 if (s->has_lsb && other->has_lsb)
691 if (other->sysv_start_priority < s->sysv_start_priority) {
692 r = strv_extend(&s->after, other->name);
696 else if (other->sysv_start_priority > s->sysv_start_priority) {
697 r = strv_extend(&s->before, other->name);
704 /* FIXME: Maybe we should compare the name here lexicographically? */
710 static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
713 STRV_FOREACH(path, lp.sysvinit_path) {
714 _cleanup_closedir_ DIR *d = NULL;
720 log_warning_errno(errno, "opendir(%s) failed: %m", *path);
724 while ((de = readdir(d))) {
727 _cleanup_free_ char *fpath = NULL, *name = NULL;
730 if (hidden_file(de->d_name))
733 fpath = strjoin(*path, "/", de->d_name, NULL);
737 if (stat(fpath, &st) < 0)
740 if (!(st.st_mode & S_IXUSR))
743 name = sysv_translate_name(de->d_name);
747 if (hashmap_contains(all_services, name))
750 service = new0(SysvStub, 1);
754 service->sysv_start_priority = -1;
755 service->name = name;
756 service->path = fpath;
758 r = hashmap_put(all_services, service->name, service);
769 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
772 _cleanup_closedir_ DIR *d = NULL;
773 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
776 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
777 _cleanup_set_free_ Set *shutdown_services = NULL;
780 STRV_FOREACH(p, lp.sysvrcnd_path)
781 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
785 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
795 log_warning_errno(errno, "opendir(%s) failed: %m", path);
800 while ((de = readdir(d))) {
803 if (hidden_file(de->d_name))
806 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
809 if (strlen(de->d_name) < 4)
812 a = undecchar(de->d_name[1]);
813 b = undecchar(de->d_name[2]);
819 fpath = strjoin(*p, "/", de->d_name, NULL);
825 name = sysv_translate_name(de->d_name + 3);
831 service = hashmap_get(all_services, name);
833 log_warning("Could not find init script for %s", name);
837 if (de->d_name[0] == 'S') {
839 if (rcnd_table[i].type == RUNLEVEL_UP) {
840 service->sysv_start_priority =
841 MAX(a*10 + b, service->sysv_start_priority);
844 r = set_ensure_allocated(&runlevel_services[i], NULL);
848 r = set_put(runlevel_services[i], service);
852 } else if (de->d_name[0] == 'K' &&
853 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
855 r = set_ensure_allocated(&shutdown_services, NULL);
859 r = set_put(shutdown_services, service);
867 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
868 SET_FOREACH(service, runlevel_services[i], j) {
869 r = strv_extend(&service->before, rcnd_table[i].target);
872 r = strv_extend(&service->wanted_by, rcnd_table[i].target);
877 SET_FOREACH(service, shutdown_services, j) {
878 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
881 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
890 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
891 set_free(runlevel_services[i]);
896 int main(int argc, char *argv[]) {
899 Hashmap *all_services;
903 if (argc > 1 && argc != 4) {
904 log_error("This program takes three or no arguments.");
911 log_set_target(LOG_TARGET_SAFE);
912 log_parse_environment();
917 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
919 log_error("Failed to find lookup paths.");
923 all_services = hashmap_new(&string_hash_ops);
929 r = enumerate_sysv(lp, all_services);
931 log_error("Failed to generate units for all init scripts.");
935 r = set_dependencies_from_rcnd(lp, all_services);
937 log_error("Failed to read runlevels from rcnd links.");
941 HASHMAP_FOREACH(service, all_services, j) {
942 q = load_sysv(service);
946 q = fix_order(service, all_services);
950 q = generate_unit_file(service);