diff options
Diffstat (limited to 'src/rc/rc.c')
-rw-r--r-- | src/rc/rc.c | 1574 |
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 (®en)) == 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); +} + |