diff options
Diffstat (limited to 'src/rc/runscript.c')
-rw-r--r-- | src/rc/runscript.c | 1311 |
1 files changed, 1311 insertions, 0 deletions
diff --git a/src/rc/runscript.c b/src/rc/runscript.c new file mode 100644 index 00000000..1385bb02 --- /dev/null +++ b/src/rc/runscript.c @@ -0,0 +1,1311 @@ +/* + * runscript.c + * Handle launching of init scripts. + */ + +/* + * Copyright 2007 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/select.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.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> +#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 SELINUX_LIB RC_LIBDIR "/runscript_selinux.so" + +#define PREFIX_LOCK RC_SVCDIR "/prefix.lock" + +/* usecs to wait while we poll the fifo */ +#define WAIT_INTERVAL 20000000 + +/* max secs to wait until a service comes up */ +#define WAIT_MAX 300 + +#define ONE_SECOND 1000000000 + +static const 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 **tmplist = NULL; +static char **providelist = 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; + +static const char *const types_b[] = { "broken", NULL }; +static const char *const types_n[] = { "ineed", NULL }; +static const char *const types_nu[] = { "ineed", "iuse", NULL }; +static const char *const types_nua[] = { "ineed", "iuse", "iafter", NULL }; + +static const char *const types_m[] = { "needsme", NULL }; +static const char *const types_mua[] = { "needsme", "usesme", "beforeme", 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); + +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"); + 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 || ! 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 (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 (exists (cold) && unlink (cold) != 0) + eerror ("%s: unlink `%s': %s", applet, cold, strerror (errno)); + free (cold); +} + +static void start_services (char **list) { + char *svc; + int i; + rc_service_state_t 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) + { + STRLIST_FOREACH (list, svc, i) { + if (rc_service_state (svc) & RC_SERVICE_STOPPED) { + if (state & RC_SERVICE_INACTIVE || + state & RC_SERVICE_WASINACTIVE) + { + rc_service_schedule_start (service, svc); + ewarn ("WARNING: %s is scheduled to started when %s has started", + svc, applet); + } else + rc_service_start (svc); + } + } + } +} + +static void restore_state (void) +{ + rc_service_state_t state; + + if (rc_in_plugin || ! in_control ()) + 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); + } + + if (exclusive) + unlink (exclusive); + free (exclusive); + exclusive = NULL; +} + +static void cleanup (void) +{ + restore_state (); + + if (! rc_in_plugin) { + if (prefix_locked) + unlink (PREFIX_LOCK); + 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 (); + rc_deptree_free (deptree); + rc_strlist_free (services); + 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); + + rc_strlist_free (env); + + if (mtime_test) + { + if (! rc_in_plugin) + unlink (mtime_test); + free (mtime_test); + } + free (exclusive); + free (service); + free (prefix); + free (softlevel); +} + +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 = 0; + fd_set rset; + int s; + char *buffer; + 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, 1); + dup2 (slave_tty, 2); + if (slave_tty > 2) + close (slave_tty); + } + + 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_LIBDIR "/sh/runscript.sh", RC_LIBDIR "/sh/runscript.sh", + service, arg1, arg2, (char *) NULL); + eerror ("%s: exec `" RC_LIBDIR "/sh/runscript.sh': %s", + service, strerror (errno)); + _exit (EXIT_FAILURE); + } + } + + selfd = MAX (master_tty, signal_pipe[0]) + 1; + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + 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) { + if (master_tty >= 0 && FD_ISSET (master_tty, &rset)) { + bytes = read (master_tty, buffer, RC_LINEBUFFER); + write_prefix (buffer, bytes, &prefixed); + } + + /* Only SIGCHLD signals come down this pipe */ + if (FD_ISSET (signal_pipe[0], &rset)) + break; + } + } + + free (buffer); + 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 bool svc_wait (rc_depinfo_t *depinfo, const char *svc) +{ + char *s; + char *fifo; + struct timespec ts; + int nloops = WAIT_MAX * (ONE_SECOND / WAIT_INTERVAL); + bool retval = false; + bool forever = false; + char **keywords = NULL; + int i; + + if (! service) + return (false); + + /* Some services don't have a timeout, like checkroot and checkfs */ + keywords = rc_deptree_depend (depinfo, svc, "keywords"); + STRLIST_FOREACH (keywords, s, i) { + if (strcmp (s, "notimeout") == 0) { + forever = true; + break; + } + } + rc_strlist_free (keywords); + + fifo = rc_strcatpaths (RC_SVCDIR, "exclusive", basename_c (svc), (char *) NULL); + ts.tv_sec = 0; + ts.tv_nsec = WAIT_INTERVAL; + + while (nloops) { + if (! exists (fifo)) { + retval = true; + break; + } + + if (nanosleep (&ts, NULL) == -1) { + if (errno != EINTR) + break; + } + + if (! forever) + nloops --; + } + + if (! exists (fifo)) + retval = true; + free (fifo); + return (retval); +} + +static rc_service_state_t svc_status () +{ + char status[10]; + int (*e) (const char *fmt, ...) = &einfo; + + rc_service_state_t state = rc_service_state (service); + + if (state & RC_SERVICE_STOPPING) { + snprintf (status, sizeof (status), "stopping"); + e = &ewarn; + } else if (state & RC_SERVICE_STARTING) { + snprintf (status, sizeof (status), "starting"); + e = &ewarn; + } else if (state & RC_SERVICE_INACTIVE) { + snprintf (status, sizeof (status), "inactive"); + e = &ewarn; + } else if (state & RC_SERVICE_STARTED) { + if (geteuid () == 0 && rc_service_daemons_crashed (service)) { + snprintf (status, sizeof (status), "crashed"); + e = &eerror; + } else + snprintf (status, sizeof (status), "started"); + } else + snprintf (status, sizeof (status), "stopped"); + + e ("status: %s", status); + return (state); +} + +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 = xmalloc (sizeof (char) * i); + snprintf (mtime_test, i, "%s.%d", path, getpid ()); + free (path); + + if (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 () +{ + 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); + rc_strlist_join (&restart_services, tmplist); + 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; + const char *const svcl[] = { applet, NULL }; + rc_service_state_t state; + + state = rc_service_state (service); + + if (rc_yesno (getenv ("IN_HOTPLUG")) || in_background) { + if (! state & RC_SERVICE_INACTIVE && + ! state & RC_SERVICE_STOPPED) + exit (EXIT_FAILURE); + background = true; + } + + if (state & RC_SERVICE_STARTED) { + ewarn ("WARNING: %s has already been started", applet); + return; + } else if (state & RC_SERVICE_STARTING) + ewarnx ("WARNING: %s is already starting", applet); + else if (state & RC_SERVICE_STOPPING) + ewarnx ("WARNING: %s is stopping", applet); + else if (state & RC_SERVICE_INACTIVE && ! background) + ewarnx ("WARNING: %s has already started, but is inactive", applet); + + if (! rc_service_mark (service, RC_SERVICE_STARTING)) + eerrorx ("ERROR: %s has been started by something else", applet); + + make_exclusive (service); + + hook_out = RC_HOOK_SERVICE_START_OUT; + rc_plugin_run (RC_HOOK_SERVICE_START_IN, applet); + + if (rc_conf_yesno ("rc_depend_strict")) + depoptions |= RC_DEP_STRICT; + + if (rc_runlevel_starting ()) + depoptions |= RC_DEP_START; + + if (deps) { + if (! deptree && ((deptree = _rc_deptree_load (NULL)) == NULL)) + eerrorx ("failed to load deptree"); + + rc_strlist_free (services); + services = rc_deptree_depends (deptree, types_b, svcl, 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 (need_services); + need_services = rc_deptree_depends (deptree, types_n, svcl, + softlevel, depoptions); + + rc_strlist_free (use_services); + use_services = rc_deptree_depends (deptree, types_nu, svcl, + softlevel, depoptions); + + if (! rc_runlevel_starting ()) { + STRLIST_FOREACH (use_services, svc, i) + if (rc_service_state (svc) & RC_SERVICE_STOPPED) { + pid_t pid = rc_service_start (svc); + if (! rc_conf_yesno ("rc_parallel")) + rc_waitpid (pid); + } + } + + /* Now wait for them to start */ + services = rc_deptree_depends (deptree, types_nua, svcl, + softlevel, depoptions); + + /* We use tmplist to hold our scheduled by list */ + rc_strlist_free (tmplist); + tmplist = NULL; + + STRLIST_FOREACH (services, svc, i) { + rc_service_state_t svcs = rc_service_state (svc); + if (svcs & RC_SERVICE_STARTED) + continue; + + /* Don't wait for services which went inactive but are now in + * starting state which we are after */ + if (svcs & RC_SERVICE_STARTING && + svcs & RC_SERVICE_WASINACTIVE) { + bool use = false; + STRLIST_FOREACH (use_services, svc2, j) + if (strcmp (svc, svc2) == 0) { + use = true; + break; + } + if (! use) + continue; + } + + if (! svc_wait (deptree, svc)) + eerror ("%s: timed out waiting for %s", applet, svc); + if ((svcs = rc_service_state (svc)) & RC_SERVICE_STARTED) + continue; + + STRLIST_FOREACH (need_services, svc2, j) + if (strcmp (svc, svc2) == 0) { + if (svcs & RC_SERVICE_INACTIVE || + svcs & 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_service_mark (service, RC_SERVICE_STOPPED); + unlink_mtime_test (); + + STRLIST_FOREACH (tmplist, svc, i) { + rc_service_schedule_start (svc, service); + rc_strlist_free (providelist); + providelist = rc_deptree_depend (deptree, "iprovide", svc); + STRLIST_FOREACH (providelist, svc2, j) + rc_service_schedule_start (svc2, service); + + len += strlen (svc) + 2; + n++; + } + + len += 5; + tmp = 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; + } + + 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); + if (ibsave) + unsetenv ("IN_BACKGROUND"); + + if (in_control ()) { + 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", applet); + else + ewarnx ("WARNING: %s not under our control, aborting", applet); + } + + rc_service_mark (service, RC_SERVICE_STARTED); + unlink_mtime_test (); + hook_out = RC_HOOK_SERVICE_START_OUT; + rc_plugin_run (RC_HOOK_SERVICE_START_DONE, applet); + + if (exclusive) + unlink (exclusive); + + /* 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_service_start (svc); + rc_strlist_free (services); + services = NULL; + + /* Do the same for any services we provide */ + rc_strlist_free (tmplist); + tmplist = rc_deptree_depend (deptree, "iprovide", applet); + + 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_service_start (svc); + } + + hook_out = 0; + rc_plugin_run (RC_HOOK_SERVICE_START_OUT, applet); +} + +static void svc_stop (bool deps) +{ + bool stopped; + const char *const svcl[] = { applet, NULL }; + + rc_service_state_t state = rc_service_state (service); + + if (rc_runlevel_stopping () && + state & RC_SERVICE_FAILED) + exit (EXIT_FAILURE); + + if (rc_yesno (getenv ("IN_HOTPLUG")) || in_background) + if (! (state & RC_SERVICE_STARTED) && + ! (state & RC_SERVICE_INACTIVE)) + exit (EXIT_FAILURE); + + if (state & RC_SERVICE_STOPPED) { + ewarn ("WARNING: %s is already stopped", applet); + return; + } else if (state & RC_SERVICE_STOPPING) + ewarnx ("WARNING: %s is already stopping", applet); + + if (! rc_service_mark (service, RC_SERVICE_STOPPING)) + eerrorx ("ERROR: %s has been stopped by something else", applet); + + make_exclusive (service); + + hook_out = RC_HOOK_SERVICE_STOP_OUT; + rc_plugin_run (RC_HOOK_SERVICE_STOP_IN, applet); + + if (! rc_runlevel_stopping () && + rc_service_in_runlevel (service, RC_LEVEL_BOOT)) + ewarn ("WARNING: you are stopping a boot service"); + + if (deps && ! (state & RC_SERVICE_WASINACTIVE)) { + int depoptions = RC_DEP_TRACE; + char *svc; + int i; + + if (rc_conf_yesno ("rc_depend_strict")) + depoptions |= RC_DEP_STRICT; + + if (rc_runlevel_stopping ()) + depoptions |= RC_DEP_STOP; + + if (! deptree && ((deptree = _rc_deptree_load (NULL)) == NULL)) + eerrorx ("failed to load deptree"); + + rc_strlist_free (tmplist); + tmplist = NULL; + rc_strlist_free (services); + services = rc_deptree_depends (deptree, types_m, svcl, + softlevel, depoptions); + rc_strlist_reverse (services); + STRLIST_FOREACH (services, svc, i) { + rc_service_state_t svcs = rc_service_state (svc); + if (svcs & RC_SERVICE_STARTED || + svcs & RC_SERVICE_INACTIVE) + { + svc_wait (deptree, svc); + svcs = rc_service_state (svc); + if (svcs & RC_SERVICE_STARTED || + svcs & RC_SERVICE_INACTIVE) + { + pid_t pid = rc_service_stop (svc); + if (! rc_conf_yesno ("rc_parallel")) + 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 */ + svc_wait (deptree, 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_service_mark (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 */ + services = rc_deptree_depends (deptree, types_mua, svcl, + softlevel, depoptions); + STRLIST_FOREACH (services, svc, i) { + if (rc_service_state (svc) & RC_SERVICE_STOPPED) + continue; + svc_wait (deptree, svc); + } + + rc_strlist_free (services); + services = NULL; + } + + 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); + if (ibsave) + unsetenv ("IN_BACKGROUND"); + + if (! in_control ()) + ewarnx ("WARNING: %s not under our control, aborting", applet); + + 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); + + unlink_mtime_test (); + hook_out = RC_HOOK_SERVICE_STOP_OUT; + rc_plugin_run (RC_HOOK_SERVICE_STOP_DONE, applet); + if (exclusive) + unlink (exclusive); + 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) { + rc_service_state_t 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 (deps); + } + + svc_start (deps); + start_services (restart_services); + rc_strlist_free (restart_services); + restart_services = NULL; +} + +#include "_usage.h" +#define getoptstring "dDsv" getoptstring_COMMON +#define extraopts "stop | start | restart | describe | zap" +static struct option longopts[] = { + { "debug", 0, NULL, 'd'}, + { "ifstarted", 0, NULL, 's'}, + { "nodeps", 0, NULL, 'D'}, + longopts_COMMON +}; +static const char * const longopts_help[] = { + "set xtrace when running the script", + "only run commands when started", + "ignore dependencies", + longopts_help_COMMON +}; +#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; + + /* Show help if insufficient args */ + if (argc < 2 || ! exists (argv[1])) { + fprintf (stderr, "runscript is not meant to be to run directly\n"); + exit (EXIT_FAILURE); + } + + applet = basename_c (argv[1]); + if (argc < 3) + usage (EXIT_FAILURE); + + if (*argv[1] == '/') + service = xstrdup (argv[1]); + else { + char d[PATH_MAX]; + getcwd (d, sizeof (d)); + i = strlen (d) + strlen (argv[1]) + 2; + service = xmalloc (sizeof (char) * i); + snprintf (service, i, "%s/%s", d, argv[1]); + } + + atexit (cleanup); + + /* Change dir to / to ensure all init scripts don't use stuff in pwd */ + chdir ("/"); + +#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 (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 = xstrdup (getenv ("RC_SOFTLEVEL"))) == NULL) { + /* Ensure our environment is pure + Also, add our configuration to it */ + env = env_filter (); + tmplist = env_config (); + 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 = 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_runlevel_get (); + } + + setenv ("EINFO_LOG", 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_conf_yesno ("rc_parallel")) { + 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 = 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 's': + if (! (rc_service_state (service) & RC_SERVICE_STARTED)) + exit (EXIT_FAILURE); + break; + case 'D': + deps = false; + break; + case_RC_COMMON_GETOPT + } + + /* 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 (! rc_conf_yesno ("rc_hotplug") || ! rc_service_plugable (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 || + strcmp (optarg, "help") == 0) + { + char *save = prefix; + + eprefix (NULL); + prefix = NULL; + svc_exec (optarg, NULL); + eprefix (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) { + int depoptions = RC_DEP_TRACE; + const char *t[] = { optarg, NULL }; + const char *s[] = { applet, NULL }; + + if (rc_conf_yesno ("rc_depend_strict")) + depoptions |= RC_DEP_STRICT; + + if (! deptree && ((deptree = _rc_deptree_load (NULL)) == NULL)) + eerrorx ("failed to load deptree"); + + rc_strlist_free (services); + services = rc_deptree_depends (deptree, t, s, 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; + if (retval & RC_SERVICE_STARTED) + retval = 0; + } 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_service_schedule_start (service, svc); + } + } + } else if (strcmp (optarg, "zap") == 0) { + einfo ("Manually resetting %s to stopped state", applet); + rc_service_mark (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) + usage (EXIT_FAILURE); + } + + return (retval); +} |