aboutsummaryrefslogtreecommitdiff
path: root/src/rc/rc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/rc/rc.c')
-rw-r--r--src/rc/rc.c1574
1 files changed, 1574 insertions, 0 deletions
diff --git a/src/rc/rc.c b/src/rc/rc.c
new file mode 100644
index 00000000..a33b6dcf
--- /dev/null
+++ b/src/rc/rc.c
@@ -0,0 +1,1574 @@
+/*
+ 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 2007-2008 Roy Marples
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+const char rc_copyright[] = "Copyright (c) 2007-2008 Roy Marples";
+
+#define SYSLOG_NAMES
+
+#include <sys/types.h>
+#include <sys/ioctl.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 <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "builtins.h"
+#include "einfo.h"
+#include "rc.h"
+#include "rc-logger.h"
+#include "rc-misc.h"
+#include "rc-plugin.h"
+#include "strlist.h"
+
+#include "version.h"
+
+#define INITSH RC_LIBDIR "/sh/init.sh"
+#define INITEARLYSH RC_LIBDIR "/sh/init-early.sh"
+#define HALTSH RC_INITDIR "/halt.sh"
+
+#define SHUTDOWN "/sbin/shutdown"
+#define SULOGIN "/sbin/sulogin"
+
+#define INTERACTIVE RC_SVCDIR "/interactive"
+
+#define DEVBOOT "/dev/.rcboot"
+
+/* Cleanup anything in main */
+#define CHAR_FREE(_item) if (_item) { \
+ free (_item); \
+ _item = NULL; \
+}
+
+extern char **environ;
+
+static char *RUNLEVEL = NULL;
+static char *PREVLEVEL = NULL;
+
+static const char *applet = NULL;
+static char *runlevel = NULL;
+static char **env = NULL;
+static char **newenv = NULL;
+static char **coldplugged_services = NULL;
+static char **stop_services = NULL;
+static char **start_services = NULL;
+static rc_depinfo_t *deptree = NULL;
+static char *tmp = NULL;
+
+struct termios *termios_orig = NULL;
+
+typedef struct pidlist
+{
+ pid_t pid;
+ struct pidlist *next;
+} pidlist_t;
+static pidlist_t *service_pids = NULL;
+
+static const char *const types_n[] = { "needsme", NULL };
+static const char *const types_nua[] = { "ineed", "iuse", "iafter", NULL };
+
+static void clean_failed (void)
+{
+ DIR *dp;
+ struct dirent *d;
+ int i;
+ 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;
+
+ i = strlen (RC_SVCDIR "/failed/") + strlen (d->d_name) + 1;
+ path = xmalloc (sizeof (char) * i);
+ snprintf (path, i, RC_SVCDIR "/failed/%s", d->d_name);
+ if (path) {
+ if (unlink (path))
+ eerror ("%s: unlink `%s': %s", applet, path,
+ strerror (errno));
+ free (path);
+ }
+ }
+ closedir (dp);
+ }
+}
+
+static void cleanup (void)
+{
+ if (applet && strcmp (applet, "rc") == 0) {
+ pidlist_t *pl = service_pids;
+
+ rc_plugin_unload ();
+
+ if (! rc_in_plugin && termios_orig) {
+ tcsetattr (fileno (stdin), TCSANOW, termios_orig);
+ free (termios_orig);
+ }
+
+ while (pl) {
+ pidlist_t *p = pl->next;
+ free (pl);
+ pl = p;
+ }
+
+ rc_strlist_free (env);
+ rc_strlist_free (newenv);
+ rc_strlist_free (coldplugged_services);
+ rc_strlist_free (stop_services);
+ rc_strlist_free (start_services);
+ rc_deptree_free (deptree);
+
+ /* Clean runlevel start, stop markers */
+ if (! rc_in_plugin && ! rc_in_logger) {
+ rmdir (RC_STARTING);
+ rmdir (RC_STOPPING);
+ clean_failed ();
+
+ rc_logger_close ();
+ }
+
+ free (runlevel);
+ }
+}
+
+static int syslog_decode (char *name, CODE *codetab)
+{
+ CODE *c;
+
+ if (isdigit (*name))
+ return (atoi (name));
+
+ for (c = codetab; c->c_name; c++)
+ if (! strcasecmp (name, c->c_name))
+ return (c->c_val);
+
+ return (-1);
+}
+
+static int do_e (int argc, char **argv)
+{
+ int retval = EXIT_SUCCESS;
+ int i;
+ int l = 0;
+ char *message = NULL;
+ char *p;
+ char *fmt = NULL;
+ int level = 0;
+
+ if (strcmp (applet, "eval_ecolors") == 0) {
+ printf ("GOOD='%s'\nWARN='%s'\nBAD='%s'\nHILITE='%s'\nBRACKET='%s'\nNORMAL='%s'\n",
+ ecolor (ECOLOR_GOOD),
+ ecolor (ECOLOR_WARN),
+ ecolor (ECOLOR_BAD),
+ ecolor (ECOLOR_HILITE),
+ ecolor (ECOLOR_BRACKET),
+ ecolor (ECOLOR_NORMAL));
+ exit (EXIT_SUCCESS);
+ }
+
+ if (argc > 0) {
+
+ if (strcmp (applet, "eend") == 0 ||
+ strcmp (applet, "ewend") == 0 ||
+ strcmp (applet, "veend") == 0 ||
+ strcmp (applet, "vweend") == 0)
+ {
+ errno = 0;
+ retval = strtol (argv[0], NULL, 0);
+ if (errno != 0)
+ retval = EXIT_FAILURE;
+ else {
+ argc--;
+ argv++;
+ }
+ } else if (strcmp (applet, "esyslog") == 0 ||
+ strcmp (applet, "elog") == 0) {
+ char *dot = strchr (argv[0], '.');
+ if ((level = syslog_decode (dot + 1, prioritynames)) == -1)
+ eerrorx ("%s: invalid log level `%s'", applet, argv[0]);
+
+ if (argc < 3)
+ eerrorx ("%s: not enough arguments", applet);
+
+ unsetenv ("EINFO_LOG");
+ setenv ("EINFO_LOG", argv[1], 1);
+
+ argc -= 2;
+ argv += 2;
+ }
+ }
+
+ if (argc > 0) {
+ for (i = 0; i < argc; i++)
+ l += strlen (argv[i]) + 1;
+
+ message = xmalloc (l);
+ p = message;
+
+ for (i = 0; i < argc; i++) {
+ if (i > 0)
+ *p++ = ' ';
+ memcpy (p, argv[i], strlen (argv[i]));
+ p += strlen (argv[i]);
+ }
+ *p = 0;
+ }
+
+ if (message)
+ fmt = xstrdup ("%s");
+
+ if (strcmp (applet, "einfo") == 0)
+ einfo (fmt, message);
+ else if (strcmp (applet, "einfon") == 0)
+ einfon (fmt, message);
+ else if (strcmp (applet, "ewarn") == 0)
+ ewarn (fmt, message);
+ else if (strcmp (applet, "ewarnn") == 0)
+ ewarnn (fmt, message);
+ else if (strcmp (applet, "eerror") == 0) {
+ eerror (fmt, message);
+ retval = 1;
+ } else if (strcmp (applet, "eerrorn") == 0) {
+ eerrorn (fmt, message);
+ retval = 1;
+ } else if (strcmp (applet, "ebegin") == 0)
+ ebegin (fmt, message);
+ else if (strcmp (applet, "eend") == 0)
+ eend (retval, fmt, message);
+ else if (strcmp (applet, "ewend") == 0)
+ ewend (retval, fmt, message);
+ else if (strcmp (applet, "esyslog") == 0)
+ elog (level, fmt, message);
+ else if (strcmp (applet, "veinfo") == 0)
+ einfov (fmt, message);
+ else if (strcmp (applet, "veinfon") == 0)
+ einfovn (fmt, message);
+ else if (strcmp (applet, "vewarn") == 0)
+ ewarnv (fmt, message);
+ else if (strcmp (applet, "vewarnn") == 0)
+ ewarnvn (fmt, message);
+ else if (strcmp (applet, "vebegin") == 0)
+ ebeginv (fmt, message);
+ else if (strcmp (applet, "veend") == 0)
+ eendv (retval, fmt, message);
+ else if (strcmp (applet, "vewend") == 0)
+ ewendv (retval, fmt, message);
+ else if (strcmp (applet, "eindent") == 0)
+ eindent ();
+ else if (strcmp (applet, "eoutdent") == 0)
+ eoutdent ();
+ else if (strcmp (applet, "veindent") == 0)
+ eindentv ();
+ else if (strcmp (applet, "veoutdent") == 0)
+ eoutdentv ();
+ else {
+ eerror ("%s: unknown applet", applet);
+ retval = EXIT_FAILURE;
+ }
+
+ if (fmt)
+ free (fmt);
+ if (message)
+ free (message);
+ return (retval);
+}
+
+static int do_service (int argc, char **argv)
+{
+ bool ok = false;
+ char *service = NULL;
+
+ if (argc > 0)
+ service = argv[0];
+ else
+ service = getenv ("SVCNAME");
+
+ if (! service || strlen (service) == 0)
+ eerrorx ("%s: no service specified", applet);
+
+ if (strcmp (applet, "service_started") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_STARTED);
+ else if (strcmp (applet, "service_stopped") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_STOPPED);
+ else if (strcmp (applet, "service_inactive") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_INACTIVE);
+ else if (strcmp (applet, "service_starting") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_STARTING);
+ else if (strcmp (applet, "service_stopping") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_STOPPING);
+ else if (strcmp (applet, "service_coldplugged") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_COLDPLUGGED);
+ else if (strcmp (applet, "service_wasinactive") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_WASINACTIVE);
+ else if (strcmp (applet, "service_started_daemon") == 0) {
+ int idx = 0;
+ char *d = argv[0];
+
+ service = getenv ("SVCNAME");
+ if (argc > 2) {
+ service = argv[0];
+ d = argv[1];
+ sscanf (argv[2], "%d", &idx);
+ } else if (argc == 2) {
+ sscanf (argv[1], "%d", &idx);
+ }
+ exit (rc_service_started_daemon (service, d, idx) ? 0 : 1);
+ } else
+ eerrorx ("%s: unknown applet", applet);
+
+ return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+static int do_mark_service (int argc, char **argv)
+{
+ bool ok = false;
+ char *svcname = getenv ("SVCNAME");
+ char *service = NULL;
+
+ if (argc > 0)
+ service = argv[0];
+ else
+ service = getenv ("SVCNAME");
+
+ if (! service || strlen (service) == 0)
+ eerrorx ("%s: no service specified", applet);
+
+ if (strcmp (applet, "mark_service_started") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_STARTED);
+ else if (strcmp (applet, "mark_service_stopped") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_STOPPED);
+ else if (strcmp (applet, "mark_service_inactive") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_INACTIVE);
+ else if (strcmp (applet, "mark_service_starting") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_STARTING);
+ else if (strcmp (applet, "mark_service_stopping") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_STOPPING);
+ else if (strcmp (applet, "mark_service_coldplugged") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_COLDPLUGGED);
+ else if (strcmp (applet, "mark_service_failed") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_FAILED);
+ else
+ eerrorx ("%s: unknown applet", applet);
+
+ /* If we're marking ourselves then we need to inform our parent runscript
+ process so they do not mark us based on our exit code */
+ if (ok && svcname && strcmp (svcname, service) == 0) {
+ char *runscript_pid = getenv ("RC_RUNSCRIPT_PID");
+ char *mtime;
+ pid_t pid = 0;
+ int l;
+
+ if (runscript_pid && sscanf (runscript_pid, "%d", &pid) == 1)
+ if (kill (pid, SIGHUP) != 0)
+ eerror ("%s: failed to signal parent %d: %s",
+ applet, pid, strerror (errno));
+
+ /* Remove the exclusive time test. This ensures that it's not
+ in control as well */
+ l = strlen (RC_SVCDIR "exclusive") +
+ strlen (svcname) +
+ strlen (runscript_pid) +
+ 4;
+ mtime = xmalloc (l);
+ snprintf (mtime, l, RC_SVCDIR "exclusive/%s.%s",
+ svcname, runscript_pid);
+ if (exists (mtime) && unlink (mtime) != 0)
+ eerror ("%s: unlink: %s", applet, strerror (errno));
+ free (mtime);
+ }
+
+ return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+static int do_value (int argc, char **argv)
+{
+ bool ok = false;
+ char *service = getenv ("SVCNAME");
+
+ if (! service)
+ eerrorx ("%s: no service specified", applet);
+
+ if (argc < 1 || ! argv[0] || strlen (argv[0]) == 0)
+ eerrorx ("%s: no option specified", applet);
+
+ if (strcmp (applet, "service_get_value") == 0 ||
+ strcmp (applet, "get_options") == 0)
+ {
+ char *option = rc_service_value_get (service, argv[0]);
+ if (option) {
+ printf ("%s", option);
+ free (option);
+ ok = true;
+ }
+ } else if (strcmp (applet, "service_set_value") == 0 ||
+ strcmp (applet, "save_options") == 0)
+ ok = rc_service_value_set (service, argv[0], argv[1]);
+ else
+ eerrorx ("%s: unknown applet", applet);
+
+ return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+static int do_shell_var (int argc, char **argv)
+{
+ int i;
+
+ for (i = 0; i < argc; i++) {
+ char *p = argv[i];
+
+ if (i != 0)
+ putchar (' ');
+
+ while (*p) {
+ char c = *p++;
+ if (! isalnum (c))
+ c = '_';
+ putchar (c);
+ }
+ }
+ putchar ('\n');
+
+ return (EXIT_SUCCESS);
+}
+
+#ifdef __linux__
+static char *proc_getent (const char *ent)
+{
+ FILE *fp;
+ char *buffer;
+ char *p;
+ char *value = NULL;
+ int i;
+
+ if (! exists ("/proc/cmdline"))
+ return (NULL);
+
+ if (! (fp = fopen ("/proc/cmdline", "r"))) {
+ eerror ("failed to open `/proc/cmdline': %s", strerror (errno));
+ return (NULL);
+ }
+
+ buffer = xmalloc (sizeof (char) * RC_LINEBUFFER);
+ memset (buffer, 0, RC_LINEBUFFER);
+ if (fgets (buffer, RC_LINEBUFFER, fp) &&
+ (p = strstr (buffer, ent)))
+ {
+ i = p - buffer;
+ if (i == '\0' || buffer[i - 1] == ' ') {
+ /* Trim the trailing carriage return if present */
+ i = strlen (buffer) - 1;
+ if (buffer[i] == '\n')
+ buffer[i] = 0;
+
+ p += strlen (ent);
+ if (*p == '=')
+ p++;
+ value = xstrdup (strsep (&p, " "));
+ }
+ } else
+ errno = ENOENT;
+ free (buffer);
+ fclose (fp);
+
+ return (value);
+}
+#endif
+
+static char read_key (bool block)
+{
+ struct termios termios;
+ char c = 0;
+ int fd = fileno (stdin);
+
+ 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 (struct termios));
+ 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);
+
+ read (fd, &c, 1);
+
+ 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 (PREVLEVEL &&
+ strcmp (PREVLEVEL, "N") != 0 &&
+ strcmp (PREVLEVEL, "S") != 0 &&
+ strcmp (PREVLEVEL, "1") != 0)
+ 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 sulogin (bool cont)
+{
+#ifdef __linux__
+ char *e = getenv ("RC_SYS");
+
+ /* VPS systems cannot do a sulogin */
+ if (e && strcmp (e, "VPS") == 0) {
+ execl ("/sbin/halt", "/sbin/halt", "-f", (char *) NULL);
+ eerrorx ("%s: unable to exec `/sbin/halt': %s", applet, strerror (errno));
+ }
+#endif
+
+ newenv = env_filter ();
+
+ if (cont) {
+ int status = 0;
+#ifdef __linux__
+ char *tty = ttyname (STDOUT_FILENO);
+#endif
+
+ pid_t pid = vfork ();
+
+ if (pid == -1)
+ eerrorx ("%s: vfork: %s", applet, strerror (errno));
+ if (pid == 0) {
+#ifdef __linux__
+ if (tty)
+ execle (SULOGIN, SULOGIN, tty, (char *) NULL, newenv);
+ else
+ execle (SULOGIN, SULOGIN, (char *) NULL, newenv);
+
+ eerror ("%s: unable to exec `%s': %s", applet, SULOGIN,
+ strerror (errno));
+#else
+ execle ("/bin/sh", "/bin/sh", (char *) NULL, newenv);
+ eerror ("%s: unable to exec `/bin/sh': %s", applet,
+ strerror (errno));
+#endif
+ _exit (EXIT_FAILURE);
+ }
+ waitpid (pid, &status, 0);
+ } else {
+ rc_logger_close ();
+
+#ifdef __linux__
+ execle ("/sbin/sulogin", "/sbin/sulogin", (char *) NULL, newenv);
+ eerrorx ("%s: unable to exec `/sbin/sulogin': %s", applet, strerror (errno));
+#else
+ exit (EXIT_SUCCESS);
+#endif
+ }
+}
+
+static void single_user (void)
+{
+ rc_logger_close ();
+
+#ifdef __linux__
+ execl ("/sbin/telinit", "/sbin/telinit", "S", (char *) NULL);
+ eerrorx ("%s: unable to exec `/sbin/telinit': %s",
+ applet, strerror (errno));
+#else
+ if (kill (1, SIGTERM) != 0)
+ eerrorx ("%s: unable to send SIGTERM to init (pid 1): %s",
+ applet, strerror (errno));
+ exit (EXIT_SUCCESS);
+#endif
+}
+
+static bool set_ksoftlevel (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_KSOFTLEVEL) &&
+ unlink (RC_KSOFTLEVEL) != 0)
+ eerror ("unlink `%s': %s", RC_KSOFTLEVEL, strerror (errno));
+ return (false);
+ }
+
+ if (! (fp = fopen (RC_KSOFTLEVEL, "w"))) {
+ eerror ("fopen `%s': %s", RC_KSOFTLEVEL, strerror (errno));
+ return (false);
+ }
+
+ fprintf (fp, "%s", level);
+ fclose (fp);
+ return (true);
+}
+
+static int get_ksoftlevel (char *buffer, int buffer_len)
+{
+ FILE *fp;
+ int i = 0;
+
+ if (! exists (RC_KSOFTLEVEL))
+ return (0);
+
+ if (! (fp = fopen (RC_KSOFTLEVEL, "r"))) {
+ eerror ("fopen `%s': %s", RC_KSOFTLEVEL, strerror (errno));
+ return (-1);
+ }
+
+ if (fgets (buffer, buffer_len, fp)) {
+ i = strlen (buffer) - 1;
+ if (buffer[i] == '\n')
+ buffer[i] = 0;
+ }
+
+ fclose (fp);
+ return (i);
+}
+
+static void add_pid (pid_t pid)
+{
+ pidlist_t *sp = service_pids;
+ if (sp) {
+ while (sp->next)
+ sp = sp->next;
+ sp->next = xmalloc (sizeof (pidlist_t));
+ sp = sp->next;
+ } else
+ sp = service_pids = xmalloc (sizeof (pidlist_t));
+ memset (sp, 0, sizeof (pidlist_t));
+ sp->pid = pid;
+}
+
+static void remove_pid (pid_t pid)
+{
+ pidlist_t *last = NULL;
+ pidlist_t *pl;
+
+ for (pl = service_pids; pl; pl = pl->next) {
+ if (pl->pid == pid) {
+ if (last)
+ last->next = pl->next;
+ else
+ service_pids = pl->next;
+ free (pl);
+ break;
+ }
+ last = pl;
+ }
+}
+
+static void wait_for_services ()
+{
+ while (waitpid (0, 0, 0) != -1);
+}
+
+static void handle_signal (int sig)
+{
+ int serrno = errno;
+ char signame[10] = { '\0' };
+ pidlist_t *pl;
+ pid_t pid;
+ int status = 0;
+ struct winsize ws;
+
+ 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[0])
+ snprintf (signame, sizeof (signame), "SIGINT");
+ case SIGTERM:
+ if (! signame[0])
+ snprintf (signame, sizeof (signame), "SIGTERM");
+ case SIGQUIT:
+ if (! signame[0])
+ snprintf (signame, sizeof (signame), "SIGQUIT");
+ eerrorx ("%s: caught %s, aborting", applet, signame);
+ case SIGUSR1:
+ eerror ("rc: Aborting!");
+ /* Kill any running services we have started */
+
+ signal (SIGCHLD, SIG_IGN);
+ for (pl = service_pids; pl; pl = pl->next)
+ kill (pl->pid, SIGTERM);
+
+ /* Notify plugins we are aborting */
+ rc_plugin_run (RC_HOOK_ABORT, NULL);
+
+ /* Only drop into single user mode if we're booting */
+ if ((PREVLEVEL &&
+ (strcmp (PREVLEVEL, "S") == 0 ||
+ strcmp (PREVLEVEL, "1") == 0)) ||
+ (RUNLEVEL &&
+ (strcmp (RUNLEVEL, "S") == 0 ||
+ strcmp (RUNLEVEL, "1") == 0)))
+ single_user ();
+
+ exit (EXIT_FAILURE);
+ break;
+
+ default:
+ eerror ("%s: caught unknown signal %d", applet, sig);
+ }
+
+ /* Restore errno */
+ errno = serrno;
+}
+
+static void run_script (const char *script)
+{
+ int status = 0;
+ pid_t pid = vfork ();
+
+ if (pid < 0)
+ eerrorx ("%s: vfork: %s", applet, strerror (errno));
+ else if (pid == 0) {
+ execl (script, script, (char *) NULL);
+ eerror ("%s: unable to exec `%s': %s",
+ script, applet, strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
+
+ do {
+ pid_t wpid = waitpid (pid, &status, 0);
+ if (wpid < 1)
+ eerror ("waitpid: %s", strerror (errno));
+ } while (! WIFEXITED (status) && ! WIFSIGNALED (status));
+
+ if (! WIFEXITED (status) || ! WEXITSTATUS (status) == 0)
+ eerrorx ("%s: failed to exec `%s'", applet, script);
+}
+
+#include "_usage.h"
+#define getoptstring "o:" getoptstring_COMMON
+static struct option longopts[] = {
+ { "override", 1, NULL, 'o' },
+ longopts_COMMON
+};
+static const char * const longopts_help[] = {
+ "override the next runlevel to change into\nwhen leaving single user or boot runlevels",
+ longopts_help_COMMON
+};
+#include "_usage.c"
+
+int main (int argc, char **argv)
+{
+ const char *bootlevel = NULL;
+ char *newlevel = NULL;
+ char *service = NULL;
+ char **deporder = NULL;
+ char **tmplist;
+ int i = 0;
+ int j = 0;
+ bool going_down = false;
+ bool interactive = false;
+ int depoptions = RC_DEP_STRICT | RC_DEP_TRACE;
+ char ksoftbuffer [PATH_MAX];
+ char pidstr[6];
+ int opt;
+ DIR *dp;
+ struct dirent *d;
+ bool parallel;
+ int regen = 0;
+
+ applet = basename_c (argv[0]);
+ atexit (cleanup);
+ if (! applet)
+ eerrorx ("arguments required");
+
+ if (argc > 1 && (strcmp (argv[1], "--version") == 0)) {
+ printf ("%s (OpenRC"
+#ifdef BRANDING
+ " " BRANDING
+#endif
+ ") version " VERSION "\n", applet);
+ exit (EXIT_SUCCESS);
+ }
+
+ /* These used to be programs in their own right, so we shouldn't
+ * touch argc or argv for them */
+ if (strcmp (applet, "fstabinfo") == 0)
+ exit (fstabinfo (argc, argv));
+ else if (strcmp (applet, "mountinfo") == 0)
+ exit (mountinfo (argc, argv));
+ else if (strcmp (applet, "rc-depend") == 0)
+ exit (rc_depend (argc, argv));
+ else if (strcmp (applet, "rc-status") == 0)
+ exit (rc_status (argc, argv));
+ else if (strcmp (applet, "rc-update") == 0 ||
+ strcmp (applet, "update-rc") == 0)
+ exit (rc_update (argc, argv));
+ else if (strcmp (applet, "runscript") == 0)
+ exit (runscript (argc, argv));
+ else if (strcmp (applet, "start-stop-daemon") == 0)
+ exit (start_stop_daemon (argc, argv));
+ else if (strcmp (applet, "checkpath") == 0)
+ exit (checkpath (argc, argv));
+
+ argc--;
+ argv++;
+
+ /* Handle multicall stuff */
+ if (applet[0] == 'e' || (applet[0] == 'v' && applet[1] == 'e'))
+ exit (do_e (argc, argv));
+
+ if (strcmp (applet, "service_get_value") == 0 ||
+ strcmp (applet, "service_set_value") == 0 ||
+ strcmp (applet, "get_options") == 0 ||
+ strcmp (applet, "save_options") == 0)
+ exit (do_value (argc, argv));
+
+ if (strncmp (applet, "service_", strlen ("service_")) == 0)
+ exit (do_service (argc, argv));
+
+ if (strncmp (applet, "mark_service_", strlen ("mark_service_")) == 0)
+ exit (do_mark_service (argc, argv));
+
+ if (strcmp (applet, "is_runlevel_start") == 0)
+ exit (rc_runlevel_starting () ? 0 : 1);
+ else if (strcmp (applet, "is_runlevel_stop") == 0)
+ exit (rc_runlevel_stopping () ? 0 : 1);
+
+ if (strcmp (applet, "shell_var") == 0)
+ exit (do_shell_var (argc, argv));
+
+ if (strcmp (applet, "rc-abort") == 0) {
+ char *p = getenv ("RC_PID");
+ pid_t pid = 0;
+
+ if (p && sscanf (p, "%d", &pid) == 1) {
+ if (kill (pid, SIGUSR1) != 0)
+ eerrorx ("rc-abort: failed to signal parent %d: %s",
+ pid, strerror (errno));
+ exit (EXIT_SUCCESS);
+ }
+ exit (EXIT_FAILURE);
+ }
+
+ if (strcmp (applet, "rc" ) != 0)
+ eerrorx ("%s: unknown applet", applet);
+
+ /* Change dir to / to ensure all scripts don't use stuff in pwd */
+ chdir ("/");
+
+ /* RUNLEVEL is set by sysvinit as is a magic number
+ RC_SOFTLEVEL is set by us and is the name for this magic number
+ even though all our userland documentation refers to runlevel */
+ RUNLEVEL = getenv ("RUNLEVEL");
+ PREVLEVEL = getenv ("PREVLEVEL");
+
+ /* Ensure our environment is pure
+ Also, add our configuration to it */
+ env = env_filter ();
+ tmplist = env_config ();
+ rc_strlist_join (&env, tmplist);
+ rc_strlist_free (tmplist);
+
+ if (env) {
+ char *p;
+
+#ifdef __linux__
+ /* clearenv isn't portable, but there's no harm in using it
+ if we have it */
+ clearenv ();
+#else
+ char *var;
+ /* No clearenv present here then.
+ We could manipulate environ directly ourselves, but it seems that
+ some kernels bitch about this according to the environ man pages
+ so we walk though environ and call unsetenv for each value. */
+ while (environ[0]) {
+ tmp = xstrdup (environ[0]);
+ p = tmp;
+ var = strsep (&p, "=");
+ unsetenv (var);
+ free (tmp);
+ }
+ tmp = NULL;
+#endif
+
+ STRLIST_FOREACH (env, p, i)
+ if (strcmp (p, "RC_SOFTLEVEL") != 0 && strcmp (p, "SOFTLEVEL") != 0)
+ putenv (p);
+
+ /* We don't free our list as that would be null in environ */
+ }
+
+ argc++;
+ argv--;
+ while ((opt = getopt_long (argc, argv, getoptstring,
+ longopts, (int *) 0)) != -1)
+ {
+ switch (opt) {
+ case 'o':
+ if (strlen (optarg) == 0)
+ optarg = NULL;
+ exit (set_ksoftlevel (optarg) ? EXIT_SUCCESS : EXIT_FAILURE);
+ case_RC_COMMON_GETOPT
+ }
+ }
+
+ newlevel = argv[optind++];
+
+ /* OK, so we really are the main RC process
+ Only root should be able to run us */
+ if (geteuid () != 0)
+ eerrorx ("%s: root access required", applet);
+
+ /* Enable logging */
+ setenv ("EINFO_LOG", "rc", 1);
+
+ /* Export our PID */
+ snprintf (pidstr, sizeof (pidstr), "%d", getpid ());
+ setenv ("RC_PID", pidstr, 1);
+
+ /* Load current softlevel */
+ bootlevel = getenv ("RC_BOOTLEVEL");
+ runlevel = rc_runlevel_get ();
+
+ rc_logger_open (newlevel ? newlevel : runlevel);
+
+ /* Setup a signal handler */
+ signal (SIGINT, handle_signal);
+ signal (SIGQUIT, handle_signal);
+ signal (SIGTERM, handle_signal);
+ signal (SIGUSR1, handle_signal);
+ signal (SIGWINCH, handle_signal);
+
+ if (! rc_yesno (getenv ("EINFO_QUIET")))
+ interactive = exists (INTERACTIVE);
+ rc_plugin_load ();
+
+ /* Check we're in the runlevel requested, ie from
+ rc single
+ rc shutdown
+ rc reboot
+ */
+ if (newlevel) {
+ if (strcmp (newlevel, RC_LEVEL_SYSINIT) == 0 &&
+ RUNLEVEL &&
+ (strcmp (RUNLEVEL, "S") == 0 ||
+ strcmp (RUNLEVEL, "1") == 0))
+ {
+ /* OK, we're either in runlevel 1 or single user mode */
+ struct utsname uts;
+#ifdef __linux__
+ char *cmd;
+#endif
+
+ /* 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_script (INITEARLYSH);
+
+ uname (&uts);
+ printf ("\n %sOpenRC %s" VERSION "%s is starting up %s",
+#ifdef BRANDING
+ BRANDING
+#else
+ ""
+#endif
+ "\n\n",
+ ecolor (ECOLOR_GOOD), ecolor (ECOLOR_HILITE),
+ 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_SOFTLEVEL", newlevel, 1);
+ rc_plugin_run (RC_HOOK_RUNLEVEL_START_IN, newlevel);
+ run_script (INITSH);
+
+#ifdef __linux__
+ /* If we requested a softlevel, save it now */
+ set_ksoftlevel (NULL);
+ if ((cmd = proc_getent ("softlevel"))) {
+ set_ksoftlevel (cmd);
+ free (cmd);
+ }
+#endif
+
+ rc_plugin_run (RC_HOOK_RUNLEVEL_START_OUT, newlevel);
+
+ if (want_interactive ())
+ mark_interactive ();
+
+ exit (EXIT_SUCCESS);
+ } else if (strcmp (newlevel, RC_LEVEL_SINGLE) == 0) {
+ if (! RUNLEVEL ||
+ (strcmp (RUNLEVEL, "S") != 0 &&
+ strcmp (RUNLEVEL, "1") != 0))
+ {
+ /* Remember the current runlevel for when we come back */
+ set_ksoftlevel (runlevel);
+ single_user ();
+ }
+ } else if (strcmp (newlevel, RC_LEVEL_REBOOT) == 0) {
+ if (! RUNLEVEL ||
+ strcmp (RUNLEVEL, "6") != 0)
+ {
+ rc_logger_close ();
+ execl (SHUTDOWN, SHUTDOWN, "-r", "now", (char *) NULL);
+ eerrorx ("%s: unable to exec `" SHUTDOWN "': %s",
+ applet, strerror (errno));
+ }
+ } else if (strcmp (newlevel, RC_LEVEL_SHUTDOWN) == 0) {
+ if (! RUNLEVEL ||
+ strcmp (RUNLEVEL, "0") != 0)
+ {
+ rc_logger_close ();
+ execl (SHUTDOWN, SHUTDOWN,
+#ifdef __linux__
+ "-h",
+#else
+ "-p",
+#endif
+ "now", (char *) NULL);
+ eerrorx ("%s: unable to exec `" SHUTDOWN "': %s",
+ applet, strerror (errno));
+ }
+ }
+ }
+
+ /* Now we start handling our children */
+ signal (SIGCHLD, handle_signal);
+
+ /* We should only use ksoftlevel if we were in single user mode
+ If not, we need to erase ksoftlevel now. */
+ if (PREVLEVEL &&
+ (strcmp (PREVLEVEL, "1") == 0 ||
+ strcmp (PREVLEVEL, "S") == 0 ||
+ strcmp (PREVLEVEL, "N") == 0))
+ {
+ /* Try not to join boot and ksoftlevels together */
+ if (! newlevel ||
+ strcmp (newlevel, getenv ("RC_BOOTLEVEL")) != 0)
+ if (get_ksoftlevel (ksoftbuffer, sizeof (ksoftbuffer)))
+ newlevel = ksoftbuffer;
+ } else if (! RUNLEVEL ||
+ (strcmp (RUNLEVEL, "1") != 0 &&
+ strcmp (RUNLEVEL, "S") != 0 &&
+ strcmp (RUNLEVEL, "N") != 0))
+ {
+ set_ksoftlevel (NULL);
+ }
+
+ if (newlevel &&
+ (strcmp (newlevel, RC_LEVEL_REBOOT) == 0 ||
+ strcmp (newlevel, RC_LEVEL_SHUTDOWN) == 0 ||
+ strcmp (newlevel, RC_LEVEL_SINGLE) == 0))
+ {
+ going_down = true;
+ rc_runlevel_set (newlevel);
+ setenv ("RC_SOFTLEVEL", newlevel, 1);
+
+#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);
+ }
+
+ /* 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 now */
+ if ((deptree = _rc_deptree_load (&regen)) == NULL)
+ eerrorx ("failed to load deptree");
+
+ /* Clean the failed services state dir now */
+ clean_failed ();
+
+ mkdir (RC_STOPPING, 0755);
+
+#ifdef __linux__
+ /* udev likes to start services before we're ready when it does
+ its coldplugging thing. runscript knows when we're not ready so it
+ stores a list of coldplugged services in DEVBOOT for us to pick up
+ here when we are ready for them */
+ if ((dp = opendir (DEVBOOT))) {
+ while ((d = readdir (dp))) {
+ if (d->d_name[0] == '.' &&
+ (d->d_name[1] == '\0' ||
+ (d->d_name[1] == '.' && d->d_name[2] == '\0')))
+ continue;
+
+ if (rc_service_exists (d->d_name) &&
+ rc_service_plugable (d->d_name))
+ rc_service_mark (d->d_name, RC_SERVICE_COLDPLUGGED);
+
+ i = strlen (DEVBOOT "/") + strlen (d->d_name) + 1;
+ tmp = xmalloc (sizeof (char) * i);
+ snprintf (tmp, i, DEVBOOT "/%s", d->d_name);
+ if (tmp) {
+ if (unlink (tmp))
+ eerror ("%s: unlink `%s': %s", applet, tmp,
+ strerror (errno));
+ free (tmp);
+ }
+ }
+ closedir (dp);
+ rmdir (DEVBOOT);
+ }
+#else
+ /* BSD's on the other hand populate /dev automagically and use devd.
+ The only downside of this approach and ours is that we have to hard code
+ the device node to the init script to simulate the coldplug into
+ runlevel for our dependency tree to work. */
+ if (newlevel && strcmp (newlevel, bootlevel) == 0 &&
+ (strcmp (runlevel, RC_LEVEL_SINGLE) == 0 ||
+ strcmp (runlevel, RC_LEVEL_SYSINIT) == 0) &&
+ rc_conf_yesno ("rc_coldplug"))
+ {
+#if defined(__DragonFly__) || defined(__FreeBSD__)
+ /* The net interfaces are easy - they're all in net /dev/net :) */
+ if ((dp = opendir ("/dev/net"))) {
+ while ((d = readdir (dp))) {
+ i = (strlen ("net.") + strlen (d->d_name) + 1);
+ tmp = xmalloc (sizeof (char) * i);
+ snprintf (tmp, i, "net.%s", d->d_name);
+ if (rc_service_exists (tmp) &&
+ rc_service_plugable (tmp))
+ rc_service_mark (tmp, RC_SERVICE_COLDPLUGGED);
+ CHAR_FREE (tmp);
+ }
+ closedir (dp);
+ }
+#endif
+
+ /* The mice are a little more tricky.
+ If we coldplug anything else, we'll probably do it here. */
+ if ((dp == opendir ("/dev"))) {
+ while ((d = readdir (dp))) {
+ if (strncmp (d->d_name, "psm", 3) == 0 ||
+ strncmp (d->d_name, "ums", 3) == 0)
+ {
+ char *p = d->d_name + 3;
+ if (p && isdigit (*p)) {
+ i = (strlen ("moused.") + strlen (d->d_name) + 1);
+ tmp = xmalloc (sizeof (char) * i);
+ snprintf (tmp, i, "moused.%s", d->d_name);
+ if (rc_service_exists (tmp) &&
+ rc_service_plugable (tmp))
+ rc_service_mark (tmp, RC_SERVICE_COLDPLUGGED);
+ CHAR_FREE (tmp);
+ }
+ }
+ }
+ closedir (dp);
+ }
+ }
+#endif
+
+ /* Build a list of all services to stop and then work out the
+ correct order for stopping them */
+ stop_services = rc_services_in_state (RC_SERVICE_STARTING);
+
+ tmplist = rc_services_in_state (RC_SERVICE_INACTIVE);
+ rc_strlist_join (&stop_services, tmplist);
+ rc_strlist_free (tmplist);
+
+ tmplist = rc_services_in_state (RC_SERVICE_STARTED);
+ rc_strlist_join (&stop_services, tmplist);
+ rc_strlist_free (tmplist);
+
+ deporder = rc_deptree_depends (deptree, types_nua,
+ (const char **) stop_services,
+ runlevel, depoptions | RC_DEP_STOP);
+
+ rc_strlist_free (stop_services);
+ stop_services = deporder;
+ deporder = NULL;
+ rc_strlist_reverse (stop_services);
+
+ /* Load our list of coldplugged services */
+ coldplugged_services = rc_services_in_state (RC_SERVICE_COLDPLUGGED);
+
+ /* Load our start services now.
+ We have different rules dependent on runlevel. */
+ if (newlevel && strcmp (newlevel, bootlevel) == 0) {
+ if (coldplugged_services) {
+ bool quiet = rc_yesno (getenv ("EINFO_QUIET"));
+
+ if (! quiet)
+ einfon ("Device initiated services:");
+ STRLIST_FOREACH (coldplugged_services, service, i) {
+ if (! quiet)
+ printf (" %s", service);
+ rc_strlist_add (&start_services, service);
+ }
+ if (! quiet)
+ printf ("\n");
+ }
+ tmplist = rc_services_in_runlevel (newlevel ? newlevel : runlevel);
+ rc_strlist_join (&start_services, tmplist);
+ rc_strlist_free (tmplist);
+ } else {
+ /* Store our list of coldplugged services */
+ tmplist = rc_services_in_state (RC_SERVICE_COLDPLUGGED);
+ rc_strlist_join (&coldplugged_services, tmplist);
+ rc_strlist_free (tmplist);
+ if (strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_SINGLE) != 0 &&
+ strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_SHUTDOWN) != 0 &&
+ strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_REBOOT) != 0)
+ {
+ /* We need to include the boot runlevel services if we're not in it */
+ tmplist = rc_services_in_runlevel (bootlevel);
+ rc_strlist_join (&start_services, tmplist);
+ rc_strlist_free (tmplist);
+ tmplist = rc_services_in_runlevel (newlevel ? newlevel : runlevel);
+ rc_strlist_join (&start_services, tmplist);
+ rc_strlist_free (tmplist);
+
+ STRLIST_FOREACH (coldplugged_services, service, i)
+ rc_strlist_add (&start_services, service);
+
+ }
+ }
+
+ /* Save out softlevel now */
+ if (going_down)
+ rc_runlevel_set (newlevel);
+
+ parallel = rc_conf_yesno ("rc_parallel");
+
+ /* Now stop the services that shouldn't be running */
+ STRLIST_FOREACH (stop_services, service, i) {
+ bool found = false;
+ char *conf = NULL;
+ char **stopdeps = NULL;
+ char *svc1 = NULL;
+ char *svc2 = NULL;
+ int k;
+
+ if (rc_service_state (service) & RC_SERVICE_STOPPED)
+ continue;
+
+ /* We always stop the service when in these runlevels */
+ if (going_down) {
+ pid_t pid = rc_service_stop (service);
+ if (pid > 0 && ! parallel)
+ rc_waitpid (pid);
+ continue;
+ }
+
+ /* If we're in the start list then don't bother stopping us */
+ STRLIST_FOREACH (start_services, svc1, j)
+ if (strcmp (svc1, service) == 0) {
+ found = true;
+ break;
+ }
+
+ /* Unless we would use a different config file */
+ if (found) {
+ int len;
+ if (! newlevel)
+ continue;
+
+ len = strlen (service) + strlen (runlevel) + 2;
+ tmp = xmalloc (sizeof (char) * len);
+ snprintf (tmp, len, "%s.%s", service, runlevel);
+ conf = rc_strcatpaths (RC_CONFDIR, tmp, (char *) NULL);
+ found = exists (conf);
+ CHAR_FREE (conf);
+ CHAR_FREE (tmp);
+ if (! found) {
+ len = strlen (service) + strlen (newlevel) + 2;
+ tmp = xmalloc (sizeof (char) * len);
+ snprintf (tmp, len, "%s.%s", service, newlevel);
+ conf = rc_strcatpaths (RC_CONFDIR, tmp, (char *) NULL);
+ found = exists (conf);
+ CHAR_FREE (conf);
+ CHAR_FREE (tmp);
+ if (!found)
+ continue;
+ }
+ } else {
+ /* Allow coldplugged services not to be in the runlevels list */
+ if (rc_service_state (service) & RC_SERVICE_COLDPLUGGED)
+ continue;
+ }
+
+ /* We got this far! Or last check is to see if any any service that
+ going to be started depends on us */
+ rc_strlist_add (&stopdeps, service);
+ deporder = rc_deptree_depends (deptree, types_n,
+ (const char **) stopdeps,
+ runlevel, RC_DEP_STRICT);
+ rc_strlist_free (stopdeps);
+ stopdeps = NULL;
+ found = false;
+ STRLIST_FOREACH (deporder, svc1, j) {
+ STRLIST_FOREACH (start_services, svc2, k)
+ if (strcmp (svc1, svc2) == 0) {
+ found = true;
+ break;
+ }
+ if (found)
+ break;
+ }
+ rc_strlist_free (deporder);
+ deporder = NULL;
+
+ /* After all that we can finally stop the blighter! */
+ if (! found) {
+ pid_t pid;
+
+ if ((pid = rc_service_stop (service)) > 0) {
+ add_pid (pid);
+
+ if (! parallel) {
+ rc_waitpid (pid);
+ remove_pid (pid);
+ }
+ }
+ }
+ }
+
+ /* Wait for our services to finish */
+ wait_for_services ();
+
+ /* Notify the plugins we have finished */
+ rc_plugin_run (RC_HOOK_RUNLEVEL_STOP_OUT, runlevel);
+
+ rmdir (RC_STOPPING);
+
+ /* Store the new runlevel */
+ if (newlevel) {
+ rc_runlevel_set (newlevel);
+ free (runlevel);
+ runlevel = xstrdup (newlevel);
+ setenv ("RC_SOFTLEVEL", runlevel, 1);
+ }
+
+ /* Run the halt script if needed */
+ if (strcmp (runlevel, RC_LEVEL_SHUTDOWN) == 0 ||
+ strcmp (runlevel, RC_LEVEL_REBOOT) == 0)
+ {
+ rc_logger_close ();
+ execl (HALTSH, HALTSH, runlevel, (char *) NULL);
+ eerrorx ("%s: unable to exec `%s': %s",
+ applet, HALTSH, strerror (errno));
+ }
+
+ /* Single user is done now */
+ if (strcmp (runlevel, RC_LEVEL_SINGLE) == 0) {
+ if (exists (INTERACTIVE))
+ unlink (INTERACTIVE);
+ sulogin (false);
+ }
+
+ mkdir (RC_STARTING, 0755);
+ rc_plugin_run (RC_HOOK_RUNLEVEL_START_IN, runlevel);
+
+ /* Re-add our coldplugged services if they stopped */
+ STRLIST_FOREACH (coldplugged_services, service, i)
+ rc_service_mark (service, RC_SERVICE_COLDPLUGGED);
+
+ /* Order the services to start */
+ deporder = rc_deptree_depends (deptree, types_nua,
+ (const char **) start_services,
+ runlevel, depoptions | RC_DEP_START);
+ rc_strlist_free (start_services);
+ start_services = deporder;
+ deporder = NULL;
+
+#ifdef __linux__
+ /* mark any services skipped as started */
+ if (PREVLEVEL && strcmp (PREVLEVEL, "N") == 0) {
+ if ((service = proc_getent ("noinitd"))) {
+ char *p = service;
+ char *token;
+
+ while ((token = strsep (&p, ",")))
+ rc_service_mark (token, RC_SERVICE_STARTED);
+ free (service);
+ }
+ }
+#endif
+
+ STRLIST_FOREACH (start_services, service, i) {
+ if (rc_service_state (service) & RC_SERVICE_STOPPED) {
+ pid_t pid;
+
+ if (! interactive)
+ interactive = want_interactive ();
+
+ if (interactive) {
+interactive_retry:
+ printf ("\n");
+ einfo ("About to start the service %s", service);
+ 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': sulogin (true); goto interactive_retry;
+ default: goto interactive_option;
+ }
+ }
+
+ /* Remember the pid if we're running in parallel */
+ if ((pid = rc_service_start (service)) > 0) {
+ add_pid (pid);
+
+ if (! parallel) {
+ rc_waitpid (pid);
+ remove_pid (pid);
+ }
+ }
+ }
+ }
+
+ /* Wait for our services to finish */
+ wait_for_services ();
+
+ rc_plugin_run (RC_HOOK_RUNLEVEL_START_OUT, runlevel);
+
+#ifdef __linux__
+ /* mark any services skipped as stopped */
+ if (PREVLEVEL && strcmp (PREVLEVEL, "N") == 0) {
+ if ((service = proc_getent ("noinitd"))) {
+ char *p = service;
+ char *token;
+
+ while ((token = strsep (&p, ",")))
+ rc_service_mark (token, RC_SERVICE_STOPPED);
+ free (service);
+ }
+ }
+#endif
+
+ /* Store our interactive status for boot */
+ if (interactive && strcmp (runlevel, bootlevel) == 0)
+ mark_interactive ();
+ else {
+ if (exists (INTERACTIVE))
+ unlink (INTERACTIVE);
+ }
+
+ /* 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);
+
+ return (EXIT_SUCCESS);
+}
+