/*
 * runscript.c
 * Handle launching of init scripts.
 */

/*
 * Copyright (c) 2007-2009 Roy Marples <roy@marples.name>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <ctype.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <getopt.h>
#include <libgen.h>
#include <limits.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>

#ifdef __linux__
#  include <pty.h>
#elif defined(__NetBSD__) || defined(__OpenBSD__)
#  include <util.h>
#else
#  include <libutil.h>
#endif

#include "builtins.h"
#include "einfo.h"
#include "rc.h"
#include "rc-misc.h"
#include "rc-plugin.h"

#define SELINUX_LIB     RC_LIBDIR "/runscript_selinux.so"

#define PREFIX_LOCK	RC_SVCDIR "/prefix.lock"

#define WAIT_INTERVAL	20000000	/* usecs to poll the lock file */
#define WAIT_TIMEOUT	60		/* seconds until we timeout */
#define WARN_TIMEOUT	10		/* warn about this every N seconds */

static const char *applet;
static char *service, *runlevel, *ibsave, *prefix;;
static RC_DEPTREE *deptree;
static RC_STRINGLIST *applet_list, *services, *tmplist;
static RC_STRINGLIST *restart_services, *need_services, *use_services;
static RC_HOOK hook_out;
static int exclusive_fd = -1, master_tty = -1;
static bool sighup, in_background, deps, dry_run;
static pid_t service_pid;
static int signal_pipe[2] = { -1, -1 };

static RC_STRINGLIST *types_b, *types_n, *types_nu, *types_nua, *types_m;
static RC_STRINGLIST *types_mua = NULL;

#ifdef __linux__
static void (*selinux_run_init_old)(void);
static void (*selinux_run_init_new)(int argc, char **argv);

static void
setup_selinux(int argc, char **argv)
{
	void *lib_handle = NULL;

	if (! exists(SELINUX_LIB))
		return;

	lib_handle = dlopen(SELINUX_LIB, RTLD_NOW | RTLD_GLOBAL);
	if (! lib_handle) {
		eerror("dlopen: %s", dlerror());
		return;
	}

	selinux_run_init_old = (void (*)(void))
	    dlfunc(lib_handle, "selinux_runscript");
	selinux_run_init_new = (void (*)(int, char **))
	    dlfunc(lib_handle, "selinux_runscript2");

	/* Use new run_init if it exists, else fall back to old */
	if (selinux_run_init_new)
		selinux_run_init_new(argc, argv);
	else if (selinux_run_init_old)
		selinux_run_init_old();
	else
		/* This shouldnt happen... probably corrupt lib */
		eerrorx("run_init is missing from runscript_selinux.so!");

	dlclose(lib_handle);
}
#endif

static void
handle_signal(int sig)
{
	int serrno = errno;
	char signame[10] = { '\0' };
	struct winsize ws;

	switch (sig) {
	case SIGHUP:
		sighup = true;
		break;

	case SIGCHLD:
		if (signal_pipe[1] > -1) {
			if (write(signal_pipe[1], &sig, sizeof(sig)) == -1)
				eerror("%s: send: %s",
				    service, strerror(errno));
		} else
			rc_waitpid(-1);
		break;

	case SIGWINCH:
		if (master_tty >= 0) {
			ioctl(fileno(stdout), TIOCGWINSZ, &ws);
			ioctl(master_tty, TIOCSWINSZ, &ws);
		}
		break;

	case SIGINT:
		if (!signame[0])
			snprintf(signame, sizeof(signame), "SIGINT");
		/* FALLTHROUGH */
	case SIGTERM:
		if (!signame[0])
			snprintf(signame, sizeof(signame), "SIGTERM");
		/* FALLTHROUGH */
	case SIGQUIT:
		if (!signame[0])
			snprintf(signame, sizeof(signame), "SIGQUIT");
		/* Send the signal to our children too */
		if (service_pid > 0)
			kill(service_pid, sig);
		eerrorx("%s: caught %s, aborting", applet, signame);
		/* NOTREACHED */

	default:
		eerror("%s: caught unknown signal %d", applet, sig);
	}

	/* Restore errno */
	errno = serrno;
}

static void
unhotplug()
{
	char file[PATH_MAX];

	snprintf(file, sizeof(file), RC_SVCDIR "/hotplugged/%s", applet);
	if (exists(file) && unlink(file) != 0)
		eerror("%s: unlink `%s': %s", applet, file, strerror(errno));
}

static void
start_services(RC_STRINGLIST *list)
{
	RC_STRING *svc;
	RC_SERVICE state = rc_service_state (service);

	if (!list)
		return;

	if (state & RC_SERVICE_INACTIVE ||
	    state & RC_SERVICE_WASINACTIVE ||
	    state & RC_SERVICE_STARTING ||
	    state & RC_SERVICE_STARTED)
	{
		TAILQ_FOREACH(svc, list, entries) {
			if (!(rc_service_state(svc->value) &
				RC_SERVICE_STOPPED))
				continue;
			if (state & RC_SERVICE_INACTIVE ||
			    state & RC_SERVICE_WASINACTIVE)
			{
				rc_service_schedule_start(service,
				    svc->value);
				ewarn("WARNING: %s is scheduled to started"
				    " when %s has started",
				    svc->value, applet);
			} else
				service_start(svc->value);
		}
	}
}

