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 f = fopen(unit, "wxe");
173 return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
176 "# Automatically generated by systemd-sysv-generator\n\n"
178 "Documentation=man:systemd-sysv-generator(8)\n"
181 s->path, s->description);
183 if (!isempty(before))
184 fprintf(f, "Before=%s\n", before);
186 fprintf(f, "After=%s\n", after);
188 fprintf(f, "Wants=%s\n", wants);
189 if (!isempty(conflicts))
190 fprintf(f, "Conflicts=%s\n", conflicts);
200 "RemainAfterExit=%s\n",
201 yes_no(!s->pid_file));
204 fprintf(f, "PIDFile=%s\n", s->pid_file);
207 "ExecStart=%s start\n"
208 "ExecStop=%s stop\n",
212 fprintf(f, "ExecReload=%s reload\n", s->path);
214 STRV_FOREACH(p, s->wanted_by) {
215 r = add_symlink(s->name, *p);
217 log_unit_error_errno(s->name, r, "Failed to create 'Wants' symlink to %s: %m", *p);
223 static bool usage_contains_reload(const char *line) {
224 return (strcasestr(line, "{reload|") ||
225 strcasestr(line, "{reload}") ||
226 strcasestr(line, "{reload\"") ||
227 strcasestr(line, "|reload|") ||
228 strcasestr(line, "|reload}") ||
229 strcasestr(line, "|reload\""));
232 static char *sysv_translate_name(const char *name) {
235 r = new(char, strlen(name) + strlen(".service") + 1);
239 if (endswith(name, ".sh"))
240 /* Drop .sh suffix */
241 strcpy(stpcpy(r, name) - 3, ".service");
243 /* Normal init script name */
244 strcpy(stpcpy(r, name), ".service");
249 static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
251 /* We silently ignore the $ prefix here. According to the LSB
252 * spec it simply indicates whether something is a
253 * standardized name or a distribution-specific one. Since we
254 * just follow what already exists and do not introduce new
255 * uses or names we don't care who introduced a new name. */
257 static const char * const table[] = {
258 /* LSB defined facilities */
260 "network", SPECIAL_NETWORK_ONLINE_TARGET,
261 "named", SPECIAL_NSS_LOOKUP_TARGET,
262 "portmap", SPECIAL_RPCBIND_TARGET,
263 "remote_fs", SPECIAL_REMOTE_FS_TARGET,
265 "time", SPECIAL_TIME_SYNC_TARGET,
271 _cleanup_free_ char *filename_no_sh = NULL;
276 n = *name == '$' ? name + 1 : name;
278 for (i = 0; i < ELEMENTSOF(table); i += 2) {
280 if (!streq(table[i], n))
286 r = strdup(table[i+1]);
293 /* strip ".sh" suffix from file name for comparison */
294 filename_no_sh = strdup(filename);
297 if (endswith(filename, ".sh"))
298 filename_no_sh[strlen(filename)-3] = '\0';
300 /* If we don't know this name, fallback heuristics to figure
301 * out whether something is a target or a service alias. */
304 if (!unit_prefix_is_valid(n))
307 /* Facilities starting with $ are most likely targets */
308 r = unit_name_build(n, NULL, ".target");
309 } else if (filename && streq(name, filename_no_sh))
310 /* Names equaling the file name of the services are redundant */
313 /* Everything else we assume to be normal service names */
314 r = sysv_translate_name(n);
325 static int load_sysv(SysvStub *s) {
326 _cleanup_fclose_ FILE *f;
336 _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
338 bool supports_reload = false;
342 f = fopen(s->path, "re");
344 return errno == ENOENT ? 0 : -errno;
347 char l[LINE_MAX], *t;
349 if (!fgets(l, sizeof(l), f)) {
353 log_unit_error(s->name,
354 "Failed to read configuration file '%s': %m",
363 /* Try to figure out whether this init script supports
364 * the reload operation. This heuristic looks for
365 * "Usage" lines which include the reload option. */
366 if ( state == USAGE_CONTINUATION ||
367 (state == NORMAL && strcasestr(t, "usage"))) {
368 if (usage_contains_reload(t)) {
369 supports_reload = true;
371 } else if (t[strlen(t)-1] == '\\')
372 state = USAGE_CONTINUATION;
380 if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
386 if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
392 t += strspn(t, WHITESPACE);
394 if (state == NORMAL) {
396 /* Try to parse Red Hat style description */
398 if (startswith_no_case(t, "description:")) {
400 size_t k = strlen(t);
404 if (t[k-1] == '\\') {
417 free(chkconfig_description);
418 chkconfig_description = d;
420 } else if (startswith_no_case(t, "pidfile:")) {
427 if (!path_is_absolute(fn)) {
428 log_unit_error(s->name,
429 "[%s:%u] PID file not absolute. Ignoring.",
442 } else if (state == DESCRIPTION) {
444 /* Try to parse Red Hat style description
447 size_t k = strlen(t);
459 if (chkconfig_description)
460 d = strjoin(chkconfig_description, " ", j, NULL);
467 free(chkconfig_description);
468 chkconfig_description = d;
471 } else if (state == LSB || state == LSB_DESCRIPTION) {
473 if (startswith_no_case(t, "Provides:")) {
474 const char *word, *state_;
479 FOREACH_WORD_QUOTED(word, z, t+9, state_) {
480 _cleanup_free_ char *n = NULL, *m = NULL;
482 n = strndup(word, z);
486 r = sysv_translate_facility(n, basename(s->path), &m);
494 if (unit_name_to_type(m) == UNIT_SERVICE) {
495 r = add_alias(s->name, m);
502 * indication that the
504 * now available. This
507 * targets do NOT pull
510 r = strv_extend(&s->before, m);
513 r = strv_extend(&s->wants, m);
516 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) {
517 r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET);
524 log_unit_error(s->name,
525 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
526 s->path, line, m, strerror(-r));
528 if (!isempty(state_))
529 log_unit_error(s->name,
530 "[%s:%u] Trailing garbage in Provides, ignoring.",
533 } else if (startswith_no_case(t, "Required-Start:") ||
534 startswith_no_case(t, "Should-Start:") ||
535 startswith_no_case(t, "X-Start-Before:") ||
536 startswith_no_case(t, "X-Start-After:")) {
537 const char *word, *state_;
542 FOREACH_WORD_QUOTED(word, z, strchr(t, ':')+1, state_) {
543 _cleanup_free_ char *n = NULL, *m = NULL;
546 n = strndup(word, z);
550 r = sysv_translate_facility(n, basename(s->path), &m);
552 log_unit_error(s->name,
553 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
554 s->path, line, n, strerror(-r));
561 is_before = startswith_no_case(t, "X-Start-Before:");
563 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
564 /* the network-online target is special, as it needs to be actively pulled in */
565 r = strv_extend(&s->after, m);
568 r = strv_extend(&s->wants, m);
574 r = strv_extend(&s->before, m);
579 r = strv_extend(&s->after, m);
586 log_unit_error(s->name,
587 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
588 s->path, line, m, strerror(-r));
590 if (!isempty(state_))
591 log_unit_error(s->name,
592 "[%s:%u] Trailing garbage in %*s, ignoring.",
594 (int)(strchr(t, ':') - t), t);
596 } else if (startswith_no_case(t, "Description:")) {
599 state = LSB_DESCRIPTION;
609 free(long_description);
610 long_description = d;
612 } else if (startswith_no_case(t, "Short-Description:")) {
625 free(short_description);
626 short_description = d;
628 } else if (state == LSB_DESCRIPTION) {
630 if (startswith(l, "#\t") || startswith(l, "# ")) {
637 if (long_description)
638 d = strjoin(long_description, " ", t, NULL);
645 free(long_description);
646 long_description = d;
655 s->reload = supports_reload;
657 /* We use the long description only if
658 * no short description is set. */
660 if (short_description)
661 description = short_description;
662 else if (chkconfig_description)
663 description = chkconfig_description;
664 else if (long_description)
665 description = long_description;
672 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
682 static int fix_order(SysvStub *s, Hashmap *all_services) {
689 if (s->sysv_start_priority < 0)
692 HASHMAP_FOREACH(other, all_services, j) {
696 if (other->sysv_start_priority < 0)
699 /* If both units have modern headers we don't care
700 * about the priorities */
701 if (s->has_lsb && other->has_lsb)
704 if (other->sysv_start_priority < s->sysv_start_priority) {
705 r = strv_extend(&s->after, other->name);
709 else if (other->sysv_start_priority > s->sysv_start_priority) {
710 r = strv_extend(&s->before, other->name);
717 /* FIXME: Maybe we should compare the name here lexicographically? */
723 static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
726 STRV_FOREACH(path, lp.sysvinit_path) {
727 _cleanup_closedir_ DIR *d = NULL;
733 log_warning_errno(errno, "opendir(%s) failed: %m", *path);
737 while ((de = readdir(d))) {
738 _cleanup_free_ char *fpath = NULL, *name = NULL;
739 _cleanup_free_ SysvStub *service = NULL;
743 if (hidden_file(de->d_name))
746 if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) {
747 log_warning_errno(errno, "stat() failed on %s/%s: %m", *path, de->d_name);
751 if (!(st.st_mode & S_IXUSR))
754 if (!S_ISREG(st.st_mode))
757 name = sysv_translate_name(de->d_name);
761 fpath = strjoin(*path, "/", de->d_name, NULL);
765 if (hashmap_contains(all_services, name))
768 service = new0(SysvStub, 1);
772 service->sysv_start_priority = -1;
773 service->name = name;
774 service->path = fpath;
776 r = hashmap_put(all_services, service->name, service);
788 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
791 _cleanup_closedir_ DIR *d = NULL;
792 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
795 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
796 _cleanup_set_free_ Set *shutdown_services = NULL;
799 STRV_FOREACH(p, lp.sysvrcnd_path)
800 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
804 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
814 log_warning_errno(errno, "opendir(%s) failed: %m", path);
819 while ((de = readdir(d))) {
822 if (hidden_file(de->d_name))
825 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
828 if (strlen(de->d_name) < 4)
831 a = undecchar(de->d_name[1]);
832 b = undecchar(de->d_name[2]);
838 fpath = strjoin(*p, "/", de->d_name, NULL);
844 name = sysv_translate_name(de->d_name + 3);
850 service = hashmap_get(all_services, name);
852 log_warning("Could not find init script for %s", name);
856 if (de->d_name[0] == 'S') {
858 if (rcnd_table[i].type == RUNLEVEL_UP) {
859 service->sysv_start_priority =
860 MAX(a*10 + b, service->sysv_start_priority);
863 r = set_ensure_allocated(&runlevel_services[i], NULL);
867 r = set_put(runlevel_services[i], service);
871 } else if (de->d_name[0] == 'K' &&
872 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
874 r = set_ensure_allocated(&shutdown_services, NULL);
878 r = set_put(shutdown_services, service);
886 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
887 SET_FOREACH(service, runlevel_services[i], j) {
888 r = strv_extend(&service->before, rcnd_table[i].target);
891 r = strv_extend(&service->wanted_by, rcnd_table[i].target);
896 SET_FOREACH(service, shutdown_services, j) {
897 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
900 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
909 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
910 set_free(runlevel_services[i]);
915 int main(int argc, char *argv[]) {
918 Hashmap *all_services;
922 if (argc > 1 && argc != 4) {
923 log_error("This program takes three or no arguments.");
930 log_set_target(LOG_TARGET_SAFE);
931 log_parse_environment();
936 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
938 log_error("Failed to find lookup paths.");
942 all_services = hashmap_new(&string_hash_ops);
948 r = enumerate_sysv(lp, all_services);
950 log_error("Failed to generate units for all init scripts.");
954 r = set_dependencies_from_rcnd(lp, all_services);
956 log_error("Failed to read runlevels from rcnd links.");
960 HASHMAP_FOREACH(service, all_services, j) {
961 q = load_sysv(service);
966 HASHMAP_FOREACH(service, all_services, j) {
967 q = fix_order(service, all_services);
971 q = generate_unit_file(service);