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));
177 if (s->sysv_start_priority > 0)
178 fprintf(f, "SysVStartPriority=%d\n", s->sysv_start_priority);
181 fprintf(f, "PIDFile=%s\n", s->pid_file);
184 "ExecStart=%s start\n"
185 "ExecStop=%s stop\n",
189 fprintf(f, "ExecReload=%s reload\n", s->path);
191 STRV_FOREACH(p, s->wanted_by) {
192 r = add_symlink(s->name, *p);
194 log_unit_error_errno(s->name, r, "Failed to create 'Wants' symlink to %s: %m", *p);
200 static bool usage_contains_reload(const char *line) {
201 return (strcasestr(line, "{reload|") ||
202 strcasestr(line, "{reload}") ||
203 strcasestr(line, "{reload\"") ||
204 strcasestr(line, "|reload|") ||
205 strcasestr(line, "|reload}") ||
206 strcasestr(line, "|reload\""));
209 static char *sysv_translate_name(const char *name) {
212 r = new(char, strlen(name) + strlen(".service") + 1);
216 if (endswith(name, ".sh"))
217 /* Drop .sh suffix */
218 strcpy(stpcpy(r, name) - 3, ".service");
220 /* Normal init script name */
221 strcpy(stpcpy(r, name), ".service");
226 static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
228 /* We silently ignore the $ prefix here. According to the LSB
229 * spec it simply indicates whether something is a
230 * standardized name or a distribution-specific one. Since we
231 * just follow what already exists and do not introduce new
232 * uses or names we don't care who introduced a new name. */
234 static const char * const table[] = {
235 /* LSB defined facilities */
237 "network", SPECIAL_NETWORK_ONLINE_TARGET,
238 "named", SPECIAL_NSS_LOOKUP_TARGET,
239 "portmap", SPECIAL_RPCBIND_TARGET,
240 "remote_fs", SPECIAL_REMOTE_FS_TARGET,
242 "time", SPECIAL_TIME_SYNC_TARGET,
252 n = *name == '$' ? name + 1 : name;
254 for (i = 0; i < ELEMENTSOF(table); i += 2) {
256 if (!streq(table[i], n))
262 r = strdup(table[i+1]);
269 /* If we don't know this name, fallback heuristics to figure
270 * out whether something is a target or a service alias. */
273 if (!unit_prefix_is_valid(n))
276 /* Facilities starting with $ are most likely targets */
277 r = unit_name_build(n, NULL, ".target");
278 } else if (filename && streq(name, filename))
279 /* Names equaling the file name of the services are redundant */
282 /* Everything else we assume to be normal service names */
283 r = sysv_translate_name(n);
294 static int load_sysv(SysvStub *s) {
295 _cleanup_fclose_ FILE *f;
305 _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
307 bool supports_reload = false;
311 f = fopen(s->path, "re");
313 return errno == ENOENT ? 0 : -errno;
316 char l[LINE_MAX], *t;
318 if (!fgets(l, sizeof(l), f)) {
322 log_unit_error(s->name,
323 "Failed to read configuration file '%s': %m",
332 /* Try to figure out whether this init script supports
333 * the reload operation. This heuristic looks for
334 * "Usage" lines which include the reload option. */
335 if ( state == USAGE_CONTINUATION ||
336 (state == NORMAL && strcasestr(t, "usage"))) {
337 if (usage_contains_reload(t)) {
338 supports_reload = true;
340 } else if (t[strlen(t)-1] == '\\')
341 state = USAGE_CONTINUATION;
349 if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
355 if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
361 t += strspn(t, WHITESPACE);
363 if (state == NORMAL) {
365 /* Try to parse Red Hat style description */
367 if (startswith_no_case(t, "description:")) {
369 size_t k = strlen(t);
373 if (t[k-1] == '\\') {
386 free(chkconfig_description);
387 chkconfig_description = d;
389 } else if (startswith_no_case(t, "pidfile:")) {
396 if (!path_is_absolute(fn)) {
397 log_unit_error(s->name,
398 "[%s:%u] PID file not absolute. Ignoring.",
411 } else if (state == DESCRIPTION) {
413 /* Try to parse Red Hat style description
416 size_t k = strlen(t);
428 if (chkconfig_description)
429 d = strjoin(chkconfig_description, " ", j, NULL);
436 free(chkconfig_description);
437 chkconfig_description = d;
440 } else if (state == LSB || state == LSB_DESCRIPTION) {
442 if (startswith_no_case(t, "Provides:")) {
443 const char *word, *state_;
448 FOREACH_WORD_QUOTED(word, z, t+9, state_) {
449 _cleanup_free_ char *n = NULL, *m = NULL;
451 n = strndup(word, z);
455 r = sysv_translate_facility(n, basename(s->path), &m);
463 if (unit_name_to_type(m) != UNIT_SERVICE) {
469 * indication that the
471 * now available. This
474 * targets do NOT pull
477 r = strv_extend(&s->before, m);
480 r = strv_extend(&s->wants, m);
483 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) {
484 r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET);
491 log_unit_error(s->name,
492 "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
493 s->path, line, m, strerror(-r));
495 if (!isempty(state_))
496 log_unit_error(s->name,
497 "[%s:%u] Trailing garbage in Provides, ignoring.",
500 } else if (startswith_no_case(t, "Required-Start:") ||
501 startswith_no_case(t, "Should-Start:") ||
502 startswith_no_case(t, "X-Start-Before:") ||
503 startswith_no_case(t, "X-Start-After:")) {
504 const char *word, *state_;
509 FOREACH_WORD_QUOTED(word, z, strchr(t, ':')+1, state_) {
510 _cleanup_free_ char *n = NULL, *m = NULL;
513 n = strndup(word, z);
517 r = sysv_translate_facility(n, basename(s->path), &m);
519 log_unit_error(s->name,
520 "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
521 s->path, line, n, strerror(-r));
528 is_before = startswith_no_case(t, "X-Start-Before:");
530 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
531 /* the network-online target is special, as it needs to be actively pulled in */
532 r = strv_extend(&s->after, m);
535 r = strv_extend(&s->wants, m);
541 r = strv_extend(&s->before, m);
546 r = strv_extend(&s->after, m);
553 log_unit_error(s->name,
554 "[%s:%u] Failed to add dependency on %s, ignoring: %s",
555 s->path, line, m, strerror(-r));
557 if (!isempty(state_))
558 log_unit_error(s->name,
559 "[%s:%u] Trailing garbage in %*s, ignoring.",
561 (int)(strchr(t, ':') - t), t);
563 } else if (startswith_no_case(t, "Description:")) {
566 state = LSB_DESCRIPTION;
576 free(long_description);
577 long_description = d;
579 } else if (startswith_no_case(t, "Short-Description:")) {
592 free(short_description);
593 short_description = d;
595 } else if (state == LSB_DESCRIPTION) {
597 if (startswith(l, "#\t") || startswith(l, "# ")) {
604 if (long_description)
605 d = strjoin(long_description, " ", t, NULL);
612 free(long_description);
613 long_description = d;
622 s->reload = supports_reload;
624 /* We use the long description only if
625 * no short description is set. */
627 if (short_description)
628 description = short_description;
629 else if (chkconfig_description)
630 description = chkconfig_description;
631 else if (long_description)
632 description = long_description;
639 d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
649 static int fix_order(SysvStub *s, Hashmap *all_services) {
656 if (s->sysv_start_priority < 0)
659 HASHMAP_FOREACH(other, all_services, j) {
663 if (other->sysv_start_priority < 0)
666 /* If both units have modern headers we don't care
667 * about the priorities */
668 if (s->has_lsb && other->has_lsb)
671 if (other->sysv_start_priority < s->sysv_start_priority) {
672 r = strv_extend(&s->after, other->name);
676 else if (other->sysv_start_priority > s->sysv_start_priority) {
677 r = strv_extend(&s->before, other->name);
684 /* FIXME: Maybe we should compare the name here lexicographically? */
690 static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
693 STRV_FOREACH(path, lp.sysvinit_path) {
694 _cleanup_closedir_ DIR *d = NULL;
700 log_warning_errno(errno, "opendir(%s) failed: %m", *path);
704 while ((de = readdir(d))) {
707 _cleanup_free_ char *fpath = NULL, *name = NULL;
710 if (ignore_file(de->d_name))
713 fpath = strjoin(*path, "/", de->d_name, NULL);
717 if (stat(fpath, &st) < 0)
720 if (!(st.st_mode & S_IXUSR))
723 name = sysv_translate_name(de->d_name);
727 if (hashmap_contains(all_services, name))
730 service = new0(SysvStub, 1);
734 service->sysv_start_priority = -1;
735 service->name = name;
736 service->path = fpath;
738 r = hashmap_put(all_services, service->name, service);
749 static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
752 _cleanup_closedir_ DIR *d = NULL;
753 _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
756 Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
757 _cleanup_set_free_ Set *shutdown_services = NULL;
760 STRV_FOREACH(p, lp.sysvrcnd_path)
761 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
765 path = strjoin(*p, "/", rcnd_table[i].path, NULL);
775 log_warning_errno(errno, "opendir(%s) failed: %m", path);
780 while ((de = readdir(d))) {
783 if (ignore_file(de->d_name))
786 if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
789 if (strlen(de->d_name) < 4)
792 a = undecchar(de->d_name[1]);
793 b = undecchar(de->d_name[2]);
799 fpath = strjoin(*p, "/", de->d_name, NULL);
805 name = sysv_translate_name(de->d_name + 3);
811 service = hashmap_get(all_services, name);
813 log_warning("Could not find init script for %s", name);
817 if (de->d_name[0] == 'S') {
819 if (rcnd_table[i].type == RUNLEVEL_UP) {
820 service->sysv_start_priority =
821 MAX(a*10 + b, service->sysv_start_priority);
824 r = set_ensure_allocated(&runlevel_services[i], NULL);
828 r = set_put(runlevel_services[i], service);
832 } else if (de->d_name[0] == 'K' &&
833 (rcnd_table[i].type == RUNLEVEL_DOWN)) {
835 r = set_ensure_allocated(&shutdown_services, NULL);
839 r = set_put(shutdown_services, service);
847 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
848 SET_FOREACH(service, runlevel_services[i], j) {
849 r = strv_extend(&service->before, rcnd_table[i].target);
852 r = strv_extend(&service->wanted_by, rcnd_table[i].target);
857 SET_FOREACH(service, shutdown_services, j) {
858 r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
861 r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
870 for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
871 set_free(runlevel_services[i]);
876 int main(int argc, char *argv[]) {
879 Hashmap *all_services;
883 if (argc > 1 && argc != 4) {
884 log_error("This program takes three or no arguments.");
891 log_set_target(LOG_TARGET_SAFE);
892 log_parse_environment();
897 r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
899 log_error("Failed to find lookup paths.");
903 all_services = hashmap_new(&string_hash_ops);
909 r = enumerate_sysv(lp, all_services);
911 log_error("Failed to generate units for all init scripts.");
915 r = set_dependencies_from_rcnd(lp, all_services);
917 log_error("Failed to read runlevels from rcnd links.");
921 HASHMAP_FOREACH(service, all_services, j) {
922 q = load_sysv(service);
926 q = fix_order(service, all_services);
930 q = generate_unit_file(service);