static void
restore_state(void)
{
	RC_SERVICE state;

	if (rc_in_plugin || exclusive_fd == -1)
		return;
	state = rc_service_state(applet);
	if (state & RC_SERVICE_STOPPING) {
		if (state & RC_SERVICE_WASINACTIVE)
			rc_service_mark(applet, RC_SERVICE_INACTIVE);
		else
			rc_service_mark(applet, RC_SERVICE_STARTED);
		if (rc_runlevel_stopping())
			rc_service_mark(applet, RC_SERVICE_FAILED);
	} else if (state & RC_SERVICE_STARTING) {
		if (state & RC_SERVICE_WASINACTIVE)
			rc_service_mark(applet, RC_SERVICE_INACTIVE);
		else
			rc_service_mark(applet, RC_SERVICE_STOPPED);
		if (rc_runlevel_starting())
			rc_service_mark(applet, RC_SERVICE_FAILED);
	}
	exclusive_fd = svc_unlock(applet, exclusive_fd);
}

static void
cleanup(void)
{
	restore_state();

	if (!rc_in_plugin) {
		if (hook_out) {
			rc_plugin_run(hook_out, applet);
			if (hook_out == RC_HOOK_SERVICE_START_DONE)
				rc_plugin_run(RC_HOOK_SERVICE_START_OUT,
				    applet);
			else if (hook_out == RC_HOOK_SERVICE_STOP_DONE)
				rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT,
				    applet);
		}

		if (restart_services)
			start_services(restart_services);
	}

	rc_plugin_unload();

#ifdef DEBUG_MEMORY
	rc_stringlist_free(types_b);
	rc_stringlist_free(types_n);
	rc_stringlist_free(types_nu);
	rc_stringlist_free(types_nua);
	rc_stringlist_free(types_m);
	rc_stringlist_free(types_mua);
	rc_deptree_free(deptree);
	rc_stringlist_free(restart_services);
	rc_stringlist_free(need_services);
	rc_stringlist_free(use_services);
	rc_stringlist_free(services);
	rc_stringlist_free(applet_list);
	rc_stringlist_free(tmplist);
	free(ibsave);
	free(service);
	free(prefix);
	free(runlevel);
#endif
}

static int
write_prefix(const char *buffer, size_t bytes, bool *prefixed)
{
	size_t i, j;
	const char *ec = ecolor(ECOLOR_HILITE);
	const char *ec_normal = ecolor(ECOLOR_NORMAL);
	ssize_t ret = 0;
	int fd = fileno(stdout), lock_fd = -1;

	/* Spin until we lock the prefix */
	for (;;) {
		lock_fd = open(PREFIX_LOCK, O_WRONLY | O_CREAT, 0664);
		if (lock_fd != -1)
			if (flock(lock_fd, LOCK_EX) == 0)
				break;
		close(lock_fd);
	}

	for (i = 0; i < bytes; i++) {
		/* We don't prefix eend calls (cursor up) */
		if (buffer[i] == '\033' && !*prefixed) {
			for (j = i + 1; j < bytes; j++) {
				if (buffer[j] == 'A')
					*prefixed = true;
				if (isalpha((unsigned int)buffer[j]))
					break;
			}
		}

		if (!*prefixed) {
			ret += write(fd, ec, strlen(ec));
			ret += write(fd, prefix, strlen(prefix));
			ret += write(fd, ec_normal, strlen(ec_normal));
			ret += write(fd, "|", 1);
			*prefixed = true;
		}

		if (buffer[i] == '\n')
			*prefixed = false;
		ret += write(fd, buffer + i, 1);
	}

	/* Release the lock */
	close(lock_fd);
	return ret;
}

