openrc/src/runscript.c
Roy Marples 45bd125dcc Use a pty for prefixed output instead of pipes for stdout/stderr. This
is so that programs can get information about the controlling terminal.
This change was triggered by bug #188506 where it's possible that
stdin, stdout and stderr didn't point to a terminal but ended up on one
via our pipes. Using a pty means that stdout and stderr always point to
a terminal, but we lose the ability to tell them apart.
If there is not a pty available then we use un-prefixed output as normal.
This change has also introduced the need for a signal pipe so that
SIGCHLD can exit the loop cleanly.
2007-09-21 08:49:43 +00:00

1286 lines
34 KiB
C

/*
* runscript.c
* Handle launching of Gentoo init scripts.
*
* Copyright 1999-2007 Gentoo Foundation
* Distributed under the terms of the GNU General Public License v2
*/
#define APPLET "runscript"
#include <sys/select.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <libgen.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#ifdef __linux__
# include <pty.h>
#else
# include <libutil.h>
#endif
#include "builtins.h"
#include "einfo.h"
#include "rc.h"
#include "rc-misc.h"
#include "rc-plugin.h"
#include "strlist.h"
#define RCSCRIPT_HELP RC_LIBDIR "/sh/rc-help.sh"
#define SELINUX_LIB RC_LIBDIR "/runscript_selinux.so"
#define PREFIX_LOCK RC_SVCDIR "/prefix.lock"
static char *applet = NULL;
static char *service = NULL;
static char *exclusive = NULL;
static char *mtime_test = NULL;
static rc_depinfo_t *deptree = NULL;
static char **services = NULL;
static char **svclist = NULL;
static char **tmplist = NULL;
static char **providelist = NULL;
static char **types = NULL;
static char **restart_services = NULL;
static char **need_services = NULL;
static char **use_services = NULL;
static char **env = NULL;
static char *tmp = NULL;
static char *softlevel = NULL;
static bool sighup = false;
static char *ibsave = NULL;
static bool in_background = false;
static rc_hook_t hook_out = 0;
static pid_t service_pid = 0;
static char *prefix = NULL;
static bool prefix_locked = false;
static int signal_pipe[2] = { -1, -1 };
static int master_tty = -1;
extern char **environ;
#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);
static void setup_selinux (int argc, char **argv)
{
void *lib_handle = NULL;
if (! rc_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 rc_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 {
wait (0);
}
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");
case SIGTERM:
if (! signame[0])
snprintf (signame, sizeof (signame), "SIGTERM");
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);
default:
eerror ("%s: caught unknown signal %d", applet, sig);
}
/* Restore errno */
errno = serrno;
}
static time_t get_mtime (const char *pathname, bool follow_link)
{
struct stat buf;
int retval;
if (! pathname)
return (0);
retval = follow_link ? stat (pathname, &buf) : lstat (pathname, &buf);
if (! retval)
return (buf.st_mtime);
errno = 0;
return (0);
}
static bool in_control ()
{
char *path;
time_t mtime;
const char *tests[] = { "starting", "started", "stopping",
"inactive", "wasinactive", NULL };
int i = 0;
if (sighup)
return (false);
if (! mtime_test || ! rc_exists (mtime_test))
return (false);
if (rc_service_state (applet, rc_service_stopped))
return (false);
if (! (mtime = get_mtime (mtime_test, false)))
return (false);
while (tests[i]) {
path = rc_strcatpaths (RC_SVCDIR, tests[i], applet, (char *) NULL);
if (rc_exists (path)) {
time_t m = get_mtime (path, false);
if (mtime < m && m != 0) {
free (path);
return (false);
}
}
free (path);
i++;
}
return (true);
}
static void uncoldplug ()
{
char *cold = rc_strcatpaths (RC_SVCDIR, "coldplugged", applet, (char *) NULL);
if (rc_exists (cold) && unlink (cold) != 0)
eerror ("%s: unlink `%s': %s", applet, cold, strerror (errno));
free (cold);
}
static void start_services (char **list) {
bool inactive;
char *svc;
int i;
if (! list)
return;
inactive = rc_service_state (service, rc_service_inactive);
if (! inactive)
inactive = rc_service_state (service, rc_service_wasinactive);
if (inactive ||
rc_service_state (service, rc_service_starting) ||
rc_service_state (service, rc_service_started))
{
STRLIST_FOREACH (list, svc, i) {
if (rc_service_state (svc, rc_service_stopped)) {
if (inactive) {
rc_schedule_start_service (service, svc);
ewarn ("WARNING: %s is scheduled to started when %s has started",
svc, applet);
} else
rc_start_service (svc);
}
}
}
}
static void cleanup (void)
{
if (! rc_in_plugin) {
if (prefix_locked)
unlink (PREFIX_LOCK);
if (hook_out)
rc_plugin_run (hook_out, applet);
if (restart_services)
start_services (restart_services);
}
rc_plugin_unload ();
rc_free_deptree (deptree);
rc_strlist_free (services);
rc_strlist_free (types);
rc_strlist_free (svclist);
rc_strlist_free (providelist);
rc_strlist_free (need_services);
rc_strlist_free (use_services);
rc_strlist_free (restart_services);
rc_strlist_free (tmplist);
free (ibsave);
if (! rc_in_plugin && in_control ()) {
if (rc_service_state (applet, rc_service_stopping)) {
/* If the we're shutting down, do it cleanly */
if ((softlevel &&
rc_runlevel_stopping () &&
(strcmp (softlevel, RC_LEVEL_SHUTDOWN) == 0 ||
strcmp (softlevel, RC_LEVEL_REBOOT) == 0)))
rc_mark_service (applet, rc_service_stopped);
else if (rc_service_state (applet, rc_service_wasinactive))
rc_mark_service (applet, rc_service_inactive);
else
rc_mark_service (applet, rc_service_started);
}
else if (rc_service_state (applet, rc_service_starting))
{
if (rc_service_state (applet, rc_service_wasinactive))
rc_mark_service (applet, rc_service_inactive);
else
rc_mark_service (applet, rc_service_stopped);
}
if (exclusive && rc_exists (exclusive))
unlink (exclusive);
}
rc_strlist_free (env);
if (mtime_test)
{
if (! rc_in_plugin)
unlink (mtime_test);
free (mtime_test);
}
free (exclusive);
free (applet);
free (prefix);
free (service);
}
static int write_prefix (const char *buffer, size_t bytes, bool *prefixed) {
unsigned int i;
const char *ec = ecolor (ecolor_hilite);
const char *ec_normal = ecolor (ecolor_normal);
ssize_t ret = 0;
int fd = fileno (stdout);
for (i = 0; i < bytes; i++) {
/* We don't prefix escape codes, like eend */
if (buffer[i] == '\033')
*prefixed = true;
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);
}
return (ret);
}
static bool svc_exec (const char *arg1, const char *arg2)
{
bool execok;
int fdout = fileno (stdout);
struct termios tt;
struct winsize ws;
int i;
int flags;
fd_set rset;
int s;
char buffer[RC_LINEBUFFER];
size_t bytes;
bool prefixed = false;
int selfd;
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 ||
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);
}
service_pid = vfork();
if (service_pid == -1)
eerrorx ("%s: vfork: %s", service, strerror (errno));
if (service_pid == 0) {
if (slave_tty >= 0) {
/* Hmmm, this shouldn't work in a vfork, but it does which is
* good for us */
close (master_tty);
dup2 (slave_tty, 0);
dup2 (slave_tty, 1);
dup2 (slave_tty, 2);
if (slave_tty > 2)
close (slave_tty);
}
if (rc_exists (RC_SVCDIR "/runscript.sh")) {
execl (RC_SVCDIR "/runscript.sh", service, service, arg1, arg2,
(char *) NULL);
eerror ("%s: exec `" RC_SVCDIR "/runscript.sh': %s",
service, strerror (errno));
_exit (EXIT_FAILURE);
} else {
execl (RC_LIBDIR "/sh/runscript.sh", service, service, arg1, arg2,
(char *) NULL);
eerror ("%s: exec `" RC_LIBDIR "/sh/runscript.sh': %s",
service, strerror (errno));
_exit (EXIT_FAILURE);
}
}
/* We need to notify the child of window resizes now */
if (master_tty >= 0)
signal (SIGWINCH, handle_signal);
selfd = MAX (master_tty, signal_pipe[0]) + 1;
while (1) {
FD_ZERO (&rset);
FD_SET (signal_pipe[0], &rset);
if (master_tty >= 0)
FD_SET (master_tty, &rset);
if ((s = select (selfd, &rset, NULL, NULL, NULL)) == -1) {
if (errno != EINTR) {
eerror ("%s: select: %s", service, strerror (errno));
break;
}
}
if (s > 0) {
/* Only SIGCHLD signals come down this pipe */
if (FD_ISSET (signal_pipe[0], &rset))
break;
if (master_tty >= 0 && FD_ISSET (master_tty, &rset)) {
bytes = read (master_tty, buffer, sizeof (buffer));
write_prefix (buffer, bytes, &prefixed);
}
}
}
close (signal_pipe[0]);
close (signal_pipe[1]);
signal_pipe[0] = signal_pipe[1] = -1;
if (master_tty >= 0) {
signal (SIGWINCH, SIG_IGN);
close (master_tty);
master_tty = -1;
}
execok = rc_waitpid (service_pid) == 0 ? true : false;
service_pid = 0;
return (execok);
}
static rc_service_state_t svc_status ()
{
char status[10];
int (*e) (const char *fmt, ...) = &einfo;
rc_service_state_t retval = rc_service_stopped;
if (rc_service_state (service, rc_service_stopping)) {
snprintf (status, sizeof (status), "stopping");
e = &ewarn;
retval = rc_service_stopping;
} else if (rc_service_state (service, rc_service_starting)) {
snprintf (status, sizeof (status), "starting");
e = &ewarn;
retval = rc_service_starting;
} else if (rc_service_state (service, rc_service_inactive)) {
snprintf (status, sizeof (status), "inactive");
e = &ewarn;
retval = rc_service_inactive;
} else if (rc_service_state (service, rc_service_crashed)) {
snprintf (status, sizeof (status), "crashed");
e = &eerror;
retval = rc_service_crashed;
} else if (rc_service_state (service, rc_service_started)) {
snprintf (status, sizeof (status), "started");
retval = rc_service_started;
} else
snprintf (status, sizeof (status), "stopped");
e ("status: %s", status);
return (retval);
}
static void make_exclusive ()
{
char *path;
int i;
/* We create a fifo so that other services can wait until we complete */
if (! exclusive)
exclusive = rc_strcatpaths (RC_SVCDIR, "exclusive", applet, (char *) NULL);
if (mkfifo (exclusive, 0600) != 0 && errno != EEXIST &&
(errno != EACCES || geteuid () == 0))
eerrorx ("%s: unable to create fifo `%s': %s",
applet, exclusive, strerror (errno));
path = rc_strcatpaths (RC_SVCDIR, "exclusive", applet, (char *) NULL);
i = strlen (path) + 16;
mtime_test = rc_xmalloc (sizeof (char *) * i);
snprintf (mtime_test, i, "%s.%d", path, getpid ());
free (path);
if (rc_exists (mtime_test) && unlink (mtime_test) != 0) {
eerror ("%s: unlink `%s': %s",
applet, mtime_test, strerror (errno));
free (mtime_test);
mtime_test = NULL;
return;
}
if (symlink (service, mtime_test) != 0) {
eerror ("%s: symlink `%s' to `%s': %s",
applet, service, mtime_test, strerror (errno));
free (mtime_test);
mtime_test = NULL;
}
}
static void unlink_mtime_test ()
{
if (unlink (mtime_test) != 0)
eerror ("%s: unlink `%s': %s", applet, mtime_test, strerror (errno));
free (mtime_test);
mtime_test = NULL;
}
static void get_started_services ()
{
char *svc;
int i;
rc_strlist_free (tmplist);
tmplist = rc_services_in_state (rc_service_inactive);
rc_strlist_free (restart_services);
restart_services = rc_services_in_state (rc_service_started);
STRLIST_FOREACH (tmplist, svc, i)
rc_strlist_addsort (&restart_services, svc);
rc_strlist_free (tmplist);
tmplist = NULL;
}
static void svc_start (bool deps)
{
bool started;
bool background = false;
char *svc;
char *svc2;
int i;
int j;
int depoptions = RC_DEP_TRACE;
rc_plugin_run (rc_hook_service_start_in, applet);
hook_out = rc_hook_service_start_out;
if (rc_is_env ("IN_HOTPLUG", "1") || in_background) {
if (! rc_service_state (service, rc_service_inactive) &&
! rc_service_state (service, rc_service_stopped))
exit (EXIT_FAILURE);
background = true;
}
if (rc_service_state (service, rc_service_started)) {
ewarn ("WARNING: %s has already been started", applet);
return;
} else if (rc_service_state (service, rc_service_starting))
ewarnx ("WARNING: %s is already starting", applet);
else if (rc_service_state (service, rc_service_stopping))
ewarnx ("WARNING: %s is stopping", applet);
else if (rc_service_state (service, rc_service_inactive) && ! background)
ewarnx ("WARNING: %s has already started, but is inactive", applet);
if (! rc_mark_service (service, rc_service_starting))
eerrorx ("ERROR: %s has been started by something else", applet);
make_exclusive (service);
if (rc_is_env ("RC_DEPEND_STRICT", "yes"))
depoptions |= RC_DEP_STRICT;
if (rc_runlevel_starting ())
depoptions |= RC_DEP_START;
if (deps) {
if (! deptree && ((deptree = rc_load_deptree ()) == NULL))
eerrorx ("failed to load deptree");
rc_strlist_free (types);
types = NULL;
rc_strlist_add (&types, "broken");
rc_strlist_free (svclist);
svclist = NULL;
rc_strlist_add (&svclist, applet);
rc_strlist_free (services);
services = rc_get_depends (deptree, types, svclist, softlevel, 0);
if (services) {
eerrorn ("ERROR: `%s' needs ", applet);
STRLIST_FOREACH (services, svc, i) {
if (i > 0)
fprintf (stderr, ", ");
fprintf (stderr, "%s", svc);
}
exit (EXIT_FAILURE);
}
rc_strlist_free (services);
services = NULL;
rc_strlist_free (types);
types = NULL;
rc_strlist_add (&types, "ineed");
rc_strlist_free (need_services);
need_services = rc_get_depends (deptree, types, svclist,
softlevel, depoptions);
rc_strlist_add (&types, "iuse");
rc_strlist_free (use_services);
use_services = rc_get_depends (deptree, types, svclist,
softlevel, depoptions);
if (! rc_runlevel_starting ()) {
STRLIST_FOREACH (use_services, svc, i)
if (rc_service_state (svc, rc_service_stopped)) {
pid_t pid = rc_start_service (svc);
if (! rc_is_env ("RC_PARALLEL", "yes"))
rc_waitpid (pid);
}
}
/* Now wait for them to start */
rc_strlist_add (&types, "iafter");
services = rc_get_depends (deptree, types, svclist,
softlevel, depoptions);
/* We use tmplist to hold our scheduled by list */
rc_strlist_free (tmplist);
tmplist = NULL;
STRLIST_FOREACH (services, svc, i) {
if (rc_service_state (svc, rc_service_started))
continue;
/* Don't wait for services which went inactive but are now in
* starting state which we are after */
if (rc_service_state (svc, rc_service_starting) &&
rc_service_state(svc, rc_service_wasinactive)) {
bool use = false;
STRLIST_FOREACH (use_services, svc2, j)
if (strcmp (svc, svc2) == 0) {
use = true;
break;
}
if (! use)
continue;
}
if (! rc_wait_service (svc))
eerror ("%s: timed out waiting for %s", applet, svc);
if (rc_service_state (svc, rc_service_started))
continue;
STRLIST_FOREACH (need_services, svc2, j)
if (strcmp (svc, svc2) == 0) {
if (rc_service_state (svc, rc_service_inactive) ||
rc_service_state (svc, rc_service_wasinactive))
rc_strlist_add (&tmplist, svc);
else
eerrorx ("ERROR: cannot start %s as %s would not start",
applet, svc);
}
}
if (tmplist) {
int n = 0;
int len = 0;
char *p;
/* Set the state now, then unlink our exclusive so that
our scheduled list is preserved */
rc_mark_service (service, rc_service_stopped);
unlink_mtime_test ();
rc_strlist_free (types);
types = NULL;
rc_strlist_add (&types, "iprovide");
STRLIST_FOREACH (tmplist, svc, i) {
rc_schedule_start_service (svc, service);
rc_strlist_free (svclist);
svclist = NULL;
rc_strlist_add (&svclist, svc);
rc_strlist_free (providelist);
providelist = rc_get_depends (deptree, types, svclist,
softlevel, depoptions);
STRLIST_FOREACH (providelist, svc2, j)
rc_schedule_start_service (svc2, service);
len += strlen (svc) + 2;
n++;
}
len += 5;
tmp = rc_xmalloc (sizeof (char *) * len);
p = tmp;
STRLIST_FOREACH (tmplist, svc, i) {
if (i > 1) {
if (i == n)
p += snprintf (p, len, " or ");
else
p += snprintf (p, len, ", ");
}
p += snprintf (p, len, "%s", svc);
}
ewarnx ("WARNING: %s is scheduled to start when %s has started",
applet, tmp);
}
rc_strlist_free (services);
services = NULL;
rc_strlist_free (types);
types = NULL;
rc_strlist_free (svclist);
svclist = NULL;
}
if (ibsave)
setenv ("IN_BACKGROUND", ibsave, 1);
rc_plugin_run (rc_hook_service_start_now, applet);
started = svc_exec ("start", NULL);
if (ibsave)
unsetenv ("IN_BACKGROUND");
if (in_control ()) {
if (! started) {
if (rc_service_state (service, rc_service_wasinactive))
rc_mark_service (service, rc_service_inactive);
else {
rc_mark_service (service, rc_service_stopped);
if (rc_runlevel_starting ())
rc_mark_service (service, rc_service_failed);
}
rc_plugin_run (rc_hook_service_start_done, applet);
eerrorx ("ERROR: %s failed to start", applet);
}
rc_mark_service (service, rc_service_started);
unlink_mtime_test ();
rc_plugin_run (rc_hook_service_start_done, applet);
} else {
rc_plugin_run (rc_hook_service_start_done, applet);
if (rc_service_state (service, rc_service_inactive))
ewarnx ("WARNING: %s has started, but is inactive", applet);
else
ewarnx ("WARNING: %s not under our control, aborting", applet);
}
/* Now start any scheduled services */
rc_strlist_free (services);
services = rc_services_scheduled (service);
STRLIST_FOREACH (services, svc, i)
if (rc_service_state (svc, rc_service_stopped))
rc_start_service (svc);
rc_strlist_free (services);
services = NULL;
/* Do the same for any services we provide */
rc_strlist_free (types);
types = NULL;
rc_strlist_add (&types, "iprovide");
rc_strlist_free (svclist);
svclist = NULL;
rc_strlist_add (&svclist, applet);
rc_strlist_free (tmplist);
tmplist = rc_get_depends (deptree, types, svclist, softlevel, depoptions);
STRLIST_FOREACH (tmplist, svc2, j) {
rc_strlist_free (services);
services = rc_services_scheduled (svc2);
STRLIST_FOREACH (services, svc, i)
if (rc_service_state (svc, rc_service_stopped))
rc_start_service (svc);
}
hook_out = 0;
rc_plugin_run (rc_hook_service_start_out, applet);
}
static void svc_stop (bool deps)
{
bool stopped;
hook_out = rc_hook_service_stop_out;
if (rc_runlevel_stopping () &&
rc_service_state (service, rc_service_failed))
exit (EXIT_FAILURE);
if (rc_is_env ("IN_HOTPLUG", "1") || in_background)
if (! rc_service_state (service, rc_service_started) &&
! rc_service_state (service, rc_service_inactive))
exit (EXIT_FAILURE);
if (rc_service_state (service, rc_service_stopped)) {
ewarn ("WARNING: %s is already stopped", applet);
return;
} else if (rc_service_state (service, rc_service_stopping))
ewarnx ("WARNING: %s is already stopping", applet);
if (! rc_mark_service (service, rc_service_stopping))
eerrorx ("ERROR: %s has been stopped by something else", applet);
make_exclusive (service);
if (! rc_runlevel_stopping () &&
rc_service_in_runlevel (service, RC_LEVEL_BOOT))
ewarn ("WARNING: you are stopping a boot service");
if (deps && ! rc_service_state (service, rc_service_wasinactive)) {
int depoptions = RC_DEP_TRACE;
char *svc;
int i;
if (rc_is_env ("RC_DEPEND_STRICT", "yes"))
depoptions |= RC_DEP_STRICT;
if (rc_runlevel_stopping ())
depoptions |= RC_DEP_STOP;
if (! deptree && ((deptree = rc_load_deptree ()) == NULL))
eerrorx ("failed to load deptree");
rc_strlist_free (types);
types = NULL;
rc_strlist_add (&types, "needsme");
rc_strlist_free (svclist);
svclist = NULL;
rc_strlist_add (&svclist, applet);
rc_strlist_free (tmplist);
tmplist = NULL;
rc_strlist_free (services);
services = rc_get_depends (deptree, types, svclist,
softlevel, depoptions);
rc_strlist_reverse (services);
STRLIST_FOREACH (services, svc, i) {
if (rc_service_state (svc, rc_service_started) ||
rc_service_state (svc, rc_service_inactive))
{
rc_wait_service (svc);
if (rc_service_state (svc, rc_service_started) ||
rc_service_state (svc, rc_service_inactive))
{
pid_t pid = rc_stop_service (svc);
if (! rc_is_env ("RC_PARALLEL", "yes"))
rc_waitpid (pid);
rc_strlist_add (&tmplist, svc);
}
}
}
rc_strlist_free (services);
services = NULL;
STRLIST_FOREACH (tmplist, svc, i) {
if (rc_service_state (svc, rc_service_stopped))
continue;
/* We used to loop 3 times here - maybe re-do this if needed */
rc_wait_service (svc);
if (! rc_service_state (svc, rc_service_stopped)) {
if (rc_runlevel_stopping ()) {
/* If shutting down, we should stop even if a dependant failed */
if (softlevel &&
(strcmp (softlevel, RC_LEVEL_SHUTDOWN) == 0 ||
strcmp (softlevel, RC_LEVEL_REBOOT) == 0 ||
strcmp (softlevel, RC_LEVEL_SINGLE) == 0))
continue;
rc_mark_service (service, rc_service_failed);
}
eerrorx ("ERROR: cannot stop %s as %s is still up",
applet, svc);
}
}
rc_strlist_free (tmplist);
tmplist = NULL;
/* We now wait for other services that may use us and are stopping
This is important when a runlevel stops */
rc_strlist_add (&types, "usesme");
rc_strlist_add (&types, "ibefore");
services = rc_get_depends (deptree, types, svclist,
softlevel, depoptions);
STRLIST_FOREACH (services, svc, i) {
if (rc_service_state (svc, rc_service_stopped))
continue;
rc_wait_service (svc);
}
rc_strlist_free (services);
services = NULL;
rc_strlist_free (types);
types = NULL;
}
if (ibsave)
setenv ("IN_BACKGROUND", ibsave, 1);
rc_plugin_run (rc_hook_service_stop_now, applet);
stopped = svc_exec ("stop", NULL);
if (ibsave)
unsetenv ("IN_BACKGROUND");
if (! in_control ()) {
rc_plugin_run (rc_hook_service_stop_done, applet);
ewarnx ("WARNING: %s not under our control, aborting", applet);
}
if (! stopped) {
if (rc_service_state (service, rc_service_wasinactive))
rc_mark_service (service, rc_service_inactive);
else
rc_mark_service (service, rc_service_started);
rc_plugin_run (rc_hook_service_stop_done, applet);
eerrorx ("ERROR: %s failed to stop", applet);
}
if (in_background)
rc_mark_service (service, rc_service_inactive);
else
rc_mark_service (service, rc_service_stopped);
unlink_mtime_test ();
rc_plugin_run (rc_hook_service_stop_done, applet);
hook_out = 0;
rc_plugin_run (rc_hook_service_stop_out, applet);
}
static void svc_restart (bool deps)
{
/* 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) {
if (rc_service_state (service, rc_service_started) ||
rc_service_state (service, 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 (deps);
}
svc_start (deps);
start_services (restart_services);
rc_strlist_free (restart_services);
restart_services = NULL;
}
#include "_usage.h"
#define getoptstring "dDqsv" getoptstring_COMMON
static struct option longopts[] = {
{ "debug", 0, NULL, 'd'},
{ "ifstarted", 0, NULL, 's'},
{ "nodeps", 0, NULL, 'D'},
{ "quiet", 0, NULL, 'q'},
{ "verbose", 0, NULL, 'v'},
longopts_COMMON
{ NULL, 0, NULL, 0}
};
#include "_usage.c"
int runscript (int argc, char **argv)
{
int i;
bool deps = true;
bool doneone = false;
char pid[16];
int retval;
int opt;
char *svc;
/* We need the full path to the service */
if (*argv[1] == '/')
service = rc_xstrdup (argv[1]);
else {
char pwd[PATH_MAX];
if (! getcwd (pwd, PATH_MAX))
eerrorx ("getcwd: %s", strerror (errno));
service = rc_strcatpaths (pwd, argv[1], (char *) NULL);
}
applet = rc_xstrdup (basename (service));
atexit (cleanup);
/* Change dir to / to ensure all init scripts don't use stuff in pwd */
chdir ("/");
/* Show help if insufficient args */
if (argc < 3) {
execl (RCSCRIPT_HELP, RCSCRIPT_HELP, service, (char *) NULL);
eerrorx ("%s: failed to exec `" RCSCRIPT_HELP "': %s",
applet, strerror (errno));
}
#ifdef __linux__
/* coldplug events can trigger init scripts, but we don't want to run them
until after rc sysinit has completed so we punt them to the boot runlevel */
if (rc_exists ("/dev/.rcsysinit")) {
eerror ("%s: cannot run until sysvinit completes", applet);
if (mkdir ("/dev/.rcboot", 0755) != 0 && errno != EEXIST)
eerrorx ("%s: mkdir `/dev/.rcboot': %s", applet, strerror (errno));
tmp = rc_strcatpaths ("/dev/.rcboot", applet, (char *) NULL);
symlink (service, tmp);
exit (EXIT_FAILURE);
}
#endif
if ((softlevel = getenv ("RC_SOFTLEVEL")) == NULL) {
/* Ensure our environment is pure
Also, add our configuration to it */
tmplist = rc_make_env();
env = rc_filter_env ();
rc_strlist_join (&env, tmplist);
rc_strlist_free (tmplist);
tmplist = NULL;
if (env) {
char *p;
#ifdef __linux__
/* clearenv isn't portable, but there's no harm in using it
if we have it */
clearenv ();
#else
char *var;
/* No clearenv present here then.
We could manipulate environ directly ourselves, but it seems that
some kernels bitch about this according to the environ man pages
so we walk though environ and call unsetenv for each value. */
while (environ[0]) {
tmp = rc_xstrdup (environ[0]);
p = tmp;
var = strsep (&p, "=");
unsetenv (var);
free (tmp);
}
tmp = NULL;
#endif
STRLIST_FOREACH (env, p, i)
putenv (p);
/* We don't free our list as that would be null in environ */
}
softlevel = rc_get_runlevel ();
}
setenv ("RC_ELOG", service, 1);
setenv ("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 (pid, sizeof (pid), "%d", (int) getpid ());
setenv ("RC_RUNSCRIPT_PID", pid, 1);
/* eprefix is kinda klunky, but it works for our purposes */
if (rc_is_env ("RC_PARALLEL", "yes")) {
int l = 0;
int ll;
/* Get the longest service name */
services = rc_services_in_runlevel (NULL);
STRLIST_FOREACH (services, svc, i) {
ll = strlen (svc);
if (ll > l)
l = ll;
}
/* Make our prefix string */
prefix = rc_xmalloc (sizeof (char *) * l);
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
/* Punt the first arg as it's 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 'h':
execl (RCSCRIPT_HELP, RCSCRIPT_HELP, service, (char *) NULL);
eerrorx ("%s: failed to exec `" RCSCRIPT_HELP "': %s",
applet, strerror (errno));
case 's':
if (! rc_service_state (service, rc_service_started))
exit (EXIT_FAILURE);
break;
case 'C':
setenv ("RC_NOCOLOR", "yes", 1);
break;
case 'D':
deps = false;
break;
case 'q':
setenv ("RC_QUIET", "yes", 1);
break;
case 'v':
setenv ("RC_VERBOSE", "yes", 1);
break;
default:
usage (EXIT_FAILURE);
}
/* 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")) {
in_background = rc_is_env ("IN_BACKGROUND", "true");
ibsave = rc_xstrdup (getenv ("IN_BACKGROUND"));
unsetenv ("IN_BACKGROUND");
}
if (rc_is_env ("IN_HOTPLUG", "1")) {
if (! rc_is_env ("RC_HOTPLUG", "yes") || ! rc_allow_plug (applet))
eerrorx ("%s: not allowed to be hotplugged", applet);
}
/* Setup a signal handler */
signal (SIGHUP, handle_signal);
signal (SIGINT, handle_signal);
signal (SIGQUIT, handle_signal);
signal (SIGTERM, handle_signal);
signal (SIGCHLD, handle_signal);
/* Load our plugins */
rc_plugin_load ();
/* Now run each option */
retval = EXIT_SUCCESS;
while (optind < argc) {
optarg = argv[optind++];
/* Abort on a sighup here */
if (sighup)
exit (EXIT_FAILURE);
if (strcmp (optarg, "status") != 0 &&
strcmp (optarg, "help") != 0) {
/* Only root should be able to run us */
}
/* 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) {
svc_exec (optarg, NULL);
} else if (strcmp (optarg, "help") == 0) {
execl (RCSCRIPT_HELP, RCSCRIPT_HELP, service, "help", (char *) NULL);
eerrorx ("%s: failed to exec `" RCSCRIPT_HELP "': %s",
applet, strerror (errno));
} 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) {
int depoptions = RC_DEP_TRACE;
if (rc_is_env ("RC_DEPEND_STRICT", "yes"))
depoptions |= RC_DEP_STRICT;
if (! deptree && ((deptree = rc_load_deptree ()) == NULL))
eerrorx ("failed to load deptree");
rc_strlist_free (types);
types = NULL;
rc_strlist_add (&types, optarg);
rc_strlist_free (svclist);
svclist = NULL;
rc_strlist_add (&svclist, applet);
rc_strlist_free (services);
services = rc_get_depends (deptree, types, svclist,
softlevel, depoptions);
STRLIST_FOREACH (services, svc, i)
printf ("%s%s", i == 1 ? "" : " ", svc);
if (services)
printf ("\n");
} else if (strcmp (optarg, "status") == 0) {
rc_service_state_t r = svc_status (service);
retval = (int) r;
} else if (strcmp (optarg, "help") == 0) {
execl (RCSCRIPT_HELP, RCSCRIPT_HELP, service, "help", (char *) NULL);
eerrorx ("%s: failed to exec `" RCSCRIPT_HELP "': %s",
applet, strerror (errno));
} else {
if (geteuid () != 0)
eerrorx ("%s: root access required", applet);
if (strcmp (optarg, "conditionalrestart") == 0 ||
strcmp (optarg, "condrestart") == 0)
{
if (rc_service_state (service, rc_service_started))
svc_restart (deps);
} else if (strcmp (optarg, "restart") == 0) {
svc_restart (deps);
} else if (strcmp (optarg, "start") == 0) {
svc_start (deps);
} else if (strcmp (optarg, "stop") == 0) {
if (deps && in_background)
get_started_services ();
svc_stop (deps);
if (deps) {
if (! in_background &&
! rc_runlevel_stopping () &&
rc_service_state (service, rc_service_stopped))
uncoldplug ();
if (in_background &&
rc_service_state (service, rc_service_inactive))
{
int j;
STRLIST_FOREACH (restart_services, svc, j)
if (rc_service_state (svc, rc_service_stopped))
rc_schedule_start_service (service, svc);
}
}
} else if (strcmp (optarg, "zap") == 0) {
einfo ("Manually resetting %s to stopped state", applet);
rc_mark_service (applet, rc_service_stopped);
uncoldplug ();
} else
svc_exec (optarg, NULL);
/* We should ensure this list is empty after an action is done */
rc_strlist_free (restart_services);
restart_services = NULL;
}
if (! doneone) {
execl (RCSCRIPT_HELP, RCSCRIPT_HELP, service, (char *) NULL);
eerrorx ("%s: failed to exec `" RCSCRIPT_HELP "': %s",
applet, strerror (errno));
}
}
return (retval);
}