1197 lines
29 KiB
C
1197 lines
29 KiB
C
/*
|
|
* 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";
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/wait.h>
|
|
|
|
/* So we can coldplug net devices */
|
|
#ifdef BSD
|
|
# include <sys/socket.h>
|
|
# include <ifaddrs.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <dirent.h>
|
|
#include <ctype.h>
|
|
#include <getopt.h>
|
|
#include <libgen.h>
|
|
#include <limits.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 "builtins.h"
|
|
#include "einfo.h"
|
|
#include "rc.h"
|
|
#include "rc-logger.h"
|
|
#include "rc-misc.h"
|
|
#include "rc-plugin.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"
|
|
|
|
static char *RUNLEVEL = NULL;
|
|
static char *PREVLEVEL = NULL;
|
|
|
|
const char *applet = NULL;
|
|
static char *runlevel = NULL;
|
|
static RC_STRINGLIST *coldplugged_services = NULL;
|
|
static RC_STRINGLIST *stop_services = NULL;
|
|
static RC_STRINGLIST *start_services = NULL;
|
|
static RC_STRINGLIST *types_n = NULL;
|
|
static RC_STRINGLIST *types_nua = NULL;
|
|
static RC_DEPTREE *deptree = NULL;
|
|
static RC_HOOK hook_out = 0;
|
|
|
|
struct termios *termios_orig = NULL;
|
|
|
|
typedef struct piditem
|
|
{
|
|
pid_t pid;
|
|
LIST_ENTRY(piditem) entries;
|
|
} PIDITEM;
|
|
LIST_HEAD(, piditem) service_pids;
|
|
|
|
static void clean_failed(void)
|
|
{
|
|
DIR *dp;
|
|
struct dirent *d;
|
|
size_t l;
|
|
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;
|
|
|
|
l = strlen(RC_SVCDIR "/failed/") + strlen(d->d_name) + 1;
|
|
path = xmalloc(sizeof(char) * l);
|
|
snprintf(path, l, 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) {
|
|
PIDITEM *p1 = LIST_FIRST(&service_pids);
|
|
PIDITEM *p2;
|
|
|
|
if (hook_out)
|
|
rc_plugin_run(hook_out, runlevel);
|
|
|
|
rc_plugin_unload();
|
|
|
|
if (! rc_in_plugin && termios_orig) {
|
|
tcsetattr(fileno(stdin), TCSANOW, termios_orig);
|
|
free(termios_orig);
|
|
}
|
|
|
|
while (p1) {
|
|
p2 = LIST_NEXT(p1, entries);
|
|
free(p1);
|
|
p1 = p2;
|
|
}
|
|
|
|
rc_stringlist_free(coldplugged_services);
|
|
rc_stringlist_free(stop_services);
|
|
rc_stringlist_free(start_services);
|
|
rc_stringlist_free(types_n);
|
|
rc_stringlist_free(types_nua);
|
|
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);
|
|
}
|
|
}
|
|
|
|
#ifdef __linux__
|
|
static char *proc_getent(const char *ent)
|
|
{
|
|
FILE *fp;
|
|
char *proc;
|
|
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;
|
|
}
|
|
|
|
if ((proc = rc_getline(fp)) &&
|
|
(p = strstr(proc, ent)))
|
|
{
|
|
i = p - proc;
|
|
if (i == '\0' || proc[i - 1] == ' ') {
|
|
p += strlen(ent);
|
|
if (*p == '=')
|
|
p++;
|
|
value = xstrdup(strsep(&p, " "));
|
|
}
|
|
} else
|
|
errno = ENOENT;
|
|
free(proc);
|
|
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(*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);
|
|
|
|
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)
|
|
{
|
|
int status = 0;
|
|
struct sigaction sa;
|
|
sigset_t full;
|
|
sigset_t old;
|
|
pid_t pid;
|
|
#ifdef __linux__
|
|
const char *sys = rc_sys();
|
|
|
|
/* VSERVER and OPENVZ systems cannot do a sulogin */
|
|
if (sys && (strcmp(sys, "VSERVER") == 0 || strcmp(sys, "OPENVZ") == 0)) {
|
|
execl("/sbin/halt", "/sbin/halt", "-f", (char *) NULL);
|
|
eerrorx("%s: unable to exec `/sbin/halt': %s",
|
|
applet, strerror(errno));
|
|
}
|
|
#endif
|
|
|
|
if (! cont) {
|
|
rc_logger_close();
|
|
#ifdef __linux__
|
|
execl("/sbin/sulogin", "/sbin/sulogin", (char *) NULL);
|
|
eerrorx("%s: unable to exec `/sbin/sulogin': %s",
|
|
applet, strerror(errno));
|
|
#else
|
|
exit(EXIT_SUCCESS);
|
|
#endif
|
|
}
|
|
|
|
/* 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 = vfork();
|
|
|
|
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(fileno(stdin), TCSANOW, termios_orig);
|
|
|
|
#ifdef __linux__
|
|
execl(SULOGIN, SULOGIN, (char *) NULL);
|
|
eerror("%s: unable to exec `%s': %s", applet, SULOGIN,
|
|
strerror(errno));
|
|
#else
|
|
execl("/bin/sh", "/bin/sh", (char *) NULL);
|
|
eerror("%s: unable to exec `/bin/sh': %s", applet,
|
|
strerror(errno));
|
|
#endif
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Unmask signals and wait for child */
|
|
sigprocmask(SIG_SETMASK, &old, NULL);
|
|
waitpid(pid, &status, 0);
|
|
}
|
|
|
|
static void single_user(void)
|
|
{
|
|
rc_logger_close();
|
|
|
|
execl(SHUTDOWN, SHUTDOWN, "now", (char *) NULL);
|
|
eerrorx("%s: unable to exec `" SHUTDOWN "': %s",
|
|
applet, strerror(errno));
|
|
}
|
|
|
|
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)
|
|
{
|
|
PIDITEM *p = xmalloc(sizeof(*p));
|
|
p->pid = pid;
|
|
LIST_INSERT_HEAD(&service_pids, p, entries);
|
|
}
|
|
|
|
static void remove_pid(pid_t pid)
|
|
{
|
|
PIDITEM *p;
|
|
|
|
LIST_FOREACH(p, &service_pids, entries)
|
|
if (p->pid == pid) {
|
|
LIST_REMOVE(p, entries);
|
|
free(p);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void wait_for_services(void)
|
|
{
|
|
while (waitpid(0, 0, 0) != -1);
|
|
}
|
|
|
|
static void handle_signal(int sig)
|
|
{
|
|
int serrno = errno;
|
|
char signame[10] = { '\0' };
|
|
pid_t pid;
|
|
PIDITEM *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[0])
|
|
snprintf(signame, sizeof(signame), "SIGINT");
|
|
/* FALLTHROUGH */
|
|
case SIGTERM:
|
|
if (! signame[0])
|
|
snprintf(signame, sizeof(signame), "SIGTERM");
|
|
/* FALLTHROUGH */
|
|
case SIGQUIT:
|
|
if (! signame[0])
|
|
snprintf(signame, sizeof(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);
|
|
|
|
/* 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);
|
|
/* NOTREACHED */
|
|
|
|
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();
|
|
pid_t wpid;
|
|
|
|
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 {
|
|
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);
|
|
}
|
|
|
|
static void do_coldplug(void)
|
|
{
|
|
size_t l;
|
|
DIR *dp;
|
|
struct dirent *d;
|
|
char *service;
|
|
RC_STRING *s;
|
|
#ifdef BSD
|
|
struct ifaddrs *ifap;
|
|
struct ifaddrs *ifa;
|
|
char *p;
|
|
#endif
|
|
|
|
if (! rc_conf_yesno("rc_coldplug") && errno != ENOENT)
|
|
return;
|
|
|
|
/* We need to ensure our state dirs exist.
|
|
* We should have a better call than this, but oh well. */
|
|
rc_deptree_update_needed();
|
|
|
|
#ifdef BSD
|
|
if (getifaddrs(&ifap) == 0) {
|
|
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
|
|
if (ifa->ifa_addr->sa_family != AF_LINK)
|
|
continue;
|
|
|
|
l = strlen("net.") + strlen(ifa->ifa_name) + 1;
|
|
service = xmalloc(sizeof (char) * l);
|
|
snprintf(service, l, "net.%s", ifa->ifa_name);
|
|
if (rc_service_exists(service) &&
|
|
service_plugable(service))
|
|
rc_service_mark(service, RC_SERVICE_COLDPLUGGED);
|
|
free(service);
|
|
}
|
|
freeifaddrs (ifap);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
p = d->d_name + 3;
|
|
if (p && isdigit((int) *p)) {
|
|
l = strlen("moused.") + strlen(d->d_name) + 1;
|
|
service = xmalloc(sizeof(char) * l);
|
|
snprintf (service, l, "moused.%s", d->d_name);
|
|
if (rc_service_exists (service) &&
|
|
service_plugable (service))
|
|
rc_service_mark (service, RC_SERVICE_COLDPLUGGED);
|
|
free(service);
|
|
}
|
|
}
|
|
}
|
|
closedir (dp);
|
|
}
|
|
|
|
#else
|
|
|
|
/* 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) &&
|
|
service_plugable(d->d_name))
|
|
rc_service_mark(d->d_name, RC_SERVICE_COLDPLUGGED);
|
|
|
|
l = strlen(DEVBOOT "/") + strlen(d->d_name) + 1;
|
|
service = xmalloc(sizeof (char) * l);
|
|
snprintf(service, l, DEVBOOT "/%s", d->d_name);
|
|
if (unlink(service))
|
|
eerror("%s: unlink `%s': %s", applet, service,
|
|
strerror(errno));
|
|
free(service);
|
|
}
|
|
closedir(dp);
|
|
rmdir(DEVBOOT);
|
|
}
|
|
#endif
|
|
|
|
if (rc_yesno(getenv("EINFO_QUIET")))
|
|
return;
|
|
|
|
/* Load our list of coldplugged services and display them */
|
|
einfon("Device initiated services:%s", ecolor(ECOLOR_HILITE));
|
|
coldplugged_services = rc_services_in_state(RC_SERVICE_COLDPLUGGED);
|
|
TAILQ_FOREACH(s, coldplugged_services, entries)
|
|
printf(" %s", s->value);
|
|
printf ("%s\n", ecolor(ECOLOR_NORMAL));
|
|
}
|
|
|
|
static bool runlevel_config(const char *service, const char *level)
|
|
{
|
|
char *init = rc_service_resolve(service);
|
|
char *conf;
|
|
size_t l;
|
|
bool retval;
|
|
|
|
init = dirname(init);
|
|
init = dirname(init);
|
|
l = strlen(init) + strlen(level) + strlen(service) + 10;
|
|
conf = xmalloc(sizeof(char) * l);
|
|
snprintf(conf, l, "%s/conf.d/%s.%s", init, service, level);
|
|
retval = exists(conf);
|
|
free(conf);
|
|
|
|
return retval;
|
|
}
|
|
|
|
#include "_usage.h"
|
|
#define getoptstring "o:" getoptstring_COMMON
|
|
static const 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;
|
|
const char *sys = rc_sys();
|
|
char *newlevel = NULL;
|
|
RC_STRINGLIST *deporder = NULL;
|
|
RC_STRINGLIST *tmplist;
|
|
RC_STRING *service;
|
|
bool going_down = false;
|
|
bool interactive = false;
|
|
int depoptions = RC_DEP_STRICT | RC_DEP_TRACE;
|
|
char ksoftbuffer [PATH_MAX];
|
|
char pidstr[6];
|
|
int opt;
|
|
bool parallel;
|
|
int regen = 0;
|
|
pid_t pid;
|
|
RC_STRING *svc1;
|
|
RC_STRING *svc2 = NULL;
|
|
struct utsname uts;
|
|
#ifdef __linux__
|
|
char *cmd;
|
|
char *proc;
|
|
char *p;
|
|
char *token;
|
|
#endif
|
|
|
|
applet = basename_c(argv[0]);
|
|
LIST_INIT(&service_pids);
|
|
atexit(cleanup);
|
|
if (! applet)
|
|
eerrorx("arguments required");
|
|
|
|
if (argc > 1 && (strcmp(argv[1], "--version") == 0)) {
|
|
printf("%s (OpenRC", applet);
|
|
if (sys)
|
|
printf(" [%s]", sys);
|
|
printf(") " VERSION
|
|
#ifdef BRANDING
|
|
" (" BRANDING ")"
|
|
#endif
|
|
"\n");
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
/* Run our built in applets. If we ran one, we don't return. */
|
|
run_applets(argc, argv);
|
|
|
|
argc--;
|
|
argv++;
|
|
|
|
/* 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_filter();
|
|
env_config();
|
|
|
|
argc++;
|
|
argv--;
|
|
while ((opt = getopt_long(argc, argv, getoptstring,
|
|
longopts, (int *) 0)) != -1)
|
|
{
|
|
switch (opt) {
|
|
case 'o':
|
|
if (*optarg == '\0')
|
|
optarg = NULL;
|
|
exit(set_ksoftlevel(optarg) ? EXIT_SUCCESS : EXIT_FAILURE);
|
|
/* NOTREACHED */
|
|
case_RC_COMMON_GETOPT
|
|
}
|
|
}
|
|
|
|
newlevel = argv[optind++];
|
|
|
|
/* 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_setup(SIGINT, handle_signal);
|
|
signal_setup(SIGQUIT, handle_signal);
|
|
signal_setup(SIGTERM, handle_signal);
|
|
signal_setup(SIGUSR1, handle_signal);
|
|
signal_setup(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
|
|
#ifndef PREFIX
|
|
&& RUNLEVEL &&
|
|
(strcmp(RUNLEVEL, "S") == 0 ||
|
|
strcmp(RUNLEVEL, "1") == 0)
|
|
#endif
|
|
)
|
|
{
|
|
/* OK, we're either in runlevel 1 or single user mode */
|
|
|
|
/* 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",
|
|
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)
|
|
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_SOFTLEVEL", newlevel, 1);
|
|
rc_plugin_run(RC_HOOK_RUNLEVEL_START_IN, newlevel);
|
|
hook_out = RC_HOOK_RUNLEVEL_START_OUT;
|
|
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
|
|
|
|
/* Setup our coldplugged services now */
|
|
do_coldplug();
|
|
|
|
rc_plugin_run(RC_HOOK_RUNLEVEL_START_OUT, newlevel);
|
|
hook_out = 0;
|
|
|
|
if (want_interactive())
|
|
mark_interactive();
|
|
|
|
exit(EXIT_SUCCESS);
|
|
} else if (strcmp(newlevel, RC_LEVEL_SINGLE) == 0) {
|
|
#ifndef PREFIX
|
|
if (! RUNLEVEL ||
|
|
(strcmp(RUNLEVEL, "S") != 0 &&
|
|
strcmp(RUNLEVEL, "1") != 0))
|
|
{
|
|
/* Remember the current runlevel for when we come back */
|
|
set_ksoftlevel(runlevel);
|
|
single_user();
|
|
}
|
|
#endif
|
|
} 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_setup(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_RUNLEVEL", 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);
|
|
}
|
|
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 ((deptree = _rc_deptree_load(®en)) == NULL)
|
|
eerrorx("failed to load deptree");
|
|
|
|
/* 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));
|
|
}
|
|
|
|
/* 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_STARTED);
|
|
tmplist = rc_services_in_state(RC_SERVICE_INACTIVE);
|
|
TAILQ_CONCAT(stop_services, tmplist);
|
|
free(tmplist);
|
|
tmplist = rc_services_in_state(RC_SERVICE_STARTING);
|
|
TAILQ_CONCAT(stop_services, tmplist);
|
|
free(tmplist);
|
|
rc_stringlist_sort(&stop_services);
|
|
|
|
types_n = rc_stringlist_new();
|
|
rc_stringlist_add(types_n, "needsme");
|
|
|
|
types_nua = rc_stringlist_new();
|
|
rc_stringlist_add(types_nua, "ineed");
|
|
rc_stringlist_add(types_nua, "iuse");
|
|
rc_stringlist_add(types_nua, "iafter");
|
|
|
|
tmplist = rc_deptree_depends(deptree, types_nua, stop_services,
|
|
runlevel, depoptions | RC_DEP_STOP);
|
|
rc_stringlist_free(stop_services);
|
|
stop_services = tmplist;
|
|
|
|
/* Load our list of coldplugged services */
|
|
coldplugged_services = rc_services_in_state(RC_SERVICE_COLDPLUGGED);
|
|
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 */
|
|
start_services = rc_services_in_runlevel(bootlevel);
|
|
if (strcmp (newlevel ? newlevel : runlevel, bootlevel) != 0) {
|
|
tmplist = rc_services_in_runlevel(newlevel ? newlevel : runlevel);
|
|
TAILQ_CONCAT(start_services, tmplist);
|
|
free(tmplist);
|
|
}
|
|
|
|
TAILQ_FOREACH(service, coldplugged_services, entries)
|
|
rc_stringlist_addu(start_services, service->value);
|
|
}
|
|
|
|
/* Save our softlevel now */
|
|
if (going_down)
|
|
rc_runlevel_set(newlevel);
|
|
|
|
parallel = rc_conf_yesno("rc_parallel");
|
|
|
|
/* Now stop the services that shouldn't be running */
|
|
TAILQ_FOREACH_REVERSE(service, stop_services, rc_stringlist, entries) {
|
|
if (rc_service_state(service->value) & RC_SERVICE_STOPPED)
|
|
continue;
|
|
|
|
/* We always stop the service when in these runlevels */
|
|
if (going_down) {
|
|
pid = rc_service_stop(service->value);
|
|
if (pid > 0 && ! parallel)
|
|
rc_waitpid(pid);
|
|
continue;
|
|
}
|
|
|
|
/* If we're in the start list then don't bother stopping us */
|
|
TAILQ_FOREACH(svc1, start_services, entries)
|
|
if (strcmp (svc1->value, service->value) == 0)
|
|
break;
|
|
|
|
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! Or 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_n, tmplist,
|
|
runlevel, RC_DEP_STRICT);
|
|
rc_stringlist_free(tmplist);
|
|
svc2 = NULL;
|
|
TAILQ_FOREACH (svc1, deporder, entries) {
|
|
TAILQ_FOREACH(svc2, start_services, entries)
|
|
if (strcmp (svc1->value, svc2->value) == 0)
|
|
break;
|
|
if (svc2)
|
|
break;
|
|
}
|
|
rc_stringlist_free(deporder);
|
|
|
|
if (svc2)
|
|
continue;
|
|
}
|
|
|
|
/* After all that we can finally stop the blighter! */
|
|
pid = rc_service_stop(service->value);
|
|
if (pid > 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);
|
|
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);
|
|
}
|
|
|
|
/* 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);
|
|
hook_out = RC_HOOK_RUNLEVEL_START_OUT;
|
|
|
|
/* Re-add our coldplugged services if they stopped */
|
|
TAILQ_FOREACH(service, coldplugged_services, entries)
|
|
rc_service_mark(service->value, RC_SERVICE_COLDPLUGGED);
|
|
|
|
/* Order the services to start */
|
|
rc_stringlist_sort(&start_services);
|
|
deporder = rc_deptree_depends(deptree, types_nua, start_services,
|
|
runlevel, depoptions | RC_DEP_START);
|
|
rc_stringlist_free(start_services);
|
|
start_services = deporder;
|
|
|
|
#ifdef __linux__
|
|
/* mark any services skipped as started */
|
|
if (PREVLEVEL && strcmp(PREVLEVEL, "N") == 0) {
|
|
proc = p = proc_getent("noinitd");
|
|
if (proc) {
|
|
while ((token = strsep(&p, ",")))
|
|
rc_service_mark(token, RC_SERVICE_STARTED);
|
|
free(proc);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TAILQ_FOREACH(service, start_services, entries) {
|
|
if (rc_service_state(service->value) & RC_SERVICE_STOPPED) {
|
|
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': sulogin(true); goto interactive_retry;
|
|
default: goto interactive_option;
|
|
}
|
|
}
|
|
|
|
pid = rc_service_start(service->value);
|
|
|
|
/* Remember the pid if we're running in parallel */
|
|
if (pid > 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);
|
|
hook_out = 0;
|
|
|
|
#ifdef __linux__
|
|
/* mark any services skipped as stopped */
|
|
if (PREVLEVEL && strcmp(PREVLEVEL, "N") == 0) {
|
|
proc = p = proc_getent("noinitd");
|
|
if (proc) {
|
|
while ((token = strsep(&p, ",")))
|
|
rc_service_mark(token, RC_SERVICE_STOPPED);
|
|
free(proc);
|
|
}
|
|
}
|
|
#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_CACHE);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|