static int
svc_exec(const char *arg1, const char *arg2)
{
	int ret, fdout = fileno(stdout);
	struct termios tt;
	struct winsize ws;
	int i;
	int flags = 0;
	struct pollfd fd[2];
	int s;
	char *buffer;
	size_t bytes;
	bool prefixed = false;
	int slave_tty;
	sigset_t sigchldmask;
	sigset_t oldmask;

	/* Setup our signal pipe */
	if (pipe(signal_pipe) == -1)
		eerrorx("%s: pipe: %s", service, applet);
	for (i = 0; i < 2; i++)
		if ((flags = fcntl(signal_pipe[i], F_GETFD, 0) == -1 ||
			fcntl(signal_pipe[i], F_SETFD, flags | FD_CLOEXEC) == -1))
			eerrorx("%s: fcntl: %s", service, strerror(errno));

	/* Open a pty for our prefixed output
	 * We do this instead of mapping pipes to stdout, stderr so that
	 * programs can tell if they're attached to a tty or not.
	 * The only loss is that we can no longer tell the difference
	 * between the childs stdout or stderr */
	master_tty = slave_tty = -1;
	if (prefix && isatty(fdout)) {
		tcgetattr(fdout, &tt);
		ioctl(fdout, TIOCGWINSZ, &ws);

		/* If the below call fails due to not enough ptys then we don't
		 * prefix the output, but we still work */
		openpty(&master_tty, &slave_tty, NULL, &tt, &ws);
		if (master_tty >= 0 &&
		    (flags = fcntl(master_tty, F_GETFD, 0)) == 0)
			fcntl(master_tty, F_SETFD, flags | FD_CLOEXEC);

		if (slave_tty >=0 &&
		    (flags = fcntl(slave_tty, F_GETFD, 0)) == 0)
			fcntl(slave_tty, F_SETFD, flags | FD_CLOEXEC);
	}

	service_pid = fork();
	if (service_pid == -1)
		eerrorx("%s: fork: %s", service, strerror(errno));
	if (service_pid == 0) {
		if (slave_tty >= 0) {
			dup2(slave_tty, STDOUT_FILENO);
			dup2(slave_tty, STDERR_FILENO);
		}

		if (exists(RC_SVCDIR "/runscript.sh")) {
			execl(RC_SVCDIR "/runscript.sh",
			    RC_SVCDIR "/runscript.sh",
			    service, arg1, arg2, (char *) NULL);
			eerror("%s: exec `" RC_SVCDIR "/runscript.sh': %s",
			    service, strerror(errno));
			_exit(EXIT_FAILURE);
		} else {
			execl(RC_LIBEXECDIR "/sh/runscript.sh",
			    RC_LIBEXECDIR "/sh/runscript.sh",
			    service, arg1, arg2, (char *) NULL);
			eerror("%s: exec `" RC_LIBEXECDIR "/sh/runscript.sh': %s",
			    service, strerror(errno));
			_exit(EXIT_FAILURE);
		}
	}

	buffer = xmalloc(sizeof(char) * BUFSIZ);
	fd[0].fd = signal_pipe[0];
	fd[0].events = fd[1].events = POLLIN;
	fd[0].revents = fd[1].revents = 0;
	if (master_tty >= 0) {
		fd[1].fd = master_tty;
		fd[1].events = POLLIN;
		fd[1].revents = 0;
	}

	for (;;) {
		if ((s = poll(fd, master_tty >= 0 ? 2 : 1, -1)) == -1) {
			if (errno != EINTR) {
				eerror("%s: poll: %s",
				    service, strerror(errno));
				break;
			}
		}

		if (s > 0) {
			if (fd[1].revents & (POLLIN | POLLHUP)) {
				bytes = read(master_tty, buffer, BUFSIZ);
				write_prefix(buffer, bytes, &prefixed);
			}

			/* Only SIGCHLD signals come down this pipe */
			if (fd[0].revents & (POLLIN | POLLHUP))
				break;
		}
	}

	free(buffer);

	sigemptyset (&sigchldmask);
	sigaddset (&sigchldmask, SIGCHLD);
	sigprocmask (SIG_BLOCK, &sigchldmask, &oldmask);

	close(signal_pipe[0]);
	close(signal_pipe[1]);
	signal_pipe[0] = signal_pipe[1] = -1;

	sigprocmask (SIG_SETMASK, &oldmask, NULL);

	if (master_tty >= 0) {
		/* Why did we do this? */
		/* signal (SIGWINCH, SIG_IGN); */
		close(master_tty);
		master_tty = -1;
	}

	ret = rc_waitpid(service_pid);
	ret = WEXITSTATUS(ret);
	if (ret != 0 && errno == ECHILD)
		/* killall5 -9 could cause this */
		ret = 0;
	service_pid = 0;

	return ret;
}

static bool
svc_wait(const char *svc)
{
	char file[PATH_MAX];
	int fd;
	bool forever = false;
	RC_STRINGLIST *keywords;
	struct timespec interval, timeout, warn;

	/* Some services don't have a timeout, like fsck */
	keywords = rc_deptree_depend(deptree, svc, "keyword");
	if (rc_stringlist_find(keywords, "-timeout") ||
	    rc_stringlist_find(keywords, "notimeout"))
		forever = true;
	rc_stringlist_free(keywords);

	snprintf(file, sizeof(file), RC_SVCDIR "/exclusive/%s",
	    basename_c(svc));

	interval.tv_sec = 0;
	interval.tv_nsec = WAIT_INTERVAL;
	timeout.tv_sec = WAIT_TIMEOUT;
	timeout.tv_nsec = 0;
	warn.tv_sec = WARN_TIMEOUT;
	warn.tv_nsec = 0;
	for (;;) {
		fd = open(file, O_RDONLY | O_NONBLOCK);
		if (fd != -1) {
			if (flock(fd, LOCK_SH | LOCK_NB) == 0) {
				close(fd);
				return true;
			}
			close(fd);
		}
		if (errno == ENOENT)
			return true;
		if (errno != EWOULDBLOCK)
			eerrorx("%s: open `%s': %s", applet, file,
			    strerror(errno));
		if (nanosleep(&interval, NULL) == -1) {
			if (errno != EINTR)
				return false;
		}
		if (!forever) {
			timespecsub(&timeout, &interval, &timeout);
			if (timeout.tv_sec <= 0)
				return false;
			timespecsub(&warn, &interval, &warn);
			if (warn.tv_sec <= 0) {
				ewarn("%s: waiting for %s (%d seconds)",
				    applet, svc, (int)timeout.tv_sec);
				warn.tv_sec = WARN_TIMEOUT;
				warn.tv_nsec = 0;
			}
		}
	}
	return false;
}

