openrc/src/rc/runscript.c

1379 lines
34 KiB
C
Raw Normal View History

/*
* runscript.c
* Handle launching of init scripts.
*/
2008-01-14 10:35:22 +05:30
/*
2009-05-01 19:41:40 +05:30
* Copyright (c) 2007-2009 Roy Marples <roy@marples.name>
* 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.
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
2009-04-18 06:27:17 +05:30
#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>
2007-12-07 20:01:51 +05:30
#include <time.h>
#include <unistd.h>
#ifdef __linux__
2009-04-24 03:01:22 +05:30
# include <pty.h>
2008-01-10 04:52:04 +05:30
#elif defined(__NetBSD__) || defined(__OpenBSD__)
2009-04-24 03:01:22 +05:30
# include <util.h>
#else
2009-04-24 03:01:22 +05:30
# include <libutil.h>
#endif
#include "builtins.h"
#include "einfo.h"
#include "rc.h"
#include "rc-misc.h"
#include "rc-plugin.h"
2007-04-12 18:48:52 +05:30
#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;
2007-10-15 20:10:53 +05:30
#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)
{
2007-04-11 18:14:47 +05:30
void *lib_handle = NULL;
if (! exists(SELINUX_LIB))
return;
2007-04-11 18:14:47 +05:30
lib_handle = dlopen(SELINUX_LIB, RTLD_NOW | RTLD_GLOBAL);
if (! lib_handle) {
eerror("dlopen: %s", dlerror());
return;
2007-04-11 18:14:47 +05:30
}
selinux_run_init_old = (void (*)(void))
2009-04-24 03:01:22 +05:30
dlfunc(lib_handle, "selinux_runscript");
selinux_run_init_new = (void (*)(int, char **))
2009-04-24 03:01:22 +05:30
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)
{
2007-04-11 18:14:47 +05:30
int serrno = errno;
char signame[10] = { '\0' };
struct winsize ws;
2007-04-11 18:14:47 +05:30
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",
2009-04-24 03:01:22 +05:30
service, strerror(errno));
} else
rc_waitpid(-1);
break;
2007-04-11 18:14:47 +05:30
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);
2007-04-11 18:14:47 +05:30
}
/* 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 ||
2008-01-11 21:21:40 +05:30
state & RC_SERVICE_WASINACTIVE ||
state & RC_SERVICE_STARTING ||
state & RC_SERVICE_STARTED)
{
TAILQ_FOREACH(svc, list, entries) {
if (!(rc_service_state(svc->value) &
2009-04-24 03:01:22 +05:30
RC_SERVICE_STOPPED))
continue;
if (state & RC_SERVICE_INACTIVE ||
state & RC_SERVICE_WASINACTIVE)
{
rc_service_schedule_start(service,
2009-04-24 03:01:22 +05:30
svc->value);
ewarn("WARNING: %s is scheduled to started"
2009-04-24 03:01:22 +05:30
" 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,
2009-04-24 03:01:22 +05:30
applet);
else if (hook_out == RC_HOOK_SERVICE_STOP_DONE)
rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT,
2009-04-24 03:01:22 +05:30
applet);
}
2007-07-11 19:58:54 +05:30
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);
}
2007-04-11 18:14:47 +05:30
/* 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;
2007-10-12 05:31:33 +05:30
char *buffer;
size_t bytes;
bool prefixed = false;
int slave_tty;
/* 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 ||
2009-04-24 03:01:22 +05:30
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)
2009-04-24 03:01:22 +05:30
fcntl(master_tty, F_SETFD, flags | FD_CLOEXEC);
if (slave_tty >=0 &&
(flags = fcntl(slave_tty, F_GETFD, 0)) == 0)
2009-04-24 03:01:22 +05:30
fcntl(slave_tty, F_SETFD, flags | FD_CLOEXEC);
}
2008-02-20 03:15:01 +05:30
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",
2009-04-24 03:01:22 +05:30
RC_SVCDIR "/runscript.sh",
service, arg1, arg2, (char *) NULL);
eerror("%s: exec `" RC_SVCDIR "/runscript.sh': %s",
2009-04-24 03:01:22 +05:30
service, strerror(errno));
_exit(EXIT_FAILURE);
2007-04-11 18:14:47 +05:30
} else {
execl(RC_LIBDIR "/sh/runscript.sh",
2009-04-24 03:01:22 +05:30
RC_LIBDIR "/sh/runscript.sh",
service, arg1, arg2, (char *) NULL);
eerror("%s: exec `" RC_LIBDIR "/sh/runscript.sh': %s",
2009-04-24 03:01:22 +05:30
service, strerror(errno));
_exit(EXIT_FAILURE);
2007-04-11 18:14:47 +05:30
}
}
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",
2009-04-24 03:01:22 +05:30
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);
close(signal_pipe[0]);
close(signal_pipe[1]);
signal_pipe[0] = signal_pipe[1] = -1;
if (master_tty >= 0) {
2008-02-01 18:50:19 +05:30
/* Why did we do this? */
/* signal (SIGWINCH, SIG_IGN); */
close(master_tty);
master_tty = -1;
}
2009-05-05 14:54:21 +05:30
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;
2008-01-31 23:31:20 +05:30
/* Some services don't have a timeout, like fsck */
keywords = rc_deptree_depend(deptree, svc, "keyword");
if (rc_stringlist_find(keywords, "notimeout"))
forever = true;
rc_stringlist_free(keywords);
snprintf(file, sizeof(file), RC_SVCDIR "/exclusive/%s",
2009-04-24 03:01:22 +05:30
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) {
2009-04-24 14:02:44 +05:30
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;
2007-04-11 18:14:47 +05:30
state = rc_service_state(service);
if (in_background) {
2009-04-30 21:15:18 +05:30
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"
2009-04-24 03:01:22 +05:30
" next runlevel", applet);
2007-04-11 18:14:47 +05:30
}
2009-04-19 01:24:04 +05:30
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);
}
2009-04-19 01:24:04 +05:30
fcntl(exclusive_fd, F_SETFD,
2009-04-24 03:01:22 +05:30
fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC);
2009-04-19 01:24:04 +05:30
if (state & RC_SERVICE_STARTED) {
ewarn("WARNING: %s has already been started", applet);
return;
} else if (state & RC_SERVICE_INACTIVE && !in_background)
ewarnx("WARNING: %s has already started, but is inactive",
2009-04-24 03:01:22 +05:30
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);
2007-04-11 18:14:47 +05:30
}
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
rc_stringlist_free(services);
services = NULL;
2007-04-11 18:14:47 +05:30
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);
}
}
}
2007-04-11 18:14:47 +05:30
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",
2009-04-24 03:01:22 +05:30
applet, svc->value);
}
}
2007-04-11 18:14:47 +05:30
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);
2008-03-17 20:01:44 +05:30
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++;
2008-03-17 20:01:44 +05:30
}
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);
2007-04-11 18:14:47 +05:30
}
rc_stringlist_free(services);
services = NULL;
}
static void svc_start_real()
{
bool started;
RC_STRING *svc, *svc2;
2007-04-11 18:14:47 +05:30
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);
2007-04-11 18:14:47 +05:30
if (ibsave)
unsetenv("IN_BACKGROUND");
2007-04-11 18:14:47 +05:30
if (!started)
eerrorx("ERROR: %s failed to start", applet);
else {
if (rc_service_state(service) & RC_SERVICE_INACTIVE)
ewarnx("WARNING: %s has started, but is inactive",
2009-04-24 03:01:22 +05:30
applet);
2007-04-11 18:14:47 +05:30
}
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);
2007-04-11 18:14:47 +05:30
/* Now start any scheduled services */
services = rc_services_scheduled(service);
TAILQ_FOREACH(svc, services, entries)
2009-04-24 03:01:22 +05:30
if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
service_start(svc->value);
rc_stringlist_free(services);
services = NULL;
2007-04-11 18:14:47 +05:30
/* 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)
2009-04-24 03:01:22 +05:30
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;
2007-04-11 18:14:47 +05:30
}
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();
}
2007-04-11 18:14:47 +05:30
static void
svc_stop_check(RC_SERVICE *state)
{
*state = rc_service_state(service);
if (rc_runlevel_stopping() && *state & RC_SERVICE_FAILED)
exit(EXIT_FAILURE);
2007-04-11 18:14:47 +05:30
if (in_background &&
!(*state & RC_SERVICE_STARTED) &&
!(*state & RC_SERVICE_INACTIVE))
exit(EXIT_FAILURE);
2007-04-11 18:14:47 +05:30
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);
}
2009-04-19 01:24:04 +05:30
fcntl(exclusive_fd, F_SETFD,
2009-04-24 03:01:22 +05:30
fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC);
2009-04-19 01:24:04 +05:30
if (*state & RC_SERVICE_STOPPED) {
ewarn("WARNING: %s is already stopped", applet);
return;
2008-03-06 17:04:38 +05:30
}
2007-04-11 18:14:47 +05:30
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");
}
}
2007-04-11 18:14:47 +05:30
static void
svc_stop_deps(RC_SERVICE state)
{
int depoptions = RC_DEP_TRACE;
RC_STRING *svc;
pid_t pid;
2007-04-11 18:14:47 +05:30
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);
2007-04-11 18:14:47 +05:30
}
}
}
rc_stringlist_free(services);
services = NULL;
if (dry_run)
return;
2007-04-11 18:14:47 +05:30
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);
2007-04-11 18:14:47 +05:30
}
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);
2007-04-11 18:14:47 +05:30
}
rc_stringlist_free(services);
services = NULL;
}
2007-04-11 18:14:47 +05:30
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);
2007-04-11 18:14:47 +05:30
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);
2007-04-11 18:14:47 +05:30
if (ibsave)
unsetenv("IN_BACKGROUND");
2007-04-11 18:14:47 +05:30
if (!stopped)
eerrorx("ERROR: %s failed to stop", applet);
2007-04-11 18:14:47 +05:30
if (in_background)
rc_service_mark(service, RC_SERVICE_INACTIVE);
2007-04-11 18:14:47 +05:30
else
rc_service_mark(service, RC_SERVICE_STOPPED);
2007-04-11 18:14:47 +05:30
hook_out = RC_HOOK_SERVICE_STOP_OUT;
rc_plugin_run(RC_HOOK_SERVICE_STOP_DONE, applet);
2007-04-11 18:14:47 +05:30
hook_out = 0;
rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet);
}
static void
svc_stop(void)
{
RC_SERVICE state;
state = 0;
if (dry_run)
einfon("stop:");
else
svc_stop_check(&state);
if (deps)
svc_stop_deps(state);
if (dry_run)
printf(" %s\n", applet);
else
svc_stop_real();
}
static void
svc_restart(void)
{
2007-04-11 18:14:47 +05:30
/* 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 it's 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");
2007-04-11 18:14:47 +05:30
else
svc_exec("start", NULL);
2007-04-11 18:14:47 +05:30
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");
2007-04-11 18:14:47 +05:30
}
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)
2008-10-30 20:33:12 +05:30
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 "dDsvl:Z" getoptstring_COMMON
#define extraopts "stop | start | restart | describe | zap"
static const struct option longopts[] = {
2007-04-17 18:14:32 +05:30
{ "debug", 0, NULL, 'd'},
{ "dry-run", 0, NULL, 'Z'},
{ "ifstarted", 0, NULL, 's'},
2007-04-17 18:14:32 +05:30
{ "nodeps", 0, NULL, 'D'},
{ "lockfd", 1, NULL, 'l'},
longopts_COMMON
2007-04-17 18:14:32 +05:30
};
static const char *const longopts_help[] = {
"set xtrace when running the script",
"show what would be done",
"only run commands when started",
"ignore dependencies",
"fd of the exclusive lock from rc",
longopts_help_COMMON
};
#include "_usage.c"
2007-04-17 18:14:32 +05:30
int
runscript(int argc, char **argv)
{
2007-04-11 18:14:47 +05:30
bool doneone = false;
int retval, opt, depoptions = RC_DEP_TRACE;
RC_STRING *svc;
char path[PATH_MAX], lnk[PATH_MAX], *dir, *save = NULL, 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",
2009-04-24 03:01:22 +05:30
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 mulitplexed 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);
dir = dirname(lnk);
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);
}
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();
2007-04-11 18:14:47 +05:30
}
setenv("EINFO_LOG", service, 1);
setenv("RC_SVCNAME", applet, 1);
2007-04-11 18:14:47 +05:30
/* 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 */
2008-04-06 18:54:10 +05:30
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")) {
2007-07-06 21:20:40 +05:30
/* 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;
2007-07-06 22:37:29 +05:30
/* 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__
2007-04-11 18:14:47 +05:30
/* Ok, we are ready to go, so setup selinux if applicable */
setup_selinux(argc, argv);
#endif
deps = true;
2007-04-12 16:10:51 +05:30
/* Punt the first arg as it's our service name */
argc--;
argv++;
2007-04-11 18:14:47 +05:30
/* Right then, parse any options there may be */
while ((opt = getopt_long(argc, argv, getoptstring,
2009-04-24 03:01:22 +05:30
longopts, (int *)0)) != -1)
switch (opt) {
case 'd':
2009-01-13 16:34:37 +05:30
setenv("RC_DEBUG", "YES", 1);
break;
case 'l':
exclusive_fd = atoi(optarg);
2009-04-19 01:24:04 +05:30
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 'D':
deps = false;
break;
case 'Z':
dry_run = true;
break;
case_RC_COMMON_GETOPT;
}
2007-04-11 18:14:47 +05:30
/* 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;
2007-04-12 16:10:51 +05:30
/* 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");
2007-04-12 16:10:51 +05:30
}
if (rc_yesno(getenv("IN_HOTPLUG"))) {
if (!service_plugable())
eerrorx("%s: not allowed to be hotplugged", applet);
in_background = true;
2007-04-11 18:14:47 +05:30
}
/* 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);
2007-04-11 18:14:47 +05:30
/* Load our plugins */
rc_plugin_load();
applet_list = rc_stringlist_new();
rc_stringlist_add(applet_list, applet);
2007-04-11 18:14:47 +05:30
/* Now run each option */
retval = EXIT_SUCCESS;
while (optind < argc) {
optarg = argv[optind++];
2007-04-11 18:14:47 +05:30
/* 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);
2007-04-11 18:14:47 +05:30
doneone = true;
if (strcmp(optarg, "describe") == 0 ||
strcmp(optarg, "help") == 0)
{
save = prefix;
eprefix(NULL);
prefix = NULL;
svc_exec(optarg, NULL);
eprefix(save);
prefix = save;
} else if (strcmp(optarg, "ineed") == 0 ||
2009-04-24 03:01:22 +05:30
strcmp(optarg, "iuse") == 0 ||
strcmp(optarg, "needsme") == 0 ||
strcmp(optarg, "usesme") == 0 ||
strcmp(optarg, "iafter") == 0 ||
strcmp(optarg, "ibefore") == 0 ||
strcmp(optarg, "iprovide") == 0)
2008-01-18 16:57:49 +05:30
{
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,
2009-04-24 03:01:22 +05:30
applet_list,
runlevel, depoptions);
rc_stringlist_free(tmplist);
TAILQ_FOREACH(svc, services, entries)
2009-04-24 03:01:22 +05:30
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) {
if (deps && in_background)
get_started_services();
svc_stop();
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,
2009-04-24 03:01:22 +05:30
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",
2009-04-24 03:01:22 +05:30
applet);
if (!rc_service_mark(applet,
2009-04-24 03:01:22 +05:30
RC_SERVICE_STOPPED))
eerrorx("rc_service_mark: %s",
2009-04-24 03:01:22 +05:30
strerror(errno));
unhotplug();
2007-07-11 01:41:42 +05:30
} else
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);
2007-04-11 18:14:47 +05:30
}
return retval;
}