diff options
Diffstat (limited to 'src/openrc')
-rw-r--r-- | src/openrc/meson.build | 17 | ||||
-rw-r--r-- | src/openrc/rc-logger.c | 314 | ||||
-rw-r--r-- | src/openrc/rc-logger.h | 23 | ||||
-rw-r--r-- | src/openrc/rc.c | 1118 |
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, ®en)) == 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; +} |