static void
get_started_services(void)
{
	RC_STRINGLIST *tmp = rc_services_in_state(RC_SERVICE_INACTIVE);

	rc_stringlist_free(restart_services);
	restart_services = rc_services_in_state(RC_SERVICE_STARTED);
	TAILQ_CONCAT(restart_services, tmp, entries);
	free(tmp);
}

static void
setup_types(void)
{
	types_b = rc_stringlist_new();
	rc_stringlist_add(types_b, "broken");

	types_n = rc_stringlist_new();
	rc_stringlist_add(types_n, "ineed");

	types_nu = rc_stringlist_new();
	rc_stringlist_add(types_nu, "ineed");
	rc_stringlist_add(types_nu, "iuse");

	types_nua = rc_stringlist_new();
	rc_stringlist_add(types_nua, "ineed");
	rc_stringlist_add(types_nua, "iuse");
	rc_stringlist_add(types_nua, "iafter");

	types_m = rc_stringlist_new();
	rc_stringlist_add(types_m, "needsme");

	types_mua = rc_stringlist_new();
	rc_stringlist_add(types_mua, "needsme");
	rc_stringlist_add(types_mua, "usesme");
	rc_stringlist_add(types_mua, "beforeme");
}

static void
svc_start_check(void)
{
	RC_SERVICE state;

	state = rc_service_state(service);

	if (in_background) {
		if (!(state & (RC_SERVICE_INACTIVE | RC_SERVICE_STOPPED)))
			exit(EXIT_FAILURE);
		if (rc_yesno(getenv("IN_HOTPLUG")))
			rc_service_mark(service, RC_SERVICE_HOTPLUGGED);
		if (strcmp(runlevel, RC_LEVEL_SYSINIT) == 0)
			ewarnx("WARNING: %s will be started in the"
			    " next runlevel", applet);
	}

	if (exclusive_fd == -1)
		exclusive_fd = svc_lock(applet);
	if (exclusive_fd == -1) {
		if (errno == EACCES)
			eerrorx("%s: superuser access required", applet);
		if (state & RC_SERVICE_STOPPING)
			ewarnx("WARNING: %s is stopping", applet);
		else
			ewarnx("WARNING: %s is already starting", applet);
	}
	fcntl(exclusive_fd, F_SETFD,
	    fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC);

	if (state & RC_SERVICE_STARTED) {
		ewarn("WARNING: %s has already been started", applet);
		exit(EXIT_SUCCESS);
	}
	else if (state & RC_SERVICE_INACTIVE && !in_background)
		ewarnx("WARNING: %s has already started, but is inactive",
		    applet);

	rc_service_mark(service, RC_SERVICE_STARTING);
	hook_out = RC_HOOK_SERVICE_START_OUT;
	rc_plugin_run(RC_HOOK_SERVICE_START_IN, applet);
}

