aboutsummaryrefslogtreecommitdiff
path: root/src/openrc
diff options
context:
space:
mode:
Diffstat (limited to 'src/openrc')
-rw-r--r--src/openrc/meson.build17
-rw-r--r--src/openrc/rc-logger.c314
-rw-r--r--src/openrc/rc-logger.h23
-rw-r--r--src/openrc/rc.c1118
4 files changed, 1472 insertions, 0 deletions
diff --git a/src/openrc/meson.build b/src/openrc/meson.build
new file mode 100644
index 00000000..9ab32ef4
--- /dev/null
+++ b/src/openrc/meson.build
@@ -0,0 +1,17 @@
+executable('openrc',
+ ['rc.c', 'rc-logger.c', misc_c, plugin_c, usage_c, version_h],
+ c_args : cc_branding_flags,
+ link_with: [libeinfo, librc],
+ dependencies: [dl_dep, util_dep],
+ include_directories: [incdir, einfo_incdir, rc_incdir],
+ install: true,
+ install_dir: sbindir)
+
+executable('rc',
+ ['rc.c', 'rc-logger.c', misc_c, plugin_c, usage_c, version_h],
+ c_args : cc_branding_flags,
+ link_with: [libeinfo, librc],
+ dependencies: [dl_dep, util_dep],
+ include_directories: [incdir, einfo_incdir, rc_incdir],
+ install: true,
+ install_dir: sbindir)
diff --git a/src/openrc/rc-logger.c b/src/openrc/rc-logger.c
new file mode 100644
index 00000000..b00550a7
--- /dev/null
+++ b/src/openrc/rc-logger.c
@@ -0,0 +1,314 @@
+/*
+ * rc-logger.c
+ * Spawns a logging daemon to capture stdout and stderr so we can log
+ * them to a buffer and/or files.
+ */
+
+/*
+ * Copyright (c) 2007-2015 The OpenRC Authors.
+ * See the Authors file at the top-level directory of this distribution and
+ * https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS
+ *
+ * This file is part of OpenRC. It is subject to the license terms in
+ * the LICENSE file found in the top-level directory of this
+ * distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE
+ * This file may not be copied, modified, propagated, or distributed
+ * except according to the terms contained in the LICENSE file.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+#if defined(__linux__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) \
+ || defined(__GNU__)
+# include <pty.h>
+#elif defined(__NetBSD__) || defined(__OpenBSD__)
+# include <util.h>
+#else
+# include <libutil.h>
+#endif
+
+#include "einfo.h"
+#include "rc-logger.h"
+#include "queue.h"
+#include "rc.h"
+#include "misc.h"
+
+#define TMPLOG RC_SVCDIR "/rc.log"
+#define DEFAULTLOG "/var/log/rc.log"
+
+static int signal_pipe[2] = { -1, -1 };
+static int fd_stdout = -1;
+static int fd_stderr = -1;
+static const char *runlevel = NULL;
+static bool in_escape = false;
+static bool in_term = false;
+
+static char *logbuf = NULL;
+static size_t logbuf_size = 0;
+static size_t logbuf_len = 0;
+
+pid_t rc_logger_pid = -1;
+int rc_logger_tty = -1;
+bool rc_in_logger = false;
+
+static void
+write_log(int logfd, const char *buffer, size_t bytes)
+{
+ const char *p = buffer;
+
+ while ((size_t)(p - buffer) < bytes) {
+ switch (*p) {
+ case '\r':
+ goto cont;
+ case '\033':
+ in_escape = true;
+ in_term = false;
+ goto cont;
+ case '\n':
+ in_escape = in_term = false;
+ break;
+ case '[':
+ if (in_escape)
+ in_term = true;
+ break;
+ }
+
+ if (!in_escape) {
+ if (!isprint((int) *p) && *p != '\n')
+ goto cont;
+ if (write(logfd, p++, 1) == -1)
+ eerror("write: %s", strerror(errno));
+ continue;
+ }
+
+ if (!in_term || isalpha((unsigned char)*p))
+ in_escape = in_term = false;
+cont:
+ p++;
+ }
+}
+
+static void
+write_time(FILE *f, const char *s)
+{
+ time_t now = time(NULL);
+ struct tm *tm = localtime(&now);
+
+ fprintf(f, "\nrc %s logging %s at %s\n", runlevel, s, asctime(tm));
+ fflush(f);
+}
+
+void
+rc_logger_close(void)
+{
+ int sig = SIGTERM;
+
+ if (signal_pipe[1] > -1) {
+ if (write(signal_pipe[1], &sig, sizeof(sig)) == -1)
+ eerror("write: %s", strerror(errno));
+ close(signal_pipe[1]);
+ signal_pipe[1] = -1;
+ }
+
+ if (rc_logger_pid > 0)
+ waitpid(rc_logger_pid, 0, 0);
+
+ if (fd_stdout > -1)
+ dup2(fd_stdout, STDOUT_FILENO);
+ if (fd_stderr > -1)
+ dup2(fd_stderr, STDERR_FILENO);
+}
+
+void
+rc_logger_open(const char *level)
+{
+ int slave_tty;
+ struct termios tt;
+ struct winsize ws;
+ char buffer[BUFSIZ];
+ struct pollfd fd[2];
+ int s = 0;
+ size_t bytes;
+ int i;
+ FILE *log = NULL;
+ FILE *plog = NULL;
+ const char *logfile;
+ int log_error = 0;
+
+ if (!rc_conf_yesno("rc_logger"))
+ return;
+
+ if (pipe(signal_pipe) == -1)
+ eerrorx("pipe: %s", strerror(errno));
+ for (i = 0; i < 2; i++)
+ if ((s = fcntl (signal_pipe[i], F_GETFD, 0) == -1 ||
+ fcntl (signal_pipe[i], F_SETFD, s | FD_CLOEXEC) == -1))
+ eerrorx("fcntl: %s", strerror (errno));
+
+ if (isatty(STDOUT_FILENO)) {
+ tcgetattr(STDOUT_FILENO, &tt);
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
+ if (openpty(&rc_logger_tty, &slave_tty, NULL, &tt, &ws))
+ return;
+ } else
+ if (openpty(&rc_logger_tty, &slave_tty, NULL, NULL, NULL))
+ return;
+
+ if ((s = fcntl(rc_logger_tty, F_GETFD, 0)) == 0)
+ fcntl(rc_logger_tty, F_SETFD, s | FD_CLOEXEC);
+
+ if ((s = fcntl(slave_tty, F_GETFD, 0)) == 0)
+ fcntl(slave_tty, F_SETFD, s | FD_CLOEXEC);
+
+ rc_logger_pid = fork();
+ switch (rc_logger_pid) {
+ case -1:
+ eerror("fork: %s", strerror(errno));
+ break;
+ case 0:
+ rc_in_logger = true;
+ close(signal_pipe[1]);
+ signal_pipe[1] = -1;
+
+ runlevel = level;
+ if ((log = fopen(TMPLOG, "ae")))
+ write_time(log, "started");
+ else {
+ free(logbuf);
+ logbuf_size = BUFSIZ * 10;
+ logbuf = xmalloc(sizeof (char) * logbuf_size);
+ logbuf_len = 0;
+ }
+
+ fd[0].fd = signal_pipe[0];
+ fd[0].events = fd[1].events = POLLIN;
+ fd[0].revents = fd[1].revents = 0;
+ if (rc_logger_tty >= 0)
+ fd[1].fd = rc_logger_tty;
+ for (;;) {
+ if ((s = poll(fd,
+ rc_logger_tty >= 0 ? 2 : 1, -1)) == -1)
+ {
+ eerror("poll: %s", strerror(errno));
+ break;
+ } else if (s == 0)
+ continue;
+
+ if (fd[1].revents & (POLLIN | POLLHUP)) {
+ memset(buffer, 0, BUFSIZ);
+ bytes = read(rc_logger_tty, buffer, BUFSIZ);
+ if (write(STDOUT_FILENO, buffer, bytes) == -1)
+ eerror("write: %s", strerror(errno));
+
+ if (log)
+ write_log(fileno (log), buffer, bytes);
+ else {
+ if (logbuf_size - logbuf_len < bytes) {
+ logbuf_size += BUFSIZ * 10;
+ logbuf = xrealloc(logbuf,
+ sizeof(char ) *
+ logbuf_size);
+ }
+
+ memcpy(logbuf + logbuf_len,
+ buffer, bytes);
+ logbuf_len += bytes;
+ }
+ }
+
+ /* Only SIGTERMS signals come down this pipe */
+ if (fd[0].revents & (POLLIN | POLLHUP))
+ break;
+ }
+ if (logbuf) {
+ if ((log = fopen(TMPLOG, "ae"))) {
+ write_time(log, "started");
+ write_log(fileno(log), logbuf, logbuf_len);
+ }
+ free(logbuf);
+ }
+ if (log) {
+ write_time(log, "stopped");
+ fclose(log);
+ }
+
+ /* Append the temporary log to the real log */
+ logfile = rc_conf_value("rc_log_path");
+ if (logfile == NULL)
+ logfile = DEFAULTLOG;
+ if (!strcmp(logfile, TMPLOG)) {
+ eerror("Cowardly refusing to concatenate a logfile into itself.");
+ eerrorx("Please change rc_log_path to something other than %s to get rid of this message", TMPLOG);
+ }
+
+ if ((plog = fopen(logfile, "ae"))) {
+ if ((log = fopen(TMPLOG, "re"))) {
+ while ((bytes = fread(buffer, sizeof(*buffer), BUFSIZ, log)) > 0) {
+ if (fwrite(buffer, sizeof(*buffer), bytes, plog) < bytes) {
+ log_error = 1;
+ eerror("Error: write(%s) failed: %s", logfile, strerror(errno));
+ break;
+ }
+ }
+ fclose(log);
+ } else {
+ log_error = 1;
+ eerror("Error: fopen(%s) failed: %s", TMPLOG, strerror(errno));
+ }
+
+ fclose(plog);
+ } else {
+ /*
+ * logfile or its basedir may be read-only during sysinit and
+ * shutdown so skip the error in this case
+ */
+ if (errno != EROFS && ((strcmp(level, RC_LEVEL_SHUTDOWN) != 0) && (strcmp(level, RC_LEVEL_SYSINIT) != 0))) {
+ log_error = 1;
+ eerror("Error: fopen(%s) failed: %s", logfile, strerror(errno));
+ }
+ }
+
+ /* Try to keep the temporary log in case of errors */
+ if (!log_error) {
+ if (errno != EROFS && ((strcmp(level, RC_LEVEL_SHUTDOWN) != 0) && (strcmp(level, RC_LEVEL_SYSINIT) != 0)))
+ if (unlink(TMPLOG) == -1)
+ eerror("Error: unlink(%s) failed: %s", TMPLOG, strerror(errno));
+ } else if (exists(TMPLOG))
+ eerrorx("Warning: temporary logfile left behind: %s", TMPLOG);
+
+ exit(0);
+ /* NOTREACHED */
+
+ default:
+ setpgid(rc_logger_pid, 0);
+ fd_stdout = dup(STDOUT_FILENO);
+ fd_stderr = dup(STDERR_FILENO);
+ if ((s = fcntl(fd_stdout, F_GETFD, 0)) == 0)
+ fcntl(fd_stdout, F_SETFD, s | FD_CLOEXEC);
+
+ if ((s = fcntl(fd_stderr, F_GETFD, 0)) == 0)
+ fcntl(fd_stderr, F_SETFD, s | FD_CLOEXEC);
+ dup2(slave_tty, STDOUT_FILENO);
+ dup2(slave_tty, STDERR_FILENO);
+ if (slave_tty != STDIN_FILENO &&
+ slave_tty != STDOUT_FILENO &&
+ slave_tty != STDERR_FILENO)
+ close(slave_tty);
+ close(signal_pipe[0]);
+ signal_pipe[0] = -1;
+ break;
+ }
+}
diff --git a/src/openrc/rc-logger.h b/src/openrc/rc-logger.h
new file mode 100644
index 00000000..52ca9717
--- /dev/null
+++ b/src/openrc/rc-logger.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2007-2015 The OpenRC Authors.
+ * See the Authors file at the top-level directory of this distribution and
+ * https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS
+ *
+ * This file is part of OpenRC. It is subject to the license terms in
+ * the LICENSE file found in the top-level directory of this
+ * distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE
+ * This file may not be copied, modified, propagated, or distributed
+ * except according to the terms contained in the LICENSE file.
+ */
+
+#ifndef RC_LOGGER_H
+#define RC_LOGGER_H
+
+extern pid_t rc_logger_pid;
+extern int rc_logger_tty;
+extern bool rc_in_logger;
+
+void rc_logger_open(const char *runlevel);
+void rc_logger_close(void);
+
+#endif
diff --git a/src/openrc/rc.c b/src/openrc/rc.c
new file mode 100644
index 00000000..bab731ed
--- /dev/null
+++ b/src/openrc/rc.c
@@ -0,0 +1,1118 @@
+/*
+ * rc.c
+ * rc - manager for init scripts which control the startup, shutdown
+ * and the running of daemons.
+ *
+ * Also a multicall binary for various commands that can be used in shell
+ * scripts to query service state, mark service state and provide the
+ * einfo family of informational functions.
+ */
+
+/*
+ * Copyright (c) 2007-2015 The OpenRC Authors.
+ * See the Authors file at the top-level directory of this distribution and
+ * https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS
+ *
+ * This file is part of OpenRC. It is subject to the license terms in
+ * the LICENSE file found in the top-level directory of this
+ * distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE
+ * This file may not be copied, modified, propagated, or distributed
+ * except according to the terms contained in the LICENSE file.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <strings.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "einfo.h"
+#include "queue.h"
+#include "rc.h"
+#include "rc-logger.h"
+#include "misc.h"
+#include "plugin.h"
+
+#include "version.h"
+#include "_usage.h"
+
+const char *extraopts = NULL;
+const char getoptstring[] = "a:no:s:S" getoptstring_COMMON;
+const struct option longopts[] = {
+ { "no-stop", 0, NULL, 'n' },
+ { "override", 1, NULL, 'o' },
+ { "service", 1, NULL, 's' },
+ { "sys", 0, NULL, 'S' },
+ longopts_COMMON
+};
+const char * const longopts_help[] = {
+ "do not stop any services",
+ "override the next runlevel to change into\n",
+ "when leaving single user or boot runlevels",
+ "runs the service specified with the rest\nof the arguments",
+ "output the RC system type, if any",
+ longopts_help_COMMON
+};
+const char *usagestring = "" \
+ "Usage: openrc [options] [<runlevel>]";
+
+#define INITSH RC_LIBEXECDIR "/sh/init.sh"
+#define INITEARLYSH RC_LIBEXECDIR "/sh/init-early.sh"
+
+#define INTERACTIVE RC_SVCDIR "/interactive"
+
+#define DEVBOOT "/dev/.rcboot"
+
+const char *applet = NULL;
+static RC_STRINGLIST *main_hotplugged_services;
+static RC_STRINGLIST *main_stop_services;
+static RC_STRINGLIST *main_start_services;
+static RC_STRINGLIST *main_types_nw;
+static RC_STRINGLIST *main_types_nwua;
+static RC_DEPTREE *main_deptree;
+static char *runlevel;
+static RC_HOOK hook_out;
+
+struct termios *termios_orig = NULL;
+
+RC_PIDLIST service_pids;
+
+static void
+clean_failed(void)
+{
+ DIR *dp;
+ struct dirent *d;
+ char *path;
+
+ /* Clean the failed services state dir now */
+ if ((dp = opendir(RC_SVCDIR "/failed"))) {
+ while ((d = readdir(dp))) {
+ if (d->d_name[0] == '.' &&
+ (d->d_name[1] == '\0' ||
+ (d->d_name[1] == '.' && d->d_name[2] == '\0')))
+ continue;
+
+ xasprintf(&path, RC_SVCDIR "/failed/%s", d->d_name);
+ if (unlink(path))
+ eerror("%s: unlink `%s': %s",
+ applet, path, strerror(errno));
+ free(path);
+ }
+ closedir(dp);
+ }
+}
+
+static void
+cleanup(void)
+{
+ RC_PID *p1 = LIST_FIRST(&service_pids);
+ RC_PID *p2;
+
+ if (!rc_in_logger && !rc_in_plugin &&
+ applet && (strcmp(applet, "rc") == 0 || strcmp(applet, "openrc") == 0))
+ {
+ if (hook_out)
+ rc_plugin_run(hook_out, runlevel);
+
+ rc_plugin_unload();
+
+ if (termios_orig) {
+ tcsetattr(STDIN_FILENO, TCSANOW, termios_orig);
+ free(termios_orig);
+ }
+
+ /* Clean runlevel start, stop markers */
+ rmdir(RC_STARTING);
+ rmdir(RC_STOPPING);
+ clean_failed();
+ rc_logger_close();
+ }
+
+ while (p1) {
+ p2 = LIST_NEXT(p1, entries);
+ free(p1);
+ p1 = p2;
+ }
+
+ rc_stringlist_free(main_hotplugged_services);
+ rc_stringlist_free(main_stop_services);
+ rc_stringlist_free(main_start_services);
+ rc_stringlist_free(main_types_nw);
+ rc_stringlist_free(main_types_nwua);
+ rc_deptree_free(main_deptree);
+ free(runlevel);
+}
+
+static char
+read_key(bool block)
+{
+ struct termios termios;
+ char c = 0;
+ int fd = STDIN_FILENO;
+
+ if (!isatty(fd))
+ return false;
+
+ /* Now save our terminal settings. We need to restore them at exit as
+ we will be changing it for non-blocking reads for Interactive */
+ if (!termios_orig) {
+ termios_orig = xmalloc(sizeof(*termios_orig));
+ tcgetattr(fd, termios_orig);
+ }
+
+ tcgetattr(fd, &termios);
+ termios.c_lflag &= ~(ICANON | ECHO);
+ if (block)
+ termios.c_cc[VMIN] = 1;
+ else {
+ termios.c_cc[VMIN] = 0;
+ termios.c_cc[VTIME] = 0;
+ }
+ tcsetattr(fd, TCSANOW, &termios);
+ if (read(fd, &c, 1) == -1)
+ eerror("read: %s", strerror(errno));
+ tcsetattr(fd, TCSANOW, termios_orig);
+ return c;
+}
+
+static bool
+want_interactive(void)
+{
+ char c;
+ static bool gotinteractive;
+ static bool interactive;
+
+ if (rc_yesno(getenv("EINFO_QUIET")))
+ return false;
+ if (!gotinteractive) {
+ gotinteractive = true;
+ interactive = rc_conf_yesno("rc_interactive");
+ }
+ if (!interactive)
+ return false;
+ c = read_key(false);
+ return (c == 'I' || c == 'i') ? true : false;
+}
+
+static void
+mark_interactive(void)
+{
+ FILE *fp = fopen(INTERACTIVE, "w");
+ if (fp)
+ fclose(fp);
+}
+
+static void
+run_program(const char *prog)
+{
+ struct sigaction sa;
+ sigset_t full;
+ sigset_t old;
+ pid_t pid;
+
+ /* We need to block signals until we have forked */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_DFL;
+ sigemptyset(&sa.sa_mask);
+ sigfillset(&full);
+ sigprocmask(SIG_SETMASK, &full, &old);
+ pid = fork();
+
+ if (pid == -1)
+ eerrorx("%s: fork: %s", applet, strerror(errno));
+ if (pid == 0) {
+ /* Restore default handlers */
+ sigaction(SIGCHLD, &sa, NULL);
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGQUIT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGUSR1, &sa, NULL);
+ sigaction(SIGWINCH, &sa, NULL);
+
+ /* Unmask signals */
+ sigprocmask(SIG_SETMASK, &old, NULL);
+
+ if (termios_orig)
+ tcsetattr(STDIN_FILENO, TCSANOW, termios_orig);
+
+ execl(prog, prog, (char *)NULL);
+ eerror("%s: unable to exec `%s': %s", applet, prog,
+ strerror(errno));
+ _exit(EXIT_FAILURE);
+ }
+
+ /* Unmask signals and wait for child */
+ sigprocmask(SIG_SETMASK, &old, NULL);
+ if (rc_waitpid(pid) == -1)
+ eerrorx("%s: failed to exec `%s'", applet, prog);
+}
+
+static void
+open_shell(void)
+{
+ const char *shell;
+ struct passwd *pw;
+
+#ifdef __linux__
+ const char *sys = rc_sys();
+
+ /* VSERVER systems cannot really drop to shells */
+ if (sys && strcmp(sys, RC_SYS_VSERVER) == 0)
+ {
+ execlp("halt", "halt", "-f", (char *) NULL);
+ eerrorx("%s: unable to exec `halt -f': %s",
+ applet, strerror(errno));
+ }
+#endif
+
+ shell = rc_conf_value("rc_shell");
+ /* No shell set, so obey env, then passwd, then default to /bin/sh */
+ if (shell == NULL) {
+ shell = getenv("SHELL");
+ if (shell == NULL) {
+ pw = getpwuid(getuid());
+ if (pw)
+ shell = pw->pw_shell;
+ if (shell == NULL)
+ shell = "/bin/sh";
+ }
+ }
+ run_program(shell);
+}
+
+static bool
+set_krunlevel(const char *level)
+{
+ FILE *fp;
+
+ if (!level ||
+ strcmp(level, getenv ("RC_BOOTLEVEL")) == 0 ||
+ strcmp(level, RC_LEVEL_SINGLE) == 0 ||
+ strcmp(level, RC_LEVEL_SYSINIT) == 0)
+ {
+ if (exists(RC_KRUNLEVEL) &&
+ unlink(RC_KRUNLEVEL) != 0)
+ eerror("unlink `%s': %s", RC_KRUNLEVEL,
+ strerror(errno));
+ return false;
+ }
+
+ if (!(fp = fopen(RC_KRUNLEVEL, "w"))) {
+ eerror("fopen `%s': %s", RC_KRUNLEVEL, strerror(errno));
+ return false;
+ }
+
+ fprintf(fp, "%s", level);
+ fclose(fp);
+ return true;
+}
+
+static char *get_krunlevel(void)
+{
+ char *buffer = NULL;
+ FILE *fp;
+ size_t i = 0;
+
+ if (!exists(RC_KRUNLEVEL))
+ return NULL;
+ if (!(fp = fopen(RC_KRUNLEVEL, "r"))) {
+ eerror("fopen `%s': %s", RC_KRUNLEVEL, strerror(errno));
+ return NULL;
+ }
+
+ if (getline(&buffer, &i, fp) != -1) {
+ i = strlen(buffer);
+ if (buffer[i - 1] == '\n')
+ buffer[i - 1] = 0;
+ }
+ fclose(fp);
+ return buffer;
+}
+
+static void
+add_pid(pid_t pid)
+{
+ RC_PID *p = xmalloc(sizeof(*p));
+ p->pid = pid;
+ LIST_INSERT_HEAD(&service_pids, p, entries);
+}
+
+static void
+remove_pid(pid_t pid)
+{
+ RC_PID *p;
+
+ LIST_FOREACH(p, &service_pids, entries)
+ if (p->pid == pid) {
+ LIST_REMOVE(p, entries);
+ free(p);
+ return;
+ }
+}
+
+static void
+wait_for_services(void)
+{
+ for (;;) {
+ while (waitpid(0, 0, 0) != -1)
+ ;
+ if (errno != EINTR)
+ break;
+ }
+}
+
+static void
+handle_signal(int sig)
+{
+ int serrno = errno;
+ char *signame = NULL;
+ pid_t pid;
+ RC_PID *pi;
+ int status = 0;
+ struct winsize ws;
+ sigset_t sset;
+
+ switch (sig) {
+ case SIGCHLD:
+ do {
+ pid = waitpid(-1, &status, WNOHANG);
+ if (pid < 0) {
+ if (errno != ECHILD)
+ eerror("waitpid: %s", strerror(errno));
+ return;
+ }
+ } while (!WIFEXITED(status) && !WIFSIGNALED(status));
+
+ /* Remove that pid from our list */
+ if (pid > 0)
+ remove_pid(pid);
+ break;
+
+ case SIGWINCH:
+ if (rc_logger_tty >= 0) {
+ ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);
+ ioctl(rc_logger_tty, TIOCSWINSZ, &ws);
+ }
+ break;
+
+ case SIGINT:
+ if (!signame)
+ xasprintf(&signame, "SIGINT");
+ /* FALLTHROUGH */
+ case SIGTERM:
+ if (!signame)
+ xasprintf(&signame, "SIGTERM");
+ /* FALLTHROUGH */
+ case SIGQUIT:
+ if (!signame)
+ xasprintf(&signame, "SIGQUIT");
+ eerrorx("%s: caught %s, aborting", applet, signame);
+ /* NOTREACHED */
+ case SIGUSR1:
+ eerror("rc: Aborting!");
+
+ /* Block child signals */
+ sigemptyset(&sset);
+ sigaddset(&sset, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &sset, NULL);
+
+ /* Kill any running services we have started */
+ LIST_FOREACH(pi, &service_pids, entries)
+ kill(pi->pid, SIGTERM);
+
+ /* Notify plugins we are aborting */
+ rc_plugin_run(RC_HOOK_ABORT, NULL);
+
+ exit(EXIT_FAILURE);
+ /* NOTREACHED */
+
+ default:
+ eerror("%s: caught unknown signal %d", applet, sig);
+ }
+
+ /* Restore errno */
+ errno = serrno;
+}
+
+static void
+do_sysinit()
+{
+ struct utsname uts;
+ const char *sys;
+
+ /* exec init-early.sh if it exists
+ * This should just setup the console to use the correct
+ * font. Maybe it should setup the keyboard too? */
+ if (exists(INITEARLYSH))
+ run_program(INITEARLYSH);
+
+ uname(&uts);
+ printf("\n %sOpenRC %s" VERSION "%s is starting up %s",
+ ecolor(ECOLOR_GOOD), ecolor(ECOLOR_HILITE),
+ ecolor(ECOLOR_NORMAL), ecolor(ECOLOR_BRACKET));
+#ifdef BRANDING
+ printf(BRANDING " (%s)", uts.machine);
+#else
+ printf("%s %s (%s)",
+ uts.sysname,
+ uts.release,
+ uts.machine);
+#endif
+
+ if ((sys = rc_sys()))
+ printf(" [%s]", sys);
+
+ printf("%s\n\n", ecolor(ECOLOR_NORMAL));
+
+ if (!rc_yesno(getenv ("EINFO_QUIET")) &&
+ rc_conf_yesno("rc_interactive"))
+ printf("Press %sI%s to enter interactive boot mode\n\n",
+ ecolor(ECOLOR_GOOD), ecolor(ECOLOR_NORMAL));
+
+ setenv("RC_RUNLEVEL", RC_LEVEL_SYSINIT, 1);
+ run_program(INITSH);
+
+ /* init may have mounted /proc so we can now detect or real
+ * sys */
+ if ((sys = rc_sys()))
+ setenv("RC_SYS", sys, 1);
+ /* force an update of the dependency tree */
+ if ((main_deptree = _rc_deptree_load(1, NULL)) == NULL)
+ eerrorx("failed to load deptree");
+}
+
+static bool
+runlevel_config(const char *service, const char *level)
+{
+ char *init = rc_service_resolve(service);
+ char *conf, *dir;
+ bool retval;
+
+ dir = dirname(init);
+ dir = dirname(init);
+ xasprintf(&conf, "%s/conf.d/%s.%s", dir, service, level);
+ retval = exists(conf);
+ free(conf);
+ free(init);
+ return retval;
+}
+
+static void
+do_stop_services(RC_STRINGLIST *types_nw, RC_STRINGLIST *start_services,
+ const RC_STRINGLIST *stop_services, const RC_DEPTREE *deptree,
+ const char *newlevel, bool parallel, bool going_down)
+{
+ pid_t pid;
+ RC_STRING *service, *svc1, *svc2;
+ RC_STRINGLIST *deporder, *tmplist, *kwords;
+ RC_SERVICE state;
+ RC_STRINGLIST *nostop;
+ bool crashed, nstop;
+
+ if (!types_nw) {
+ types_nw = rc_stringlist_new();
+ rc_stringlist_add(types_nw, "needsme");
+ rc_stringlist_add(types_nw, "wantsme");
+ }
+
+ crashed = rc_conf_yesno("rc_crashed_stop");
+
+ nostop = rc_stringlist_split(rc_conf_value("rc_nostop"), " ");
+ TAILQ_FOREACH_REVERSE(service, stop_services, rc_stringlist, entries)
+ {
+ state = rc_service_state(service->value);
+ if (state & RC_SERVICE_STOPPED || state & RC_SERVICE_FAILED)
+ continue;
+
+ /* Sometimes we don't ever want to stop a service. */
+ if (rc_stringlist_find(nostop, service->value)) {
+ rc_service_mark(service->value, RC_SERVICE_FAILED);
+ continue;
+ }
+ kwords = rc_deptree_depend(deptree, service->value, "keyword");
+ if (rc_stringlist_find(kwords, "-stop") ||
+ rc_stringlist_find(kwords, "nostop") ||
+ (going_down &&
+ (rc_stringlist_find(kwords, "-shutdown") ||
+ rc_stringlist_find(kwords, "noshutdown"))))
+ nstop = true;
+ else
+ nstop = false;
+ rc_stringlist_free(kwords);
+ if (nstop) {
+ rc_service_mark(service->value, RC_SERVICE_FAILED);
+ continue;
+ }
+
+ /* If the service has crashed, skip futher checks and just stop
+ it */
+ if (crashed &&
+ rc_service_daemons_crashed(service->value))
+ goto stop;
+
+ /* If we're in the start list then don't bother stopping us */
+ svc1 = rc_stringlist_find(start_services, service->value);
+ if (svc1) {
+ if (newlevel && strcmp(runlevel, newlevel) != 0) {
+ /* So we're in the start list. But we should
+ * be stopped if we have a runlevel
+ * configuration file for either the current
+ * or next so we use the correct one. */
+ if (!runlevel_config(service->value,runlevel) &&
+ !runlevel_config(service->value,newlevel))
+ continue;
+ }
+ else
+ continue;
+ }
+
+ /* We got this far. Last check is to see if any any service
+ * that going to be started depends on us */
+ if (!svc1) {
+ tmplist = rc_stringlist_new();
+ rc_stringlist_add(tmplist, service->value);
+ deporder = rc_deptree_depends(deptree, types_nw,
+ tmplist, newlevel ? newlevel : runlevel,
+ RC_DEP_STRICT | RC_DEP_TRACE);
+ rc_stringlist_free(tmplist);
+ svc2 = NULL;
+ TAILQ_FOREACH(svc1, deporder, entries) {
+ svc2 = rc_stringlist_find(start_services,
+ svc1->value);
+ if (svc2)
+ break;
+ }
+ rc_stringlist_free(deporder);
+
+ if (svc2)
+ continue;
+ }
+
+stop:
+ /* After all that we can finally stop the blighter! */
+ pid = service_stop(service->value);
+ if (pid > 0) {
+ add_pid(pid);
+ if (!parallel) {
+ rc_waitpid(pid);
+ remove_pid(pid);
+ }
+ }
+ }
+
+ rc_stringlist_free(nostop);
+}
+
+static void
+do_start_services(const RC_STRINGLIST *start_services, bool parallel)
+{
+ RC_STRING *service;
+ pid_t pid;
+ bool interactive = false;
+ RC_SERVICE state;
+ bool crashed = false;
+
+ if (!rc_yesno(getenv("EINFO_QUIET")))
+ interactive = exists(INTERACTIVE);
+ errno = 0;
+ crashed = rc_conf_yesno("rc_crashed_start");
+ if (errno == ENOENT)
+ crashed = true;
+
+ TAILQ_FOREACH(service, start_services, entries) {
+ state = rc_service_state(service->value);
+ if (state & RC_SERVICE_FAILED)
+ continue;
+ if (!(state & RC_SERVICE_STOPPED)) {
+ if (crashed &&
+ rc_service_daemons_crashed(service->value))
+ rc_service_mark(service->value,
+ RC_SERVICE_STOPPED);
+ else
+ continue;
+ }
+ if (!interactive)
+ interactive = want_interactive();
+
+ if (interactive) {
+ interactive_retry:
+ printf("\n");
+ einfo("About to start the service %s",
+ service->value);
+ eindent();
+ einfo("1) Start the service\t\t2) Skip the service");
+ einfo("3) Continue boot process\t\t4) Exit to shell");
+ eoutdent();
+ interactive_option:
+ switch (read_key(true)) {
+ case '1': break;
+ case '2': continue;
+ case '3': interactive = false; break;
+ case '4': open_shell(); goto interactive_retry;
+ default: goto interactive_option;
+ }
+ }
+
+ pid = service_start(service->value);
+ if (pid == -1)
+ break;
+ /* Remember the pid if we're running in parallel */
+ if (pid > 0) {
+ add_pid(pid);
+ if (!parallel) {
+ rc_waitpid(pid);
+ remove_pid(pid);
+ }
+ }
+ }
+
+ /* Store our interactive status for boot */
+ if (interactive &&
+ (strcmp(runlevel, RC_LEVEL_SYSINIT) == 0 ||
+ strcmp(runlevel, getenv("RC_BOOTLEVEL")) == 0))
+ mark_interactive();
+ else {
+ if (exists(INTERACTIVE))
+ unlink(INTERACTIVE);
+ }
+
+}
+
+#ifdef RC_DEBUG
+static void
+handle_bad_signal(int sig)
+{
+ char pid[10];
+ int status;
+ pid_t crashed_pid = getpid();
+
+ switch (fork()) {
+ case -1:
+ _exit(sig);
+ /* NOTREACHED */
+ case 0:
+ sprintf(pid, "%i", crashed_pid);
+ printf("\nAuto launching gdb!\n\n");
+ _exit(execlp("gdb", "gdb", "--quiet", "--pid", pid,
+ "-ex", "bt full", NULL));
+ /* NOTREACHED */
+ default:
+ wait(&status);
+ }
+ _exit(1);
+ /* NOTREACHED */
+}
+#endif
+
+int main(int argc, char **argv)
+{
+ const char *bootlevel = NULL;
+ char *newlevel = NULL;
+ const char *systype = NULL;
+ RC_STRINGLIST *deporder = NULL;
+ RC_STRINGLIST *tmplist;
+ RC_STRING *service;
+ bool going_down = false;
+ int depoptions = RC_DEP_STRICT | RC_DEP_TRACE;
+ char *krunlevel = NULL;
+ char *pidstr = NULL;
+ int opt;
+ bool parallel;
+ int regen = 0;
+ bool nostop = false;
+#ifdef __linux__
+ char *proc;
+ char *p;
+ char *token;
+#endif
+
+#ifdef RC_DEBUG
+ signal_setup(SIGBUS, handle_bad_signal);
+ signal_setup(SIGILL, handle_bad_signal);
+ signal_setup(SIGSEGV, handle_bad_signal);
+#endif
+
+ applet = basename_c(argv[0]);
+ LIST_INIT(&service_pids);
+ atexit(cleanup);
+ if (!applet)
+ eerrorx("arguments required");
+
+ argc--;
+ argv++;
+
+ /* Change dir to / to ensure all scripts don't use stuff in pwd */
+ if (chdir("/") == -1)
+ eerror("chdir: %s", strerror(errno));
+
+ /* Ensure our environment is pure
+ * Also, add our configuration to it */
+ env_filter();
+ env_config();
+
+ /* complain about old configuration settings if they exist */
+ if (exists(RC_CONF_OLD)) {
+ ewarn("%s still exists on your system and should be removed.",
+ RC_CONF_OLD);
+ ewarn("Please migrate to the appropriate settings in %s", RC_CONF);
+ }
+
+ argc++;
+ argv--;
+ while ((opt = getopt_long(argc, argv, getoptstring,
+ longopts, (int *) 0)) != -1)
+ {
+ switch (opt) {
+ case 'n':
+ nostop = true;
+ break;
+ case 'o':
+ if (*optarg == '\0')
+ optarg = NULL;
+ if (!rc_runlevel_exists(optarg)) {
+ eerror("runlevel `%s' does not exist", optarg);
+ exit(EXIT_FAILURE);
+ }
+ if (!set_krunlevel(optarg))
+ exit(EXIT_FAILURE);
+ einfo("Overriding next runlevel to %s", optarg);
+ exit(EXIT_SUCCESS);
+ /* NOTREACHED */
+ case 's':
+ newlevel = rc_service_resolve(optarg);
+ if (!newlevel)
+ eerrorx("%s: service `%s' does not exist",
+ applet, optarg);
+ argv += optind - 1;
+ *argv = newlevel;
+ execv(*argv, argv);
+ eerrorx("%s: %s", applet, strerror(errno));
+ /* NOTREACHED */
+ case 'S':
+ systype = rc_sys();
+ if (systype)
+ printf("%s\n", systype);
+ exit(EXIT_SUCCESS);
+ /* NOTREACHED */
+ case_RC_COMMON_GETOPT
+ }
+ }
+
+ if (strcmp(applet, "rc") == 0)
+ ewarn("rc is deprecated, please use openrc instead.");
+ newlevel = argv[optind++];
+ /* To make life easier, we only have the shutdown runlevel as
+ * nothing really needs to know that we're rebooting.
+ * But for those that do, you can test against RC_REBOOT. */
+ if (newlevel) {
+ if (strcmp(newlevel, "reboot") == 0) {
+ newlevel = UNCONST(RC_LEVEL_SHUTDOWN);
+ setenv("RC_REBOOT", "YES", 1);
+ }
+ }
+
+ /* Enable logging */
+ setenv("EINFO_LOG", "openrc", 1);
+
+ /* Export our PID */
+ xasprintf(&pidstr, "%d", getpid());
+ setenv("RC_PID", pidstr, 1);
+ free(pidstr);
+
+ /* Create a list of all services which should be started for the new or
+ * current runlevel including those in boot, sysinit and hotplugged
+ * runlevels. Clearly, some of these will already be started so we
+ * won't actually be starting them all.
+ */
+ bootlevel = getenv("RC_BOOTLEVEL");
+ runlevel = rc_runlevel_get();
+
+ rc_logger_open(newlevel ? newlevel : runlevel);
+
+ /* Setup a signal handler */
+ signal_setup(SIGINT, handle_signal);
+ signal_setup(SIGQUIT, handle_signal);
+ signal_setup(SIGTERM, handle_signal);
+ signal_setup(SIGUSR1, handle_signal);
+ signal_setup(SIGWINCH, handle_signal);
+
+ /* Run any special sysinit foo */
+ if (newlevel && strcmp(newlevel, RC_LEVEL_SYSINIT) == 0) {
+ do_sysinit();
+ free(runlevel);
+ runlevel = rc_runlevel_get();
+ }
+
+ rc_plugin_load();
+
+ /* Now we start handling our children */
+ signal_setup(SIGCHLD, handle_signal);
+
+ if (newlevel &&
+ (strcmp(newlevel, RC_LEVEL_SHUTDOWN) == 0 ||
+ strcmp(newlevel, RC_LEVEL_SINGLE) == 0))
+ {
+ going_down = true;
+ if (!exists(RC_KRUNLEVEL))
+ set_krunlevel(runlevel);
+ rc_runlevel_set(newlevel);
+ setenv("RC_RUNLEVEL", newlevel, 1);
+ setenv("RC_GOINGDOWN", "YES", 1);
+ } else {
+ /* We should not use krunevel in sysinit or boot runlevels */
+ if (!newlevel ||
+ (strcmp(newlevel, RC_LEVEL_SYSINIT) != 0 &&
+ strcmp(newlevel, getenv("RC_BOOTLEVEL")) != 0))
+ {
+ krunlevel = get_krunlevel();
+ if (krunlevel) {
+ newlevel = krunlevel;
+ set_krunlevel(NULL);
+ }
+ }
+
+ if (newlevel) {
+ if (strcmp(runlevel, newlevel) != 0 &&
+ !rc_runlevel_exists(newlevel))
+ eerrorx("%s: not a valid runlevel", newlevel);
+
+#ifdef __linux__
+ if (strcmp(newlevel, RC_LEVEL_SYSINIT) == 0) {
+ /* If we requested a runlevel, save it now */
+ p = rc_proc_getent("rc_runlevel");
+ if (p == NULL)
+ p = rc_proc_getent("softlevel");
+ if (p != NULL) {
+ set_krunlevel(p);
+ free(p);
+ }
+ }
+#endif
+ }
+ }
+
+ if (going_down) {
+#ifdef __FreeBSD__
+ /* FIXME: we shouldn't have todo this */
+ /* For some reason, wait_for_services waits for the logger
+ * proccess to finish as well, but only on FreeBSD.
+ * We cannot allow this so we stop logging now. */
+ rc_logger_close();
+#endif
+
+ rc_plugin_run(RC_HOOK_RUNLEVEL_STOP_IN, newlevel);
+ } else {
+ rc_plugin_run(RC_HOOK_RUNLEVEL_STOP_IN, runlevel);
+ }
+ hook_out = RC_HOOK_RUNLEVEL_STOP_OUT;
+
+ /* Check if runlevel is valid if we're changing */
+ if (newlevel && strcmp(runlevel, newlevel) != 0 && !going_down) {
+ if (!rc_runlevel_exists(newlevel))
+ eerrorx("%s: is not a valid runlevel", newlevel);
+ }
+
+ /* Load our deptree */
+ if ((main_deptree = _rc_deptree_load(0, &regen)) == NULL)
+ eerrorx("failed to load deptree");
+ if (exists(RC_DEPTREE_SKEWED))
+ ewarn("WARNING: clock skew detected!");
+
+ /* Clean the failed services state dir */
+ clean_failed();
+
+ if (mkdir(RC_STOPPING, 0755) != 0) {
+ if (errno == EACCES)
+ eerrorx("%s: superuser access required", applet);
+ eerrorx("%s: failed to create stopping dir `%s': %s",
+ applet, RC_STOPPING, strerror(errno));
+ }
+
+ /* Create a list of all services which we could stop (assuming
+ * they won't be active in the new or current runlevel) including
+ * all those services which have been started, are inactive or
+ * are currently starting. Clearly, some of these will be listed
+ * in the new or current runlevel so we won't actually be stopping
+ * them all.
+ */
+ main_stop_services = rc_services_in_state(RC_SERVICE_STARTED);
+ tmplist = rc_services_in_state(RC_SERVICE_INACTIVE);
+ TAILQ_CONCAT(main_stop_services, tmplist, entries);
+ free(tmplist);
+ tmplist = rc_services_in_state(RC_SERVICE_STARTING);
+ TAILQ_CONCAT(main_stop_services, tmplist, entries);
+ free(tmplist);
+ if (main_stop_services)
+ rc_stringlist_sort(&main_stop_services);
+
+ main_types_nwua = rc_stringlist_new();
+ rc_stringlist_add(main_types_nwua, "ineed");
+ rc_stringlist_add(main_types_nwua, "iwant");
+ rc_stringlist_add(main_types_nwua, "iuse");
+ rc_stringlist_add(main_types_nwua, "iafter");
+
+ if (main_stop_services) {
+ tmplist = rc_deptree_depends(main_deptree, main_types_nwua, main_stop_services,
+ runlevel, depoptions | RC_DEP_STOP);
+ rc_stringlist_free(main_stop_services);
+ main_stop_services = tmplist;
+ }
+
+ /* Create a list of all services which should be started for the new or
+ * current runlevel including those in boot, sysinit and hotplugged
+ * runlevels. Clearly, some of these will already be started so we
+ * won't actually be starting them all.
+ */
+ main_hotplugged_services = rc_services_in_state(RC_SERVICE_HOTPLUGGED);
+ main_start_services = rc_services_in_runlevel_stacked(newlevel ?
+ newlevel : runlevel);
+ if (strcmp(newlevel ? newlevel : runlevel, RC_LEVEL_SHUTDOWN) != 0 &&
+ strcmp(newlevel ? newlevel : runlevel, RC_LEVEL_SYSINIT) != 0)
+ {
+ tmplist = rc_services_in_runlevel(RC_LEVEL_SYSINIT);
+ TAILQ_CONCAT(main_start_services, tmplist, entries);
+ free(tmplist);
+ /* If we are NOT headed for the single-user runlevel... */
+ if (strcmp(newlevel ? newlevel : runlevel,
+ RC_LEVEL_SINGLE) != 0)
+ {
+ /* If we are NOT headed for the boot runlevel... */
+ if (strcmp(newlevel ? newlevel : runlevel,
+ bootlevel) != 0)
+ {
+ tmplist = rc_services_in_runlevel(bootlevel);
+ TAILQ_CONCAT(main_start_services, tmplist, entries);
+ free(tmplist);
+ }
+ if (main_hotplugged_services) {
+ TAILQ_FOREACH(service, main_hotplugged_services,
+ entries)
+ rc_stringlist_addu(main_start_services,
+ service->value);
+ }
+ }
+ }
+
+ parallel = rc_conf_yesno("rc_parallel");
+
+ /* Now stop the services that shouldn't be running */
+ if (main_stop_services && !nostop)
+ do_stop_services(main_types_nw, main_start_services, main_stop_services, main_deptree, newlevel, parallel, going_down);
+
+ /* Wait for our services to finish */
+ wait_for_services();
+
+ /* Notify the plugins we have finished */
+ rc_plugin_run(RC_HOOK_RUNLEVEL_STOP_OUT,
+ going_down ? newlevel : runlevel);
+ hook_out = 0;
+
+ rmdir(RC_STOPPING);
+
+ /* Store the new runlevel */
+ if (newlevel) {
+ rc_runlevel_set(newlevel);
+ free(runlevel);
+ runlevel = xstrdup(newlevel);
+ setenv("RC_RUNLEVEL", runlevel, 1);
+ }
+
+#ifdef __linux__
+ /* We can't log beyond this point as the shutdown runlevel
+ * will mount / readonly. */
+ if (strcmp(runlevel, RC_LEVEL_SHUTDOWN) == 0)
+ rc_logger_close();
+#endif
+
+ mkdir(RC_STARTING, 0755);
+ rc_plugin_run(RC_HOOK_RUNLEVEL_START_IN, runlevel);
+ hook_out = RC_HOOK_RUNLEVEL_START_OUT;
+
+ /* Re-add our hotplugged services if they stopped */
+ if (main_hotplugged_services)
+ TAILQ_FOREACH(service, main_hotplugged_services, entries)
+ rc_service_mark(service->value, RC_SERVICE_HOTPLUGGED);
+
+#ifdef __linux__
+ /* If the "noinit" parameter was passed on the kernel command line then
+ * mark the specified services as started so they will not be started
+ * by us. */
+ proc = p = rc_proc_getent("noinit");
+ if (proc) {
+ while ((token = strsep(&p, ",")))
+ rc_service_mark(token, RC_SERVICE_STARTED);
+ free(proc);
+ }
+#endif
+
+ /* If we have a list of services to start then... */
+ if (main_start_services) {
+ /* Get a list of the chained runlevels which compose the target runlevel */
+ RC_STRINGLIST *runlevel_chain = rc_runlevel_stacks(runlevel);
+
+ /* Loop through them in reverse order. */
+ RC_STRING *rlevel;
+ TAILQ_FOREACH_REVERSE(rlevel, runlevel_chain, rc_stringlist, entries)
+ {
+ /* Get a list of all the services in that runlevel */
+ RC_STRINGLIST *run_services = rc_services_in_runlevel(rlevel->value);
+
+ /* Start those services. */
+ rc_stringlist_sort(&run_services);
+ deporder = rc_deptree_depends(main_deptree, main_types_nwua, run_services, rlevel->value, depoptions | RC_DEP_START);
+ rc_stringlist_free(run_services);
+ run_services = deporder;
+ do_start_services(run_services, parallel);
+
+ /* Wait for our services to finish */
+ wait_for_services();
+
+ /* Free the list of services, we're done with it. */
+ rc_stringlist_free(run_services);
+ }
+ rc_stringlist_free(runlevel_chain);
+ }
+
+#ifdef __linux__
+ /* If the "noinit" parameter was passed on the kernel command line then
+ * mark the specified services as stopped so that our records reflect
+ * reality. */
+ proc = p = rc_proc_getent("noinit");
+ if (proc) {
+ while ((token = strsep(&p, ",")))
+ rc_service_mark(token, RC_SERVICE_STOPPED);
+ free(proc);
+ }
+
+#endif
+
+ rc_plugin_run(RC_HOOK_RUNLEVEL_START_OUT, runlevel);
+ hook_out = 0;
+
+ /* If we're in the boot runlevel and we regenerated our dependencies
+ * we need to delete them so that they are regenerated again in the
+ * default runlevel as they may depend on things that are now
+ * available */
+ if (regen && strcmp(runlevel, bootlevel) == 0)
+ unlink(RC_DEPTREE_CACHE);
+
+ return EXIT_SUCCESS;
+}