openrc/src/rc/runscript.c
Christian Ruppert 30a56cdb46 Add "ifstopped" command to runscript
Signed-off-by: Christian Ruppert <idl0r@gentoo.org>
2012-01-15 02:10:35 +01:00

1409 lines
35 KiB
C

/*
* runscript.c
* Handle launching of init scripts.
*/
/*
* Copyright (c) 2007-2009 Roy Marples <roy@marples.name>
*
* 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.
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <ctype.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <getopt.h>
#include <libgen.h>
#include <limits.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>
#ifdef __linux__
# include <pty.h>
#elif defined(__NetBSD__) || defined(__OpenBSD__)
# include <util.h>
#else
# include <libutil.h>
#endif
#include "builtins.h"
#include "einfo.h"
#include "rc.h"
#include "rc-misc.h"
#include "rc-plugin.h"
#define SELINUX_LIB RC_LIBDIR "/runscript_selinux.so"
#define PREFIX_LOCK RC_SVCDIR "/prefix.lock"
#define WAIT_INTERVAL 20000000 /* usecs to poll the lock file */
#define WAIT_TIMEOUT 60 /* seconds until we timeout */
#define WARN_TIMEOUT 10 /* warn about this every N seconds */
static const char *applet;
static char *service, *runlevel, *ibsave, *prefix;;
static RC_DEPTREE *deptree;
static RC_STRINGLIST *applet_list, *services, *tmplist;
static RC_STRINGLIST *restart_services, *need_services, *use_services;
static RC_HOOK hook_out;
static int exclusive_fd = -1, master_tty = -1;
static bool sighup, in_background, deps, dry_run;
static pid_t service_pid;
static int signal_pipe[2] = { -1, -1 };
static RC_STRINGLIST *types_b, *types_n, *types_nu, *types_nua, *types_m;
static RC_STRINGLIST *types_mua = NULL;
#ifdef __linux__
static void (*selinux_run_init_old)(void);
static void (*selinux_run_init_new)(int argc, char **argv);
static void
setup_selinux(int argc, char **argv)
{
void *lib_handle = NULL;
if (! exists(SELINUX_LIB))
return;
lib_handle = dlopen(SELINUX_LIB, RTLD_NOW | RTLD_GLOBAL);
if (! lib_handle) {
eerror("dlopen: %s", dlerror());
return;
}
selinux_run_init_old = (void (*)(void))
dlfunc(lib_handle, "selinux_runscript");
selinux_run_init_new = (void (*)(int, char **))
dlfunc(lib_handle, "selinux_runscript2");
/* Use new run_init if it exists, else fall back to old */
if (selinux_run_init_new)
selinux_run_init_new(argc, argv);
else if (selinux_run_init_old)
selinux_run_init_old();
else
/* This shouldnt happen... probably corrupt lib */
eerrorx("run_init is missing from runscript_selinux.so!");
dlclose(lib_handle);
}
#endif
static void
handle_signal(int sig)
{
int serrno = errno;
char signame[10] = { '\0' };
struct winsize ws;
switch (sig) {
case SIGHUP:
sighup = true;
break;
case SIGCHLD:
if (signal_pipe[1] > -1) {
if (write(signal_pipe[1], &sig, sizeof(sig)) == -1)
eerror("%s: send: %s",
service, strerror(errno));
} else
rc_waitpid(-1);
break;
case SIGWINCH:
if (master_tty >= 0) {
ioctl(fileno(stdout), TIOCGWINSZ, &ws);
ioctl(master_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");
/* Send the signal to our children too */
if (service_pid > 0)
kill(service_pid, sig);
eerrorx("%s: caught %s, aborting", applet, signame);
/* NOTREACHED */
default:
eerror("%s: caught unknown signal %d", applet, sig);
}
/* Restore errno */
errno = serrno;
}
static void
unhotplug()
{
char file[PATH_MAX];
snprintf(file, sizeof(file), RC_SVCDIR "/hotplugged/%s", applet);
if (exists(file) && unlink(file) != 0)
eerror("%s: unlink `%s': %s", applet, file, strerror(errno));
}
static void
start_services(RC_STRINGLIST *list)
{
RC_STRING *svc;
RC_SERVICE state = rc_service_state (service);
if (!list)
return;
if (state & RC_SERVICE_INACTIVE ||
state & RC_SERVICE_WASINACTIVE ||
state & RC_SERVICE_STARTING ||
state & RC_SERVICE_STARTED)
{
TAILQ_FOREACH(svc, list, entries) {
if (!(rc_service_state(svc->value) &
RC_SERVICE_STOPPED))
continue;
if (state & RC_SERVICE_INACTIVE ||
state & RC_SERVICE_WASINACTIVE)
{
rc_service_schedule_start(service,
svc->value);
ewarn("WARNING: %s is scheduled to started"
" when %s has started",
svc->value, applet);
} else
service_start(svc->value);
}
}
}
static void
restore_state(void)
{
RC_SERVICE state;
if (rc_in_plugin || exclusive_fd == -1)
return;
state = rc_service_state(applet);
if (state & RC_SERVICE_STOPPING) {
if (state & RC_SERVICE_WASINACTIVE)
rc_service_mark(applet, RC_SERVICE_INACTIVE);
else
rc_service_mark(applet, RC_SERVICE_STARTED);
if (rc_runlevel_stopping())
rc_service_mark(applet, RC_SERVICE_FAILED);
} else if (state & RC_SERVICE_STARTING) {
if (state & RC_SERVICE_WASINACTIVE)
rc_service_mark(applet, RC_SERVICE_INACTIVE);
else
rc_service_mark(applet, RC_SERVICE_STOPPED);
if (rc_runlevel_starting())
rc_service_mark(applet, RC_SERVICE_FAILED);
}
exclusive_fd = svc_unlock(applet, exclusive_fd);
}
static void
cleanup(void)
{
restore_state();
if (!rc_in_plugin) {
if (hook_out) {
rc_plugin_run(hook_out, applet);
if (hook_out == RC_HOOK_SERVICE_START_DONE)
rc_plugin_run(RC_HOOK_SERVICE_START_OUT,
applet);
else if (hook_out == RC_HOOK_SERVICE_STOP_DONE)
rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT,
applet);
}
if (restart_services)
start_services(restart_services);
}
rc_plugin_unload();
#ifdef DEBUG_MEMORY
rc_stringlist_free(types_b);
rc_stringlist_free(types_n);
rc_stringlist_free(types_nu);
rc_stringlist_free(types_nua);
rc_stringlist_free(types_m);
rc_stringlist_free(types_mua);
rc_deptree_free(deptree);
rc_stringlist_free(restart_services);
rc_stringlist_free(need_services);
rc_stringlist_free(use_services);
rc_stringlist_free(services);
rc_stringlist_free(applet_list);
rc_stringlist_free(tmplist);
free(ibsave);
free(service);
free(prefix);
free(runlevel);
#endif
}
static int
write_prefix(const char *buffer, size_t bytes, bool *prefixed)
{
size_t i, j;
const char *ec = ecolor(ECOLOR_HILITE);
const char *ec_normal = ecolor(ECOLOR_NORMAL);
ssize_t ret = 0;
int fd = fileno(stdout), lock_fd = -1;
/* Spin until we lock the prefix */
for (;;) {
lock_fd = open(PREFIX_LOCK, O_WRONLY | O_CREAT, 0664);
if (lock_fd != -1)
if (flock(lock_fd, LOCK_EX) == 0)
break;
close(lock_fd);
}
for (i = 0; i < bytes; i++) {
/* We don't prefix eend calls (cursor up) */
if (buffer[i] == '\033' && !*prefixed) {
for (j = i + 1; j < bytes; j++) {
if (buffer[j] == 'A')
*prefixed = true;
if (isalpha((unsigned int)buffer[j]))
break;
}
}
if (!*prefixed) {
ret += write(fd, ec, strlen(ec));
ret += write(fd, prefix, strlen(prefix));
ret += write(fd, ec_normal, strlen(ec_normal));
ret += write(fd, "|", 1);
*prefixed = true;
}
if (buffer[i] == '\n')
*prefixed = false;
ret += write(fd, buffer + i, 1);
}
/* Release the lock */
close(lock_fd);
return ret;
}
static int
svc_exec(const char *arg1, const char *arg2)
{
int ret, fdout = fileno(stdout);
struct termios tt;
struct winsize ws;
int i;
int flags = 0;
struct pollfd fd[2];
int s;
char *buffer;
size_t bytes;
bool prefixed = false;
int slave_tty;
sigset_t sigchldmask;
sigset_t oldmask;
/* Setup our signal pipe */
if (pipe(signal_pipe) == -1)
eerrorx("%s: pipe: %s", service, applet);
for (i = 0; i < 2; i++)
if ((flags = fcntl(signal_pipe[i], F_GETFD, 0) == -1 ||
fcntl(signal_pipe[i], F_SETFD, flags | FD_CLOEXEC) == -1))
eerrorx("%s: fcntl: %s", service, strerror(errno));
/* Open a pty for our prefixed output
* We do this instead of mapping pipes to stdout, stderr so that
* programs can tell if they're attached to a tty or not.
* The only loss is that we can no longer tell the difference
* between the childs stdout or stderr */
master_tty = slave_tty = -1;
if (prefix && isatty(fdout)) {
tcgetattr(fdout, &tt);
ioctl(fdout, TIOCGWINSZ, &ws);
/* If the below call fails due to not enough ptys then we don't
* prefix the output, but we still work */
openpty(&master_tty, &slave_tty, NULL, &tt, &ws);
if (master_tty >= 0 &&
(flags = fcntl(master_tty, F_GETFD, 0)) == 0)
fcntl(master_tty, F_SETFD, flags | FD_CLOEXEC);
if (slave_tty >=0 &&
(flags = fcntl(slave_tty, F_GETFD, 0)) == 0)
fcntl(slave_tty, F_SETFD, flags | FD_CLOEXEC);
}
service_pid = fork();
if (service_pid == -1)
eerrorx("%s: fork: %s", service, strerror(errno));
if (service_pid == 0) {
if (slave_tty >= 0) {
dup2(slave_tty, STDOUT_FILENO);
dup2(slave_tty, STDERR_FILENO);
}
if (exists(RC_SVCDIR "/runscript.sh")) {
execl(RC_SVCDIR "/runscript.sh",
RC_SVCDIR "/runscript.sh",
service, arg1, arg2, (char *) NULL);
eerror("%s: exec `" RC_SVCDIR "/runscript.sh': %s",
service, strerror(errno));
_exit(EXIT_FAILURE);
} else {
execl(RC_LIBEXECDIR "/sh/runscript.sh",
RC_LIBEXECDIR "/sh/runscript.sh",
service, arg1, arg2, (char *) NULL);
eerror("%s: exec `" RC_LIBEXECDIR "/sh/runscript.sh': %s",
service, strerror(errno));
_exit(EXIT_FAILURE);
}
}
buffer = xmalloc(sizeof(char) * BUFSIZ);
fd[0].fd = signal_pipe[0];
fd[0].events = fd[1].events = POLLIN;
fd[0].revents = fd[1].revents = 0;
if (master_tty >= 0) {
fd[1].fd = master_tty;
fd[1].events = POLLIN;
fd[1].revents = 0;
}
for (;;) {
if ((s = poll(fd, master_tty >= 0 ? 2 : 1, -1)) == -1) {
if (errno != EINTR) {
eerror("%s: poll: %s",
service, strerror(errno));
break;
}
}
if (s > 0) {
if (fd[1].revents & (POLLIN | POLLHUP)) {
bytes = read(master_tty, buffer, BUFSIZ);
write_prefix(buffer, bytes, &prefixed);
}
/* Only SIGCHLD signals come down this pipe */
if (fd[0].revents & (POLLIN | POLLHUP))
break;
}
}
free(buffer);
sigemptyset (&sigchldmask);
sigaddset (&sigchldmask, SIGCHLD);
sigprocmask (SIG_BLOCK, &sigchldmask, &oldmask);
close(signal_pipe[0]);
close(signal_pipe[1]);
signal_pipe[0] = signal_pipe[1] = -1;
sigprocmask (SIG_SETMASK, &oldmask, NULL);
if (master_tty >= 0) {
/* Why did we do this? */
/* signal (SIGWINCH, SIG_IGN); */
close(master_tty);
master_tty = -1;
}
ret = rc_waitpid(service_pid);
ret = WEXITSTATUS(ret);
if (ret != 0 && errno == ECHILD)
/* killall5 -9 could cause this */
ret = 0;
service_pid = 0;
return ret;
}
static bool
svc_wait(const char *svc)
{
char file[PATH_MAX];
int fd;
bool forever = false;
RC_STRINGLIST *keywords;
struct timespec interval, timeout, warn;
/* Some services don't have a timeout, like fsck */
keywords = rc_deptree_depend(deptree, svc, "keyword");
if (rc_stringlist_find(keywords, "-timeout") ||
rc_stringlist_find(keywords, "notimeout"))
forever = true;
rc_stringlist_free(keywords);
snprintf(file, sizeof(file), RC_SVCDIR "/exclusive/%s",
basename_c(svc));
interval.tv_sec = 0;
interval.tv_nsec = WAIT_INTERVAL;
timeout.tv_sec = WAIT_TIMEOUT;
timeout.tv_nsec = 0;
warn.tv_sec = WARN_TIMEOUT;
warn.tv_nsec = 0;
for (;;) {
fd = open(file, O_RDONLY | O_NONBLOCK);
if (fd != -1) {
if (flock(fd, LOCK_SH | LOCK_NB) == 0) {
close(fd);
return true;
}
close(fd);
}
if (errno == ENOENT)
return true;
if (errno != EWOULDBLOCK)
eerrorx("%s: open `%s': %s", applet, file,
strerror(errno));
if (nanosleep(&interval, NULL) == -1) {
if (errno != EINTR)
return false;
}
if (!forever) {
timespecsub(&timeout, &interval, &timeout);
if (timeout.tv_sec <= 0)
return false;
timespecsub(&warn, &interval, &warn);
if (warn.tv_sec <= 0) {
ewarn("%s: waiting for %s (%d seconds)",
applet, svc, (int)timeout.tv_sec);
warn.tv_sec = WARN_TIMEOUT;
warn.tv_nsec = 0;
}
}
}
return false;
}
static void
get_started_services(void)
{
RC_STRINGLIST *tmp = rc_services_in_state(RC_SERVICE_INACTIVE);
rc_stringlist_free(restart_services);
restart_services = rc_services_in_state(RC_SERVICE_STARTED);
TAILQ_CONCAT(restart_services, tmp, entries);
free(tmp);
}
static void
setup_types(void)
{
types_b = rc_stringlist_new();
rc_stringlist_add(types_b, "broken");
types_n = rc_stringlist_new();
rc_stringlist_add(types_n, "ineed");
types_nu = rc_stringlist_new();
rc_stringlist_add(types_nu, "ineed");
rc_stringlist_add(types_nu, "iuse");
types_nua = rc_stringlist_new();
rc_stringlist_add(types_nua, "ineed");
rc_stringlist_add(types_nua, "iuse");
rc_stringlist_add(types_nua, "iafter");
types_m = rc_stringlist_new();
rc_stringlist_add(types_m, "needsme");
types_mua = rc_stringlist_new();
rc_stringlist_add(types_mua, "needsme");
rc_stringlist_add(types_mua, "usesme");
rc_stringlist_add(types_mua, "beforeme");
}
static void
svc_start_check(void)
{
RC_SERVICE state;
state = rc_service_state(service);
if (in_background) {
if (!(state & (RC_SERVICE_INACTIVE | RC_SERVICE_STOPPED)))
exit(EXIT_FAILURE);
if (rc_yesno(getenv("IN_HOTPLUG")))
rc_service_mark(service, RC_SERVICE_HOTPLUGGED);
if (strcmp(runlevel, RC_LEVEL_SYSINIT) == 0)
ewarnx("WARNING: %s will be started in the"
" next runlevel", applet);
}
if (exclusive_fd == -1)
exclusive_fd = svc_lock(applet);
if (exclusive_fd == -1) {
if (errno == EACCES)
eerrorx("%s: superuser access required", applet);
if (state & RC_SERVICE_STOPPING)
ewarnx("WARNING: %s is stopping", applet);
else
ewarnx("WARNING: %s is already starting", applet);
}
fcntl(exclusive_fd, F_SETFD,
fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC);
if (state & RC_SERVICE_STARTED) {
ewarn("WARNING: %s has already been started", applet);
exit(EXIT_SUCCESS);
}
else if (state & RC_SERVICE_INACTIVE && !in_background)
ewarnx("WARNING: %s has already started, but is inactive",
applet);
rc_service_mark(service, RC_SERVICE_STARTING);
hook_out = RC_HOOK_SERVICE_START_OUT;
rc_plugin_run(RC_HOOK_SERVICE_START_IN, applet);
}
static void
svc_start_deps(void)
{
bool first;
RC_STRING *svc, *svc2;
RC_SERVICE state;
int depoptions = RC_DEP_TRACE, n;
size_t len;
char *p, *tmp;
pid_t pid;
errno = 0;
if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT)
depoptions |= RC_DEP_STRICT;
if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL))
eerrorx("failed to load deptree");
if (!types_b)
setup_types();
services = rc_deptree_depends(deptree, types_b, applet_list,
runlevel, 0);
if (TAILQ_FIRST(services)) {
eerrorn("ERROR: %s needs service(s) ", applet);
first = true;
TAILQ_FOREACH(svc, services, entries) {
if (first)
first = false;
else
fprintf(stderr, ", ");
fprintf(stderr, "%s", svc->value);
}
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
rc_stringlist_free(services);
services = NULL;
need_services = rc_deptree_depends(deptree, types_n,
applet_list, runlevel, depoptions);
use_services = rc_deptree_depends(deptree, types_nu,
applet_list, runlevel, depoptions);
if (!rc_runlevel_starting()) {
TAILQ_FOREACH(svc, use_services, entries) {
state = rc_service_state(svc->value);
/* Don't stop failed services again.
* If you remove this check, ensure that the
* exclusive file isn't created. */
if (state & RC_SERVICE_FAILED &&
rc_runlevel_starting())
continue;
if (state & RC_SERVICE_STOPPED) {
if (dry_run) {
printf(" %s", svc->value);
continue;
}
pid = service_start(svc->value);
if (!rc_conf_yesno("rc_parallel"))
rc_waitpid(pid);
}
}
}
if (dry_run)
return;
/* Now wait for them to start */
services = rc_deptree_depends(deptree, types_nua, applet_list,
runlevel, depoptions);
/* We use tmplist to hold our scheduled by list */
tmplist = rc_stringlist_new();
TAILQ_FOREACH(svc, services, entries) {
state = rc_service_state(svc->value);
if (state & RC_SERVICE_STARTED)
continue;
/* Don't wait for services which went inactive but are
* now in starting state which we are after */
if (state & RC_SERVICE_STARTING &&
state & RC_SERVICE_WASINACTIVE)
{
if (!rc_stringlist_find(need_services, svc->value) &&
!rc_stringlist_find(use_services, svc->value))
continue;
}
if (!svc_wait(svc->value))
eerror("%s: timed out waiting for %s",
applet, svc->value);
state = rc_service_state(svc->value);
if (state & RC_SERVICE_STARTED)
continue;
if (rc_stringlist_find(need_services, svc->value)) {
if (state & RC_SERVICE_INACTIVE ||
state & RC_SERVICE_WASINACTIVE)
{
rc_stringlist_add(tmplist, svc->value);
} else if (!TAILQ_FIRST(tmplist))
eerrorx("ERROR: cannot start %s as"
" %s would not start",
applet, svc->value);
}
}
if (TAILQ_FIRST(tmplist)) {
/* Set the state now, then unlink our exclusive so that
our scheduled list is preserved */
rc_service_mark(service, RC_SERVICE_STOPPED);
rc_stringlist_free(use_services);
use_services = NULL;
len = 0;
n = 0;
TAILQ_FOREACH(svc, tmplist, entries) {
rc_service_schedule_start(svc->value, service);
use_services = rc_deptree_depend(deptree,
"iprovide", svc->value);
TAILQ_FOREACH(svc2, use_services, entries)
rc_service_schedule_start(svc2->value, service);
rc_stringlist_free(use_services);
use_services = NULL;
len += strlen(svc->value) + 2;
n++;
}
len += 5;
tmp = p = xmalloc(sizeof(char) * len);
TAILQ_FOREACH(svc, tmplist, entries) {
if (p != tmp)
p += snprintf(p, len, ", ");
p += snprintf(p, len - (p - tmp),
"%s", svc->value);
}
rc_stringlist_free(tmplist);
tmplist = NULL;
ewarnx("WARNING: %s is scheduled to start when "
"%s has started", applet, tmp);
free(tmp);
}
rc_stringlist_free(tmplist);
tmplist = NULL;
rc_stringlist_free(services);
services = NULL;
}
static void svc_start_real()
{
bool started;
RC_STRING *svc, *svc2;
if (ibsave)
setenv("IN_BACKGROUND", ibsave, 1);
hook_out = RC_HOOK_SERVICE_START_DONE;
rc_plugin_run(RC_HOOK_SERVICE_START_NOW, applet);
started = (svc_exec("start", NULL) == 0);
if (ibsave)
unsetenv("IN_BACKGROUND");
if (rc_service_state(service) & RC_SERVICE_INACTIVE)
ewarnx("WARNING: %s has started, but is inactive", applet);
else if (!started)
eerrorx("ERROR: %s failed to start", applet);
rc_service_mark(service, RC_SERVICE_STARTED);
exclusive_fd = svc_unlock(applet, exclusive_fd);
hook_out = RC_HOOK_SERVICE_START_OUT;
rc_plugin_run(RC_HOOK_SERVICE_START_DONE, applet);
/* Now start any scheduled services */
services = rc_services_scheduled(service);
TAILQ_FOREACH(svc, services, entries)
if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
service_start(svc->value);
rc_stringlist_free(services);
services = NULL;
/* Do the same for any services we provide */
if (deptree) {
tmplist = rc_deptree_depend(deptree, "iprovide", applet);
TAILQ_FOREACH(svc, tmplist, entries) {
services = rc_services_scheduled(svc->value);
TAILQ_FOREACH(svc2, services, entries)
if (rc_service_state(svc2->value) &
RC_SERVICE_STOPPED)
service_start(svc2->value);
rc_stringlist_free(services);
services = NULL;
}
rc_stringlist_free(tmplist);
tmplist = NULL;
}
hook_out = 0;
rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet);
}
static void
svc_start(void)
{
if (dry_run)
einfon("start:");
else
svc_start_check();
if (deps)
svc_start_deps();
if (dry_run)
printf(" %s\n", applet);
else
svc_start_real();
}
static int
svc_stop_check(RC_SERVICE *state)
{
*state = rc_service_state(service);
if (rc_runlevel_stopping() && *state & RC_SERVICE_FAILED)
exit(EXIT_FAILURE);
if (in_background &&
!(*state & RC_SERVICE_STARTED) &&
!(*state & RC_SERVICE_INACTIVE))
exit(EXIT_FAILURE);
if (exclusive_fd == -1)
exclusive_fd = svc_lock(applet);
if (exclusive_fd == -1) {
if (errno == EACCES)
eerrorx("%s: superuser access required", applet);
if (*state & RC_SERVICE_STOPPING)
ewarnx("WARNING: %s is already stopping", applet);
eerrorx("ERROR: %s stopped by something else", applet);
}
fcntl(exclusive_fd, F_SETFD,
fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC);
if (*state & RC_SERVICE_STOPPED) {
ewarn("WARNING: %s is already stopped", applet);
return 1;
}
rc_service_mark(service, RC_SERVICE_STOPPING);
hook_out = RC_HOOK_SERVICE_STOP_OUT;
rc_plugin_run(RC_HOOK_SERVICE_STOP_IN, applet);
if (!rc_runlevel_stopping()) {
if (rc_service_in_runlevel(service, RC_LEVEL_SYSINIT))
ewarn("WARNING: you are stopping a sysinit service");
else if (rc_service_in_runlevel(service, RC_LEVEL_BOOT))
ewarn("WARNING: you are stopping a boot service");
}
return 0;
}
static void
svc_stop_deps(RC_SERVICE state)
{
int depoptions = RC_DEP_TRACE;
RC_STRING *svc;
pid_t pid;
if (state & RC_SERVICE_WASINACTIVE)
return;
errno = 0;
if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT)
depoptions |= RC_DEP_STRICT;
if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL))
eerrorx("failed to load deptree");
if (!types_m)
setup_types();
services = rc_deptree_depends(deptree, types_m, applet_list,
runlevel, depoptions);
tmplist = rc_stringlist_new();
TAILQ_FOREACH_REVERSE(svc, services, rc_stringlist, entries) {
state = rc_service_state(svc->value);
/* Don't stop failed services again.
* If you remove this check, ensure that the
* exclusive file isn't created. */
if (state & RC_SERVICE_FAILED &&
rc_runlevel_stopping())
continue;
if (state & RC_SERVICE_STARTED ||
state & RC_SERVICE_INACTIVE)
{
if (dry_run) {
printf(" %s", svc->value);
continue;
}
svc_wait(svc->value);
state = rc_service_state(svc->value);
if (state & RC_SERVICE_STARTED ||
state & RC_SERVICE_INACTIVE)
{
pid = service_stop(svc->value);
if (!rc_conf_yesno("rc_parallel"))
rc_waitpid(pid);
rc_stringlist_add(tmplist, svc->value);
}
}
}
rc_stringlist_free(services);
services = NULL;
if (dry_run)
return;
TAILQ_FOREACH(svc, tmplist, entries) {
if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
continue;
svc_wait(svc->value);
if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
continue;
if (rc_runlevel_stopping()) {
/* If shutting down, we should stop even
* if a dependant failed */
if (runlevel &&
(strcmp(runlevel,
RC_LEVEL_SHUTDOWN) == 0 ||
strcmp(runlevel,
RC_LEVEL_SINGLE) == 0))
continue;
rc_service_mark(service, RC_SERVICE_FAILED);
}
eerrorx("ERROR: cannot stop %s as %s "
"is still up", applet, svc->value);
}
rc_stringlist_free(tmplist);
tmplist = NULL;
/* We now wait for other services that may use us and are
* stopping. This is important when a runlevel stops */
services = rc_deptree_depends(deptree, types_mua, applet_list,
runlevel, depoptions);
TAILQ_FOREACH(svc, services, entries) {
if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
continue;
svc_wait(svc->value);
}
rc_stringlist_free(services);
services = NULL;
}
static void
svc_stop_real(void)
{
bool stopped;
/* If we're stopping localmount, set LC_ALL=C so that
* bash doesn't load anything blocking the unmounting of /usr */
if (strcmp(applet, "localmount") == 0)
setenv("LC_ALL", "C", 1);
if (ibsave)
setenv("IN_BACKGROUND", ibsave, 1);
hook_out = RC_HOOK_SERVICE_STOP_DONE;
rc_plugin_run(RC_HOOK_SERVICE_STOP_NOW, applet);
stopped = (svc_exec("stop", NULL) == 0);
if (ibsave)
unsetenv("IN_BACKGROUND");
if (!stopped)
eerrorx("ERROR: %s failed to stop", applet);
if (in_background)
rc_service_mark(service, RC_SERVICE_INACTIVE);
else
rc_service_mark(service, RC_SERVICE_STOPPED);
hook_out = RC_HOOK_SERVICE_STOP_OUT;
rc_plugin_run(RC_HOOK_SERVICE_STOP_DONE, applet);
hook_out = 0;
rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet);
}
static int
svc_stop(void)
{
RC_SERVICE state;
state = 0;
if (dry_run)
einfon("stop:");
else
if (svc_stop_check(&state) == 1)
return 1; /* Service has been stopped already */
if (deps)
svc_stop_deps(state);
if (dry_run)
printf(" %s\n", applet);
else
svc_stop_real();
return 0;
}
static void
svc_restart(void)
{
/* This is hairly and a better way needs to be found I think!
* The issue is this - openvpn need net and dns. net can restart
* dns via resolvconf, so you could have openvpn trying to restart
* dnsmasq which in turn is waiting on net which in turn is waiting
* on dnsmasq.
* The work around is for resolvconf to restart its services with
* --nodeps which means just that.
* The downside is that there is a small window when our status is
* invalid.
* One workaround would be to introduce a new status,
* or status locking. */
if (!deps) {
RC_SERVICE state = rc_service_state(service);
if (state & RC_SERVICE_STARTED || state & RC_SERVICE_INACTIVE)
svc_exec("stop", "start");
else
svc_exec("start", NULL);
return;
}
if (!(rc_service_state(service) & RC_SERVICE_STOPPED)) {
get_started_services();
svc_stop();
if (dry_run)
ewarn("Cannot calculate restart start dependencies"
" on a dry-run");
}
svc_start();
start_services(restart_services);
rc_stringlist_free(restart_services);
restart_services = NULL;
}
static bool
service_plugable(void)
{
char *list, *p, *token;
bool allow = true, truefalse;
char *match = rc_conf_value("rc_hotplug");
if (!match)
match = rc_conf_value("rc_plug_services");
if (!match)
return false;
list = xstrdup(match);
p = list;
while ((token = strsep(&p, " "))) {
if (token[0] == '!') {
truefalse = false;
token++;
} else
truefalse = true;
if (fnmatch(token, applet, 0) == 0) {
allow = truefalse;
break;
}
}
#ifdef DEBUG_MEMORY
free(list);
#endif
return allow;
}
#include "_usage.h"
#define getoptstring "dDsSvl:Z" getoptstring_COMMON
#define extraopts "stop | start | restart | describe | zap"
static const struct option longopts[] = {
{ "debug", 0, NULL, 'd'},
{ "dry-run", 0, NULL, 'Z'},
{ "ifstarted", 0, NULL, 's'},
{ "ifstopped", 0, NULL, 'S'},
{ "nodeps", 0, NULL, 'D'},
{ "lockfd", 1, NULL, 'l'},
longopts_COMMON
};
static const char *const longopts_help[] = {
"set xtrace when running the script",
"show what would be done",
"only run commands when started",
"only run commands when stopped",
"ignore dependencies",
"fd of the exclusive lock from rc",
longopts_help_COMMON
};
#include "_usage.c"
int
runscript(int argc, char **argv)
{
bool doneone = false;
int retval, opt, depoptions = RC_DEP_TRACE;
RC_STRING *svc;
char path[PATH_MAX], lnk[PATH_MAX];
char *dir, *save = NULL, *saveLnk = NULL;
char pidstr[10];
size_t l = 0, ll;
const char *file;
struct stat stbuf;
/* Show help if insufficient args */
if (argc < 2 || !exists(argv[1])) {
fprintf(stderr, "runscript should not be run directly\n");
exit(EXIT_FAILURE);
}
if (stat(argv[1], &stbuf) != 0) {
fprintf(stderr, "runscript `%s': %s\n",
argv[1], strerror(errno));
exit(EXIT_FAILURE);
}
atexit(cleanup);
/* We need to work out the real full path to our service.
* This works fine, provided that we ONLY allow multiplexed services
* to exist in the same directory as the master link.
* Also, the master link as to be a real file in the init dir. */
if (!realpath(argv[1], path)) {
fprintf(stderr, "realpath: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
memset(lnk, 0, sizeof(lnk));
if (readlink(argv[1], lnk, sizeof(lnk)-1)) {
dir = dirname(path);
if (strchr(lnk, '/')) {
save = xstrdup(dir);
saveLnk = xstrdup(lnk);
dir = dirname(saveLnk);
if (strcmp(dir, save) == 0)
file = basename_c(argv[1]);
else
file = basename_c(lnk);
dir = save;
} else
file = basename_c(argv[1]);
ll = strlen(dir) + strlen(file) + 2;
service = xmalloc(ll);
snprintf(service, ll, "%s/%s", dir, file);
if (stat(service, &stbuf) != 0) {
free(service);
service = xstrdup(lnk);
}
free(save);
free(saveLnk);
}
if (!service)
service = xstrdup(path);
applet = basename_c(service);
if (argc < 3)
usage(EXIT_FAILURE);
/* Change dir to / to ensure all init scripts don't use stuff in pwd */
if (chdir("/") == -1)
eerror("chdir: %s", strerror(errno));
if ((runlevel = xstrdup(getenv("RC_RUNLEVEL"))) == NULL) {
env_filter();
env_config();
runlevel = rc_runlevel_get();
}
setenv("EINFO_LOG", service, 1);
setenv("RC_SVCNAME", applet, 1);
/* Set an env var so that we always know our pid regardless of any
subshells the init script may create so that our mark_service_*
functions can always instruct us of this change */
snprintf(pidstr, sizeof(pidstr), "%d", (int) getpid());
setenv("RC_RUNSCRIPT_PID", pidstr, 1);
/* eprefix is kinda klunky, but it works for our purposes */
if (rc_conf_yesno("rc_parallel")) {
/* Get the longest service name */
services = rc_services_in_runlevel(NULL);
TAILQ_FOREACH(svc, services, entries) {
ll = strlen(svc->value);
if (ll > l)
l = ll;
}
rc_stringlist_free(services);
services = NULL;
ll = strlen(applet);
if (ll > l)
l = ll;
/* Make our prefix string */
prefix = xmalloc(sizeof(char) * l + 1);
ll = strlen(applet);
memcpy(prefix, applet, ll);
memset(prefix + ll, ' ', l - ll);
memset(prefix + l, 0, 1);
eprefix(prefix);
}
#ifdef __linux__
/* Ok, we are ready to go, so setup selinux if applicable */
setup_selinux(argc, argv);
#endif
deps = true;
/* Punt the first arg as its our service name */
argc--;
argv++;
/* Right then, parse any options there may be */
while ((opt = getopt_long(argc, argv, getoptstring,
longopts, (int *)0)) != -1)
switch (opt) {
case 'd':
setenv("RC_DEBUG", "YES", 1);
break;
case 'l':
exclusive_fd = atoi(optarg);
fcntl(exclusive_fd, F_SETFD,
fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC);
break;
case 's':
if (!(rc_service_state(service) & RC_SERVICE_STARTED))
exit(EXIT_FAILURE);
break;
case 'S':
if (!(rc_service_state(service) & RC_SERVICE_STOPPED))
exit(EXIT_FAILURE);
break;
case 'D':
deps = false;
break;
case 'Z':
dry_run = true;
break;
case_RC_COMMON_GETOPT
}
/* If we're changing runlevels and not called by rc then we cannot
work with any dependencies */
if (deps && getenv("RC_PID") == NULL &&
(rc_runlevel_starting() || rc_runlevel_stopping()))
deps = false;
/* Save the IN_BACKGROUND env flag so it's ONLY passed to the service
that is being called and not any dependents */
if (getenv("IN_BACKGROUND")) {
ibsave = xstrdup(getenv("IN_BACKGROUND"));
in_background = rc_yesno(ibsave);
unsetenv("IN_BACKGROUND");
}
if (rc_yesno(getenv("IN_HOTPLUG"))) {
if (!service_plugable())
eerrorx("%s: not allowed to be hotplugged", applet);
in_background = true;
}
/* Setup a signal handler */
signal_setup(SIGHUP, handle_signal);
signal_setup(SIGINT, handle_signal);
signal_setup(SIGQUIT, handle_signal);
signal_setup(SIGTERM, handle_signal);
signal_setup(SIGCHLD, handle_signal);
/* Load our plugins */
rc_plugin_load();
applet_list = rc_stringlist_new();
rc_stringlist_add(applet_list, applet);
/* Now run each option */
retval = EXIT_SUCCESS;
while (optind < argc) {
optarg = argv[optind++];
/* Abort on a sighup here */
if (sighup)
exit (EXIT_FAILURE);
/* Export the command we're running.
This is important as we stamp on the restart function now but
some start/stop routines still need to behave differently if
restarting. */
unsetenv("RC_CMD");
setenv("RC_CMD", optarg, 1);
doneone = true;
if (strcmp(optarg, "describe") == 0 ||
strcmp(optarg, "help") == 0 ||
strcmp(optarg, "depend") == 0)
{
save = prefix;
eprefix(NULL);
prefix = NULL;
svc_exec(optarg, NULL);
eprefix(save);
prefix = save;
} else if (strcmp(optarg, "ineed") == 0 ||
strcmp(optarg, "iuse") == 0 ||
strcmp(optarg, "needsme") == 0 ||
strcmp(optarg, "usesme") == 0 ||
strcmp(optarg, "iafter") == 0 ||
strcmp(optarg, "ibefore") == 0 ||
strcmp(optarg, "iprovide") == 0)
{
errno = 0;
if (rc_conf_yesno("rc_depend_strict") ||
errno == ENOENT)
depoptions |= RC_DEP_STRICT;
if (!deptree &&
((deptree = _rc_deptree_load(0, NULL)) == NULL))
eerrorx("failed to load deptree");
tmplist = rc_stringlist_new();
rc_stringlist_add(tmplist, optarg);
services = rc_deptree_depends(deptree, tmplist,
applet_list,
runlevel, depoptions);
rc_stringlist_free(tmplist);
TAILQ_FOREACH(svc, services, entries)
printf("%s ", svc->value);
printf ("\n");
rc_stringlist_free(services);
services = NULL;
} else if (strcmp (optarg, "status") == 0) {
save = prefix;
eprefix(NULL);
prefix = NULL;
retval = svc_exec("status", NULL);
} else {
if (strcmp(optarg, "conditionalrestart") == 0 ||
strcmp(optarg, "condrestart") == 0)
{
if (rc_service_state(service) &
RC_SERVICE_STARTED)
svc_restart();
} else if (strcmp(optarg, "restart") == 0) {
svc_restart();
} else if (strcmp(optarg, "start") == 0) {
svc_start();
} else if (strcmp(optarg, "stop") == 0 || strcmp(optarg, "pause") == 0) {
if (strcmp(optarg, "pause") == 0) {
ewarn("WARNING: 'pause' is deprecated; please use '--nodeps stop'");
deps = false;
}
if (deps && in_background)
get_started_services();
if (svc_stop() == 1)
continue; /* Service has been stopped already */
if (deps) {
if (!in_background &&
!rc_runlevel_stopping() &&
rc_service_state(service) &
RC_SERVICE_STOPPED)
unhotplug();
if (in_background &&
rc_service_state(service) &
RC_SERVICE_INACTIVE)
{
TAILQ_FOREACH(svc,
restart_services,
entries)
if (rc_service_state(svc->value) &
RC_SERVICE_STOPPED)
rc_service_schedule_start(service, svc->value);
}
}
} else if (strcmp(optarg, "zap") == 0) {
einfo("Manually resetting %s to stopped state",
applet);
if (!rc_service_mark(applet,
RC_SERVICE_STOPPED))
eerrorx("rc_service_mark: %s",
strerror(errno));
unhotplug();
} else
retval = svc_exec(optarg, NULL);
/* We should ensure this list is empty after
* an action is done */
rc_stringlist_free(restart_services);
restart_services = NULL;
}
if (!doneone)
usage(EXIT_FAILURE);
}
return retval;
}