static void
svc_start_deps(void)
{
	bool first;
	RC_STRING *svc, *svc2;
	RC_SERVICE state;
	int depoptions = RC_DEP_TRACE, n;
	size_t len;
	char *p, *tmp;
	pid_t pid;

	errno = 0;
	if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT)
		depoptions |= RC_DEP_STRICT;

	if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL))
		eerrorx("failed to load deptree");
	if (!types_b)
		setup_types();

	services = rc_deptree_depends(deptree, types_b, applet_list,
	    runlevel, 0);
	if (TAILQ_FIRST(services)) {
		eerrorn("ERROR: %s needs service(s) ", applet);
		first = true;
		TAILQ_FOREACH(svc, services, entries) {
			if (first)
				first = false;
			else
				fprintf(stderr, ", ");
			fprintf(stderr, "%s", svc->value);
		}
		fprintf(stderr, "\n");
		exit(EXIT_FAILURE);
	}
	rc_stringlist_free(services);
	services = NULL;

	need_services = rc_deptree_depends(deptree, types_n,
	    applet_list, runlevel, depoptions);
	use_services = rc_deptree_depends(deptree, types_nu,
	    applet_list, runlevel, depoptions);

	if (!rc_runlevel_starting()) {
		TAILQ_FOREACH(svc, use_services, entries) {
			state = rc_service_state(svc->value);
			/* Don't stop failed services again.
			 * If you remove this check, ensure that the
			 * exclusive file isn't created. */
			if (state & RC_SERVICE_FAILED &&
			    rc_runlevel_starting())
				continue;
			if (state & RC_SERVICE_STOPPED) {
				if (dry_run) {
					printf(" %s", svc->value);
					continue;
				}
				pid = service_start(svc->value);
				if (!rc_conf_yesno("rc_parallel"))
					rc_waitpid(pid);
			}
		}
	}

	if (dry_run)
		return;

	/* Now wait for them to start */
	services = rc_deptree_depends(deptree, types_nua, applet_list,
	    runlevel, depoptions);
	/* We use tmplist to hold our scheduled by list */
	tmplist = rc_stringlist_new();
	TAILQ_FOREACH(svc, services, entries) {
		state = rc_service_state(svc->value);
		if (state & RC_SERVICE_STARTED)
			continue;

		/* Don't wait for services which went inactive but are
		 * now in starting state which we are after */
		if (state & RC_SERVICE_STARTING &&
		    state & RC_SERVICE_WASINACTIVE)
		{
			if (!rc_stringlist_find(need_services, svc->value) &&
			    !rc_stringlist_find(use_services, svc->value))
				continue;
		}

		if (!svc_wait(svc->value))
			eerror("%s: timed out waiting for %s",
			    applet, svc->value);
		state = rc_service_state(svc->value);
		if (state & RC_SERVICE_STARTED)
			continue;
		if (rc_stringlist_find(need_services, svc->value)) {
			if (state & RC_SERVICE_INACTIVE ||
			    state & RC_SERVICE_WASINACTIVE)
			{
				rc_stringlist_add(tmplist, svc->value);
			} else if (!TAILQ_FIRST(tmplist))
				eerrorx("ERROR: cannot start %s as"
				    " %s would not start",
				    applet, svc->value);
		}
	}

	if (TAILQ_FIRST(tmplist)) {
		/* Set the state now, then unlink our exclusive so that
		   our scheduled list is preserved */
		rc_service_mark(service, RC_SERVICE_STOPPED);

		rc_stringlist_free(use_services);
		use_services = NULL;
		len = 0;
		n = 0;
		TAILQ_FOREACH(svc, tmplist, entries) {
			rc_service_schedule_start(svc->value, service);
			use_services = rc_deptree_depend(deptree,
			    "iprovide", svc->value);
			TAILQ_FOREACH(svc2, use_services, entries)
			    rc_service_schedule_start(svc2->value, service);
			rc_stringlist_free(use_services);
			use_services = NULL;
			len += strlen(svc->value) + 2;
			n++;
		}

		len += 5;
		tmp = p = xmalloc(sizeof(char) * len);
		TAILQ_FOREACH(svc, tmplist, entries) {
			if (p != tmp)
				p += snprintf(p, len, ", ");
			p += snprintf(p, len - (p - tmp),
			    "%s", svc->value);
		}
		rc_stringlist_free(tmplist);
		tmplist = NULL;
		ewarnx("WARNING: %s is scheduled to start when "
		    "%s has started", applet, tmp);
		free(tmp);
	}

	rc_stringlist_free(tmplist);
	tmplist = NULL;
	rc_stringlist_free(services);
	services = NULL;
}

static void svc_start_real()
{
	bool started;
	RC_STRING *svc, *svc2;

	if (ibsave)
		setenv("IN_BACKGROUND", ibsave, 1);
	hook_out = RC_HOOK_SERVICE_START_DONE;
	rc_plugin_run(RC_HOOK_SERVICE_START_NOW, applet);
	started = (svc_exec("start", NULL) == 0);
	if (ibsave)
		unsetenv("IN_BACKGROUND");

	if (rc_service_state(service) & RC_SERVICE_INACTIVE)
		ewarnx("WARNING: %s has started, but is inactive", applet);
	else if (!started)
		eerrorx("ERROR: %s failed to start", applet);

	rc_service_mark(service, RC_SERVICE_STARTED);
	exclusive_fd = svc_unlock(applet, exclusive_fd);
	hook_out = RC_HOOK_SERVICE_START_OUT;
	rc_plugin_run(RC_HOOK_SERVICE_START_DONE, applet);

	/* Now start any scheduled services */
	services = rc_services_scheduled(service);
	TAILQ_FOREACH(svc, services, entries)
	    if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
		    service_start(svc->value);
	rc_stringlist_free(services);
	services = NULL;

	/* Do the same for any services we provide */
	if (deptree) {
		tmplist = rc_deptree_depend(deptree, "iprovide", applet);
		TAILQ_FOREACH(svc, tmplist, entries) {
			services = rc_services_scheduled(svc->value);
			TAILQ_FOREACH(svc2, services, entries)
			    if (rc_service_state(svc2->value) &
				RC_SERVICE_STOPPED)
				    service_start(svc2->value);
			rc_stringlist_free(services);
			services = NULL;
		}
		rc_stringlist_free(tmplist);
		tmplist = NULL;
	}

	hook_out = 0;
	rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet);
}

static void
svc_start(void)
{
	if (dry_run)
		einfon("start:");
	else
		svc_start_check();
	if (deps)
		svc_start_deps();
	if (dry_run)
		printf(" %s\n", applet);
	else
		svc_start_real();
}

