summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am10
-rw-r--r--src/sysv-generator/Makefile1
-rw-r--r--src/sysv-generator/sysv-generator.c919
4 files changed, 931 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 908c563f41..061b4af9c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -101,6 +101,7 @@
/systemd-socket-proxyd
/systemd-sysctl
/systemd-system-update-generator
+/systemd-sysv-generator
/systemd-timedated
/systemd-timesyncd
/systemd-tmpfiles
diff --git a/Makefile.am b/Makefile.am
index 8d5082fca6..ee4ced390d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -549,6 +549,7 @@ nodist_systemunit_DATA += \
units/halt-local.service
systemgenerator_PROGRAMS += \
+ systemd-sysv-generator \
systemd-rc-local-generator
endif
@@ -1915,6 +1916,15 @@ UNINSTALL_EXEC_HOOKS += dbus1-generator-uninstall-hook
endif
# ------------------------------------------------------------------------------
+systemd_sysv_generator_SOURCES = \
+ src/sysv-generator/sysv-generator.c
+
+systemd_sysv_generator_LDADD = \
+ libsystemd-core.la \
+ libsystemd-label.la \
+ libsystemd-shared.la
+
+# ------------------------------------------------------------------------------
systemd_rc_local_generator_SOURCES = \
src/rc-local-generator/rc-local-generator.c
diff --git a/src/sysv-generator/Makefile b/src/sysv-generator/Makefile
new file mode 100644
index 0000000000..530e5e919a
--- /dev/null
+++ b/src/sysv-generator/Makefile
@@ -0,0 +1 @@
+../Makefile
diff --git a/src/sysv-generator/sysv-generator.c b/src/sysv-generator/sysv-generator.c
new file mode 100644
index 0000000000..0b8d8f73dd
--- /dev/null
+++ b/src/sysv-generator/sysv-generator.c
@@ -0,0 +1,919 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Thomas H.P. Andersen
+ Copyright 2010 Lennart Poettering
+ Copyright 2011 Michal Schmidt
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "mkdir.h"
+#include "strv.h"
+#include "path-util.h"
+#include "path-lookup.h"
+#include "log.h"
+#include "strv.h"
+#include "unit.h"
+#include "unit-name.h"
+#include "special.h"
+#include "exit-status.h"
+#include "def.h"
+#include "env-util.h"
+#include "fileio.h"
+#include "hashmap.h"
+
+typedef enum RunlevelType {
+ RUNLEVEL_UP,
+ RUNLEVEL_DOWN
+} RunlevelType;
+
+static const struct {
+ const char *path;
+ const char *target;
+ const RunlevelType type;
+} rcnd_table[] = {
+ /* Standard SysV runlevels for start-up */
+ { "rc1.d", SPECIAL_RESCUE_TARGET, RUNLEVEL_UP },
+ { "rc2.d", SPECIAL_RUNLEVEL2_TARGET, RUNLEVEL_UP },
+ { "rc3.d", SPECIAL_RUNLEVEL3_TARGET, RUNLEVEL_UP },
+ { "rc4.d", SPECIAL_RUNLEVEL4_TARGET, RUNLEVEL_UP },
+ { "rc5.d", SPECIAL_RUNLEVEL5_TARGET, RUNLEVEL_UP },
+
+ /* Standard SysV runlevels for shutdown */
+ { "rc0.d", SPECIAL_POWEROFF_TARGET, RUNLEVEL_DOWN },
+ { "rc6.d", SPECIAL_REBOOT_TARGET, RUNLEVEL_DOWN }
+
+ /* Note that the order here matters, as we read the
+ directories in this order, and we want to make sure that
+ sysv_start_priority is known when we first load the
+ unit. And that value we only know from S links. Hence
+ UP must be read before DOWN */
+};
+
+typedef struct SysvStub {
+ char *name;
+ char *path;
+ char *description;
+ int sysv_start_priority;
+ char *pid_file;
+ char **before;
+ char **after;
+ char **wants;
+ char **conflicts;
+ bool has_lsb;
+ bool reload;
+} SysvStub;
+
+const char *arg_dest = "/tmp";
+
+static int add_symlink(const char *service, const char *where) {
+ _cleanup_free_ char *from = NULL, *to = NULL;
+ int r;
+
+ assert(service);
+ assert(where);
+
+ from = strjoin(arg_dest, "/", service, NULL);
+ if (!from)
+ return log_oom();
+
+ to = strjoin(arg_dest, "/", where, ".wants/", service, NULL);
+ if (!to)
+ return log_oom();
+
+ mkdir_parents_label(to, 0755);
+
+ r = symlink(from, to);
+ if (r < 0) {
+ if (errno == EEXIST)
+ return 0;
+ return -errno;
+ }
+
+ return 1;
+}
+
+static int generate_unit_file(SysvStub *s) {
+ char *unit;
+ char **p;
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *before = NULL;
+ _cleanup_free_ char *after = NULL;
+ _cleanup_free_ char *conflicts = NULL;
+ int r;
+
+ before = strv_join(s->before, " ");
+ if (!before)
+ return log_oom();
+
+ after = strv_join(s->after, " ");
+ if (!after)
+ return log_oom();
+
+ conflicts = strv_join(s->conflicts, " ");
+ if (!conflicts)
+ return log_oom();
+
+ unit = strjoin(arg_dest, "/", s->name, NULL);
+ if (!unit)
+ return log_oom();
+
+ f = fopen(unit, "wxe");
+ if (!f) {
+ log_error("Failed to create unit file %s: %m", unit);
+ return -errno;
+ }
+
+ fprintf(f,
+ "# Automatically generated by systemd-sysv-generator\n\n"
+ "[Unit]\n"
+ "SourcePath=%s\n"
+ "Description=%s\n",
+ s->path, s->description);
+
+ if (!isempty(before))
+ fprintf(f, "Before=%s\n", before);
+ if (!isempty(after))
+ fprintf(f, "After=%s\n", after);
+ if (!isempty(conflicts))
+ fprintf(f, "Conflicts=%s\n", conflicts);
+
+ fprintf(f,
+ "\n[Service]\n"
+ "Type=forking\n"
+ "Restart=no\n"
+ "TimeoutSec=5min\n"
+ "IgnoreSIGPIPE=no\n"
+ "KillMode=process\n"
+ "GuessMainPID=no\n"
+ "RemainAfterExit=%s\n",
+ yes_no(!s->pid_file));
+
+ if (s->sysv_start_priority > 0)
+ fprintf(f, "SysVStartPriority=%d\n", s->sysv_start_priority);
+
+ if (s->pid_file)
+ fprintf(f, "PidFile=%s\n", s->pid_file);
+
+ fprintf(f,
+ "ExecStart=%s start\n"
+ "ExecStop=%s stop\n",
+ s->path, s->path);
+
+ if (s->reload)
+ fprintf(f, "ExecReload=%s reload\n", s->path);
+
+ STRV_FOREACH(p, s->wants) {
+ r = add_symlink(s->name, *p);
+ if (r < 0)
+ log_error_unit(s->name, "Failed to create 'Wants' symlink to %s: %s", *p, strerror(-r));
+ }
+
+ return 0;
+}
+
+static bool usage_contains_reload(const char *line) {
+ return (strcasestr(line, "{reload|") ||
+ strcasestr(line, "{reload}") ||
+ strcasestr(line, "{reload\"") ||
+ strcasestr(line, "|reload|") ||
+ strcasestr(line, "|reload}") ||
+ strcasestr(line, "|reload\""));
+}
+
+static char *sysv_translate_name(const char *name) {
+ char *r;
+
+ r = new(char, strlen(name) + strlen(".service") + 1);
+ if (!r)
+ return NULL;
+
+ if (endswith(name, ".sh"))
+ /* Drop .sh suffix */
+ strcpy(stpcpy(r, name) - 3, ".service");
+ else
+ /* Normal init script name */
+ strcpy(stpcpy(r, name), ".service");
+
+ return r;
+}
+
+static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
+
+ /* We silently ignore the $ prefix here. According to the LSB
+ * spec it simply indicates whether something is a
+ * standardized name or a distribution-specific one. Since we
+ * just follow what already exists and do not introduce new
+ * uses or names we don't care who introduced a new name. */
+
+ static const char * const table[] = {
+ /* LSB defined facilities */
+ "local_fs", NULL,
+ "network", SPECIAL_NETWORK_ONLINE_TARGET,
+ "named", SPECIAL_NSS_LOOKUP_TARGET,
+ "portmap", SPECIAL_RPCBIND_TARGET,
+ "remote_fs", SPECIAL_REMOTE_FS_TARGET,
+ "syslog", NULL,
+ "time", SPECIAL_TIME_SYNC_TARGET,
+ };
+
+ unsigned i;
+ char *r;
+ const char *n;
+
+ assert(name);
+ assert(_r);
+
+ n = *name == '$' ? name + 1 : name;
+
+ for (i = 0; i < ELEMENTSOF(table); i += 2) {
+
+ if (!streq(table[i], n))
+ continue;
+
+ if (!table[i+1])
+ return 0;
+
+ r = strdup(table[i+1]);
+ if (!r)
+ return log_oom();
+
+ goto finish;
+ }
+
+ /* If we don't know this name, fallback heuristics to figure
+ * out whether something is a target or a service alias. */
+
+ if (*name == '$') {
+ if (!unit_prefix_is_valid(n))
+ return -EINVAL;
+
+ /* Facilities starting with $ are most likely targets */
+ r = unit_name_build(n, NULL, ".target");
+ } else if (filename && streq(name, filename))
+ /* Names equaling the file name of the services are redundant */
+ return 0;
+ else
+ /* Everything else we assume to be normal service names */
+ r = sysv_translate_name(n);
+
+ if (!r)
+ return -ENOMEM;
+
+finish:
+ *_r = r;
+
+ return 1;
+}
+
+static int load_sysv(SysvStub *s) {
+ _cleanup_fclose_ FILE *f;
+ unsigned line = 0;
+ int r;
+ enum {
+ NORMAL,
+ DESCRIPTION,
+ LSB,
+ LSB_DESCRIPTION,
+ USAGE_CONTINUATION
+ } state = NORMAL;
+ _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
+ char *description;
+ bool supports_reload = false;
+
+ assert(s);
+
+ f = fopen(s->path, "re");
+ if (!f)
+ return errno == ENOENT ? 0 : -errno;
+
+ while (!feof(f)) {
+ char l[LINE_MAX], *t;
+
+ if (!fgets(l, sizeof(l), f)) {
+ if (feof(f))
+ break;
+
+ log_error_unit(s->name,
+ "Failed to read configuration file '%s': %m",
+ s->path);
+ return -errno;
+ }
+
+ line++;
+
+ t = strstrip(l);
+ if (*t != '#') {
+ /* Try to figure out whether this init script supports
+ * the reload operation. This heuristic looks for
+ * "Usage" lines which include the reload option. */
+ if ( state == USAGE_CONTINUATION ||
+ (state == NORMAL && strcasestr(t, "usage"))) {
+ if (usage_contains_reload(t)) {
+ supports_reload = true;
+ state = NORMAL;
+ } else if (t[strlen(t)-1] == '\\')
+ state = USAGE_CONTINUATION;
+ else
+ state = NORMAL;
+ }
+
+ continue;
+ }
+
+ if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
+ state = LSB;
+ s->has_lsb = true;
+ continue;
+ }
+
+ if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
+ state = NORMAL;
+ continue;
+ }
+
+ t++;
+ t += strspn(t, WHITESPACE);
+
+ if (state == NORMAL) {
+
+ /* Try to parse Red Hat style description */
+
+ if (startswith_no_case(t, "description:")) {
+
+ size_t k = strlen(t);
+ char *d;
+ const char *j;
+
+ if (t[k-1] == '\\') {
+ state = DESCRIPTION;
+ t[k-1] = 0;
+ }
+
+ j = strstrip(t+12);
+ if (j && *j) {
+ d = strdup(j);
+ if (!d)
+ return -ENOMEM;
+ } else
+ d = NULL;
+
+ free(chkconfig_description);
+ chkconfig_description = d;
+
+ } else if (startswith_no_case(t, "pidfile:")) {
+
+ char *fn;
+
+ state = NORMAL;
+
+ fn = strstrip(t+8);
+ if (!path_is_absolute(fn)) {
+ log_error_unit(s->name,
+ "[%s:%u] PID file not absolute. Ignoring.",
+ s->path, line);
+ continue;
+ }
+
+ fn = strdup(fn);
+ if (!fn)
+ return -ENOMEM;
+
+ free(s->pid_file);
+ s->pid_file = fn;
+ }
+
+ } else if (state == DESCRIPTION) {
+
+ /* Try to parse Red Hat style description
+ * continuation */
+
+ size_t k = strlen(t);
+ char *j;
+
+ if (t[k-1] == '\\')
+ t[k-1] = 0;
+ else
+ state = NORMAL;
+
+ j = strstrip(t);
+ if (j && *j) {
+ char *d = NULL;
+
+ if (chkconfig_description)
+ d = strjoin(chkconfig_description, " ", j, NULL);
+ else
+ d = strdup(j);
+
+ if (!d)
+ return -ENOMEM;
+
+ free(chkconfig_description);
+ chkconfig_description = d;
+ }
+
+ } else if (state == LSB || state == LSB_DESCRIPTION) {
+
+ if (startswith_no_case(t, "Provides:")) {
+ char *i, *w;
+ size_t z;
+
+ state = LSB;
+
+ FOREACH_WORD_QUOTED(w, z, t+9, i) {
+ _cleanup_free_ char *n = NULL, *m = NULL;
+
+ n = strndup(w, z);
+ if (!n)
+ return -ENOMEM;
+
+ r = sysv_translate_facility(n, basename(s->path), &m);
+
+ if (r < 0)
+ return r;
+
+ if (r == 0)
+ continue;
+
+ if (unit_name_to_type(m) != UNIT_SERVICE) {
+ /* NB: SysV targets
+ * which are provided
+ * by a service are
+ * pulled in by the
+ * services, as an
+ * indication that the
+ * generic service is
+ * now available. This
+ * is strictly
+ * one-way. The
+ * targets do NOT pull
+ * in the SysV
+ * services! */
+ r = strv_extend(&s->before, m);
+ if (r < 0)
+ return log_oom();
+ r = strv_extend(&s->wants, m);
+ if (r < 0)
+ return log_oom();
+ }
+
+ if (r < 0)
+ log_error_unit(s->name,
+ "[%s:%u] Failed to add LSB Provides name %s, ignoring: %s",
+ s->path, line, m, strerror(-r));
+ }
+
+ } else if (startswith_no_case(t, "Required-Start:") ||
+ startswith_no_case(t, "Should-Start:") ||
+ startswith_no_case(t, "X-Start-Before:") ||
+ startswith_no_case(t, "X-Start-After:")) {
+ char *i, *w;
+ size_t z;
+
+ state = LSB;
+
+ FOREACH_WORD_QUOTED(w, z, strchr(t, ':')+1, i) {
+ _cleanup_free_ char *n = NULL, *m = NULL;
+ bool is_before;
+
+ n = strndup(w, z);
+ if (!n)
+ return -ENOMEM;
+
+ r = sysv_translate_facility(n, basename(s->path), &m);
+ if (r < 0) {
+ log_error_unit(s->name,
+ "[%s:%u] Failed to translate LSB dependency %s, ignoring: %s",
+ s->path, line, n, strerror(-r));
+ continue;
+ }
+
+ if (r == 0)
+ continue;
+
+ is_before = startswith_no_case(t, "X-Start-Before:");
+
+ if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
+ /* the network-online target is special, as it needs to be actively pulled in */
+ r = strv_extend(&s->after, m);
+ if (r < 0)
+ return log_oom();
+ r = strv_extend(&s->wants, m);
+ if (r < 0)
+ return log_oom();
+ }
+ else {
+ if (is_before) {
+ r = strv_extend(&s->before, m);
+ if (r < 0)
+ return log_oom();
+ }
+ else {
+ r = strv_extend(&s->after, m);
+ if (r < 0)
+ return log_oom();
+ }
+ }
+
+ if (r < 0)
+ log_error_unit(s->name,
+ "[%s:%u] Failed to add dependency on %s, ignoring: %s",
+ s->path, line, m, strerror(-r));
+ }
+
+ } else if (startswith_no_case(t, "Description:")) {
+ char *d, *j;
+
+ state = LSB_DESCRIPTION;
+
+ j = strstrip(t+12);
+ if (j && *j) {
+ d = strdup(j);
+ if (!d)
+ return -ENOMEM;
+ } else
+ d = NULL;
+
+ free(long_description);
+ long_description = d;
+
+ } else if (startswith_no_case(t, "Short-Description:")) {
+ char *d, *j;
+
+ state = LSB;
+
+ j = strstrip(t+18);
+ if (j && *j) {
+ d = strdup(j);
+ if (!d)
+ return -ENOMEM;
+ } else
+ d = NULL;
+
+ free(short_description);
+ short_description = d;
+
+ } else if (state == LSB_DESCRIPTION) {
+
+ if (startswith(l, "#\t") || startswith(l, "# ")) {
+ char *j;
+
+ j = strstrip(t);
+ if (j && *j) {
+ char *d = NULL;
+
+ if (long_description)
+ d = strjoin(long_description, " ", t, NULL);
+ else
+ d = strdup(j);
+
+ if (!d)
+ return -ENOMEM;
+
+ free(long_description);
+ long_description = d;
+ }
+
+ } else
+ state = LSB;
+ }
+ }
+ }
+
+ s->reload = supports_reload;
+
+ /* We use the long description only if
+ * no short description is set. */
+
+ if (short_description)
+ description = short_description;
+ else if (chkconfig_description)
+ description = chkconfig_description;
+ else if (long_description)
+ description = long_description;
+ else
+ description = NULL;
+
+ if (description) {
+ char *d;
+
+ d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
+ if (!d)
+ return -ENOMEM;
+
+ s->description = d;
+ }
+
+ return 0;
+}
+
+static int fix_order(SysvStub *s, Hashmap *all_services) {
+ SysvStub *other;
+ Iterator j;
+ int r;
+
+ assert(s);
+
+ if (s->sysv_start_priority < 0)
+ return 0;
+
+ HASHMAP_FOREACH(other, all_services, j) {
+ if (s == other)
+ continue;
+
+ if (other->sysv_start_priority < 0)
+ continue;
+
+ /* If both units have modern headers we don't care
+ * about the priorities */
+ if (s->has_lsb && other->has_lsb)
+ continue;
+
+ if (other->sysv_start_priority < s->sysv_start_priority) {
+ r = strv_extend(&s->after, other->name);
+ if (r < 0)
+ return log_oom();
+ }
+ else if (other->sysv_start_priority > s->sysv_start_priority) {
+ r = strv_extend(&s->before, other->name);
+ if (r < 0)
+ return log_oom();
+ }
+ else
+ continue;
+
+ /* FIXME: Maybe we should compare the name here lexicographically? */
+ }
+
+ return 0;
+}
+
+static int enumerate_sysv(LookupPaths lp, Hashmap *all_services) {
+ char **path;
+
+ STRV_FOREACH(path, lp.sysvinit_path) {
+ _cleanup_closedir_ DIR *d = NULL;
+ struct dirent *de;
+
+ d = opendir(*path);
+ if (!d) {
+ if (errno != ENOENT)
+ log_warning("opendir(%s) failed: %m", *path);
+ continue;
+ }
+
+ while ((de = readdir(d))) {
+ SysvStub *service;
+ struct stat st;
+ _cleanup_free_ char *fpath = NULL, *name = NULL;
+ int r;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ fpath = strjoin(*path, "/", de->d_name, NULL);
+ if (!fpath)
+ return log_oom();
+
+ if (stat(fpath, &st) < 0)
+ continue;
+
+ if (!(st.st_mode & S_IXUSR))
+ continue;
+
+ name = sysv_translate_name(de->d_name);
+ if (!name)
+ return log_oom();
+
+ if (hashmap_contains(all_services, name))
+ continue;
+
+ service = new0(SysvStub, 1);
+ if (!service)
+ return log_oom();
+
+ service->sysv_start_priority = -1;
+ service->name = name;
+ service->path = fpath;
+
+ r = hashmap_put(all_services, service->name, service);
+ if (r < 0)
+ return log_oom();
+
+ name = fpath = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static int set_dependencies_from_rcnd(LookupPaths lp, Hashmap *all_services) {
+ char **p;
+ unsigned i;
+ _cleanup_closedir_ DIR *d = NULL;
+ _cleanup_free_ char *path = NULL, *fpath = NULL, *name = NULL;
+ SysvStub *service;
+ Iterator j;
+ Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
+ _cleanup_set_free_ Set *shutdown_services = NULL;
+ int r = 0;
+
+ STRV_FOREACH(p, lp.sysvrcnd_path)
+ for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
+ struct dirent *de;
+
+ free(path);
+ path = strjoin(*p, "/", rcnd_table[i].path, NULL);
+ if (!path)
+ return -ENOMEM;
+
+ if (d)
+ closedir(d);
+
+ d = opendir(path);
+ if (!d) {
+ if (errno != ENOENT)
+ log_warning("opendir(%s) failed: %m", path);
+
+ continue;
+ }
+
+ while ((de = readdir(d))) {
+ int a, b;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
+ continue;
+
+ if (strlen(de->d_name) < 4)
+ continue;
+
+ a = undecchar(de->d_name[1]);
+ b = undecchar(de->d_name[2]);
+
+ if (a < 0 || b < 0)
+ continue;
+
+ free(fpath);
+ fpath = strjoin(*p, "/", de->d_name, NULL);
+ if (!fpath) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ name = sysv_translate_name(de->d_name + 3);
+ if (!name) {
+ r = log_oom();
+ goto finish;
+ }
+
+ if (hashmap_contains(all_services, name))
+ service = hashmap_get(all_services, name);
+ else {
+ log_warning("Could not find init scirpt for %s", name);
+ continue;
+ }
+
+ if (de->d_name[0] == 'S') {
+
+ if (rcnd_table[i].type == RUNLEVEL_UP) {
+ service->sysv_start_priority =
+ MAX(a*10 + b, service->sysv_start_priority);
+ }
+
+ r = set_ensure_allocated(&runlevel_services[i],
+ trivial_hash_func, trivial_compare_func);
+ if (r < 0)
+ goto finish;
+
+ r = set_put(runlevel_services[i], service);
+ if (r < 0)
+ goto finish;
+
+ } else if (de->d_name[0] == 'K' &&
+ (rcnd_table[i].type == RUNLEVEL_DOWN)) {
+
+ r = set_ensure_allocated(&shutdown_services,
+ trivial_hash_func, trivial_compare_func);
+ if (r < 0)
+ goto finish;
+
+ r = set_put(shutdown_services, service);
+ if (r < 0)
+ goto finish;
+ }
+ }
+ }
+
+
+ for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
+ SET_FOREACH(service, runlevel_services[i], j) {
+ r = strv_extend(&service->before, rcnd_table[i].target);
+ if (r < 0)
+ return log_oom();
+ r = strv_extend(&service->wants, rcnd_table[i].target);
+ if (r < 0)
+ return log_oom();
+ }
+
+ SET_FOREACH(service, shutdown_services, j) {
+ r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET);
+ if (r < 0)
+ return log_oom();
+ r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET);
+ if (r < 0)
+ return log_oom();
+ }
+
+ r = 0;
+
+finish:
+
+ for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
+ set_free(runlevel_services[i]);
+
+ return r;
+}
+
+int main(int argc, char *argv[]) {
+ int r, q;
+ LookupPaths lp;
+ Hashmap *all_services;
+ SysvStub *service;
+ Iterator j;
+
+ if (argc > 1 && argc != 4) {
+ log_error("This program takes three or no arguments.");
+ return EXIT_FAILURE;
+ }
+
+ if (argc > 1)
+ arg_dest = argv[3];
+
+ log_set_target(LOG_TARGET_SAFE);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = lookup_paths_init(&lp, SYSTEMD_SYSTEM, true, NULL, NULL, NULL, NULL);
+ if (r < 0) {
+ log_error("Failed to find lookup paths.");
+ return EXIT_FAILURE;
+ }
+
+ all_services = hashmap_new(string_hash_func, string_compare_func);
+ if (!all_services) {
+ log_oom();
+ return EXIT_FAILURE;
+ }
+
+ r = enumerate_sysv(lp, all_services);
+ if (r < 0) {
+ log_error("Failed to generate units for all init scripts.");
+ return EXIT_FAILURE;
+ }
+
+ r = set_dependencies_from_rcnd(lp, all_services);
+ if (r < 0) {
+ log_error("Failed to read runlevels from rcnd links.");
+ return EXIT_FAILURE;
+ }
+
+ HASHMAP_FOREACH(service, all_services, j) {
+ q = load_sysv(service);
+ if (q < 0)
+ continue;
+
+ q = fix_order(service, all_services);
+ if (q < 0)
+ continue;
+
+ q = generate_unit_file(service);
+ if (q < 0)
+ continue;
+ }
+
+ return EXIT_SUCCESS;
+}