static void
svc_stop_check(RC_SERVICE *state)
{
	*state = rc_service_state(service);

	if (rc_runlevel_stopping() && *state & RC_SERVICE_FAILED)
		exit(EXIT_FAILURE);

	if (in_background &&
	    !(*state & RC_SERVICE_STARTED) &&
	    !(*state & RC_SERVICE_INACTIVE))
		exit(EXIT_FAILURE);

	if (exclusive_fd == -1)
		exclusive_fd = svc_lock(applet);
	if (exclusive_fd == -1) {
		if (errno == EACCES)
			eerrorx("%s: superuser access required", applet);
		if (*state & RC_SERVICE_STOPPING)
			ewarnx("WARNING: %s is already stopping", applet);
		eerrorx("ERROR: %s stopped by something else", applet);
	}
	fcntl(exclusive_fd, F_SETFD,
	    fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC);

	if (*state & RC_SERVICE_STOPPED) {
		ewarn("WARNING: %s is already stopped", applet);
		exit(EXIT_SUCCESS);
	}

	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");
	}
}

static void
svc_stop_deps(RC_SERVICE state)
{
	int depoptions = RC_DEP_TRACE;
	RC_STRING *svc;
	pid_t pid;

	if (state & RC_SERVICE_WASINACTIVE)
		return;

	errno = 0;
	if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT)
		depoptions |= RC_DEP_STRICT;

	if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL))
		eerrorx("failed to load deptree");

	if (!types_m)
		setup_types();

	services = rc_deptree_depends(deptree, types_m, applet_list,
	    runlevel, depoptions);
	tmplist = rc_stringlist_new();
	TAILQ_FOREACH_REVERSE(svc, services, rc_stringlist, entries) {
		state = rc_service_state(svc->value);
		/* Don't stop failed services again.
		 * If you remove this check, ensure that the
		 * exclusive file isn't created. */
		if (state & RC_SERVICE_FAILED &&
		    rc_runlevel_stopping())
			continue;
		if (state & RC_SERVICE_STARTED ||
		    state & RC_SERVICE_INACTIVE)
		{
			if (dry_run) {
				printf(" %s", svc->value);
				continue;
			}
			svc_wait(svc->value);
			state = rc_service_state(svc->value);
			if (state & RC_SERVICE_STARTED ||
			    state & RC_SERVICE_INACTIVE)
			{
				pid = service_stop(svc->value);
				if (!rc_conf_yesno("rc_parallel"))
					rc_waitpid(pid);
				rc_stringlist_add(tmplist, svc->value);
			}
		}
	}
	rc_stringlist_free(services);
	services = NULL;
	if (dry_run)
		return;

	TAILQ_FOREACH(svc, tmplist, entries) {
		if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
			continue;
		svc_wait(svc->value);
		if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
			continue;
		if (rc_runlevel_stopping()) {
			/* If shutting down, we should stop even
			 * if a dependant failed */
			if (runlevel &&
			    (strcmp(runlevel,
				RC_LEVEL_SHUTDOWN) == 0 ||
				strcmp(runlevel,
				    RC_LEVEL_SINGLE) == 0))
				continue;
			rc_service_mark(service, RC_SERVICE_FAILED);
		}
		eerrorx("ERROR: cannot stop %s as %s "
		    "is still up", applet, svc->value);
	}
	rc_stringlist_free(tmplist);
	tmplist = NULL;

	/* We now wait for other services that may use us and are
	 * stopping. This is important when a runlevel stops */
	services = rc_deptree_depends(deptree, types_mua, applet_list,
	    runlevel, depoptions);
	TAILQ_FOREACH(svc, services, entries) {
		if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
			continue;
		svc_wait(svc->value);
	}
	rc_stringlist_free(services);
	services = NULL;
}

static void
svc_stop_real(void)
{
	bool stopped;

	/* If we're stopping localmount, set LC_ALL=C so that
	 * bash doesn't load anything blocking the unmounting of /usr */
	if (strcmp(applet, "localmount") == 0)
		setenv("LC_ALL", "C", 1);

	if (ibsave)
		setenv("IN_BACKGROUND", ibsave, 1);
	hook_out = RC_HOOK_SERVICE_STOP_DONE;
	rc_plugin_run(RC_HOOK_SERVICE_STOP_NOW, applet);
	stopped = (svc_exec("stop", NULL) == 0);
	if (ibsave)
		unsetenv("IN_BACKGROUND");

	if (!stopped)
		eerrorx("ERROR: %s failed to stop", applet);

	if (in_background)
		rc_service_mark(service, RC_SERVICE_INACTIVE);
	else
		rc_service_mark(service, RC_SERVICE_STOPPED);

	hook_out = RC_HOOK_SERVICE_STOP_OUT;
	rc_plugin_run(RC_HOOK_SERVICE_STOP_DONE, applet);
	hook_out = 0;
	rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet);
}

static 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)
{
	/* This is hairly and a better way needs to be found I think!
	 * The issue is this - openvpn need net and dns. net can restart
	 * dns via resolvconf, so you could have openvpn trying to restart
	 * dnsmasq which in turn is waiting on net which in turn is waiting
	 * on dnsmasq.
	 * The work around is for resolvconf to restart its services with
	 * --nodeps which means just that.
	 * The downside is that there is a small window when our status is
	 * invalid.
	 * One workaround would be to introduce a new status,
	 * or status locking. */
	if (!deps) {
		RC_SERVICE state = rc_service_state(service);
		if (state & RC_SERVICE_STARTED || state & RC_SERVICE_INACTIVE)
			svc_exec("stop", "start");
		else
			svc_exec("start", NULL);
		return;
	}

	if (!(rc_service_state(service) & RC_SERVICE_STOPPED)) {
		get_started_services();
		svc_stop();
		if (dry_run)
			ewarn("Cannot calculate restart start dependencies"
			    " on a dry-run");
	}

	svc_start();
	start_services(restart_services);
	rc_stringlist_free(restart_services);
	restart_services = NULL;
}

static bool
service_plugable(void)
{
	char *list, *p, *token;
	bool allow = true, truefalse;
	char *match = rc_conf_value("rc_hotplug");

	if (!match)
		match = rc_conf_value("rc_plug_services");
	if (!match)
		return false;

	list = xstrdup(match);
	p = list;
	while ((token = strsep(&p, " "))) {
		if (token[0] == '!') {
			truefalse = false;
			token++;
		} else
			truefalse = true;

		if (fnmatch(token, applet, 0) == 0) {
			allow = truefalse;
			break;
		}
	}
#ifdef DEBUG_MEMORY
	free(list);
#endif
	return allow;
}

#include "_usage.h"
#define getoptstring "dDsvl:Z" getoptstring_COMMON
#define extraopts "stop | start | restart | describe | zap"
static const struct option longopts[] = {
	{ "debug",      0, NULL, 'd'},
	{ "dry-run",    0, NULL, 'Z'},
	{ "ifstarted",  0, NULL, 's'},
	{ "nodeps",     0, NULL, 'D'},
	{ "lockfd",     1, NULL, 'l'},
	longopts_COMMON
};
static const char *const longopts_help[] = {
	"set xtrace when running the script",
	"show what would be done",
	"only run commands when started",
	"ignore dependencies",
	"fd of the exclusive lock from rc",
	longopts_help_COMMON
};
#include "_usage.c"

int
runscript(int argc, char **argv)
{
	bool doneone = false;
	int retval, opt, depoptions = RC_DEP_TRACE;
	RC_STRING *svc;
	char *save = NULL;
	char pidstr[10];
	size_t l = 0, ll;
	struct stat stbuf;

	/* Show help if insufficient args */
	if (argc < 2 || !exists(argv[1])) {
		fprintf(stderr, "runscript should not be run directly\n");
		exit(EXIT_FAILURE);
	}

	if (stat(argv[1], &stbuf) != 0) {
		fprintf(stderr, "runscript `%s': %s\n",
		    argv[1], strerror(errno));
		exit(EXIT_FAILURE);
	}

	atexit(cleanup);

	service = xstrdup(argv[1]);
	applet = basename_c(service);

	if (argc < 3)
		usage(EXIT_FAILURE);

	/* Change dir to / to ensure all init scripts don't use stuff in pwd */
	if (chdir("/") == -1)
		eerror("chdir: %s", strerror(errno));

	if ((runlevel = xstrdup(getenv("RC_RUNLEVEL"))) == NULL) {
		env_filter();
		env_config();
		runlevel = rc_runlevel_get();
	}

	setenv("EINFO_LOG", service, 1);
	setenv("RC_SVCNAME", applet, 1);

	/* Set an env var so that we always know our pid regardless of any
	   subshells the init script may create so that our mark_service_*
	   functions can always instruct us of this change */
	snprintf(pidstr, sizeof(pidstr), "%d", (int) getpid());
	setenv("RC_RUNSCRIPT_PID", pidstr, 1);

	/* eprefix is kinda klunky, but it works for our purposes */
	if (rc_conf_yesno("rc_parallel")) {
		/* Get the longest service name */
		services = rc_services_in_runlevel(NULL);
		TAILQ_FOREACH(svc, services, entries) {
			ll = strlen(svc->value);
			if (ll > l)
				l = ll;
		}
		rc_stringlist_free(services);
		services = NULL;
		ll = strlen(applet);
		if (ll > l)
			l = ll;

		/* Make our prefix string */
		prefix = xmalloc(sizeof(char) * l + 1);
		ll = strlen(applet);
		memcpy(prefix, applet, ll);
		memset(prefix + ll, ' ', l - ll);
		memset(prefix + l, 0, 1);
		eprefix(prefix);
	}

#ifdef __linux__
	/* Ok, we are ready to go, so setup selinux if applicable */
	setup_selinux(argc, argv);
#endif

	deps = true;

	/* Punt the first arg as its our service name */
	argc--;
	argv++;

	/* Right then, parse any options there may be */
	while ((opt = getopt_long(argc, argv, getoptstring,
		    longopts, (int *)0)) != -1)
		switch (opt) {
		case 'd':
			setenv("RC_DEBUG", "YES", 1);
			break;
		case 'l':
			exclusive_fd = atoi(optarg);
			fcntl(exclusive_fd, F_SETFD,
			    fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC);
			break;
		case 's':
			if (!(rc_service_state(service) & RC_SERVICE_STARTED))
				exit(EXIT_FAILURE);
			break;
		case 'D':
			deps = false;
			break;
		case 'Z':
			dry_run = true;
			break;
		case_RC_COMMON_GETOPT
		}

	/* If we're changing runlevels and not called by rc then we cannot
	   work with any dependencies */
	if (deps && getenv("RC_PID") == NULL &&
	    (rc_runlevel_starting() || rc_runlevel_stopping()))
		deps = false;

	/* Save the IN_BACKGROUND env flag so it's ONLY passed to the service
	   that is being called and not any dependents */
	if (getenv("IN_BACKGROUND")) {
		ibsave = xstrdup(getenv("IN_BACKGROUND"));
		in_background = rc_yesno(ibsave);
		unsetenv("IN_BACKGROUND");
	}

	if (rc_yesno(getenv("IN_HOTPLUG"))) {
		if (!service_plugable())
			eerrorx("%s: not allowed to be hotplugged", applet);
		in_background = true;
	}

	/* Setup a signal handler */
	signal_setup(SIGHUP, handle_signal);
	signal_setup(SIGINT, handle_signal);
	signal_setup(SIGQUIT, handle_signal);
	signal_setup(SIGTERM, handle_signal);
	signal_setup(SIGCHLD, handle_signal);

	/* Load our plugins */
	rc_plugin_load();

	applet_list = rc_stringlist_new();
	rc_stringlist_add(applet_list, applet);

	/* Now run each option */
	retval = EXIT_SUCCESS;
	while (optind < argc) {
		optarg = argv[optind++];

		/* Abort on a sighup here */
		if (sighup)
			exit (EXIT_FAILURE);

		/* Export the command we're running.
		   This is important as we stamp on the restart function now but
		   some start/stop routines still need to behave differently if
		   restarting. */
		unsetenv("RC_CMD");
		setenv("RC_CMD", optarg, 1);

		doneone = true;

		if (strcmp(optarg, "describe") == 0 ||
		    strcmp(optarg, "help") == 0 ||
		    strcmp(optarg, "depend") == 0)
		{
			save = prefix;
			eprefix(NULL);
			prefix = NULL;
			svc_exec(optarg, NULL);
			eprefix(save);
			prefix = save;
		} else if (strcmp(optarg, "ineed") == 0 ||
		    strcmp(optarg, "iuse") == 0 ||
		    strcmp(optarg, "needsme") == 0 ||
		    strcmp(optarg, "usesme") == 0 ||
		    strcmp(optarg, "iafter") == 0 ||
		    strcmp(optarg, "ibefore") == 0 ||
		    strcmp(optarg, "iprovide") == 0)
		{
			errno = 0;
			if (rc_conf_yesno("rc_depend_strict") ||
			    errno == ENOENT)
				depoptions |= RC_DEP_STRICT;

			if (!deptree &&
			    ((deptree = _rc_deptree_load(0, NULL)) == NULL))
				eerrorx("failed to load deptree");

			tmplist = rc_stringlist_new();
			rc_stringlist_add(tmplist, optarg);
			services = rc_deptree_depends(deptree, tmplist,
			    applet_list,
			    runlevel, depoptions);
			rc_stringlist_free(tmplist);
			TAILQ_FOREACH(svc, services, entries)
			    printf("%s ", svc->value);
			printf ("\n");
			rc_stringlist_free(services);
			services = NULL;
		} else if (strcmp (optarg, "status") == 0) {
			save = prefix;
			eprefix(NULL);
			prefix = NULL;
			retval = svc_exec("status", NULL);
		} else {
			if (strcmp(optarg, "conditionalrestart") == 0 ||
			    strcmp(optarg, "condrestart") == 0)
			{
				if (rc_service_state(service) &
				    RC_SERVICE_STARTED)
					svc_restart();
			} else if (strcmp(optarg, "restart") == 0) {
				svc_restart();
			} else if (strcmp(optarg, "start") == 0) {
				svc_start();
			} else if (strcmp(optarg, "stop") == 0 || strcmp(optarg, "pause") == 0) {
				if (strcmp(optarg, "pause") == 0) {
					ewarn("WARNING: 'pause' is deprecated; please use '--nodeps stop'");
					deps = false;
				}
				if (deps && in_background)
					get_started_services();
				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,
						    restart_services,
						    entries)
						    if (rc_service_state(svc->value) &
							RC_SERVICE_STOPPED)
							    rc_service_schedule_start(service, svc->value);
					}
				}
			} else if (strcmp(optarg, "zap") == 0) {
				einfo("Manually resetting %s to stopped state",
				    applet);
				if (!rc_service_mark(applet,
					RC_SERVICE_STOPPED))
					eerrorx("rc_service_mark: %s",
					    strerror(errno));
				unhotplug();
			} else
				retval = svc_exec(optarg, NULL);

			/* We should ensure this list is empty after
			 * an action is done */
			rc_stringlist_free(restart_services);
			restart_services = NULL;
		}

		if (!doneone)
			usage(EXIT_FAILURE);
	}

	return retval;
}