aboutsummaryrefslogtreecommitdiff
path: root/src/rc/openrc-run.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/rc/openrc-run.c')
-rw-r--r--src/rc/openrc-run.c1396
1 files changed, 1396 insertions, 0 deletions
diff --git a/src/rc/openrc-run.c b/src/rc/openrc-run.c
new file mode 100644
index 00000000..989779bb
--- /dev/null
+++ b/src/rc/openrc-run.c
@@ -0,0 +1,1396 @@
+/*
+ * openrc-run.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 <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>
+
+#if defined(__linux__) || (defined(__FreeBSD_kernel__) && \
+ defined(__GLIBC__))
+# include <pty.h>
+#elif defined(__NetBSD__) || defined(__OpenBSD__)
+# include <util.h>
+#else
+# include <libutil.h>
+#endif
+
+#include "builtins.h"
+#include "einfo.h"
+#include "queue.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "rc-plugin.h"
+#include "rc-selinux.h"
+
+#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;
+
+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 will start 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
+}
+
+/* Buffer and lock all output messages so that we get readable content */
+/* FIXME: Use a dynamic lock file that contains the tty/pts as well.
+ * For example openrc-pts8.lock or openrc-tty1.lock.
+ * Using a static lock file makes no sense, esp. in multi-user environments.
+ * Why don't we use (f)printf, as it is thread-safe through POSIX already?
+ * Bug: 360013
+ */
+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;
+
+ /*
+ * Lock the prefix.
+ * open() may fail here when running as user, as RC_SVCDIR may not be writable.
+ */
+ lock_fd = open(PREFIX_LOCK, O_WRONLY | O_CREAT, 0664);
+
+ if (lock_fd != -1) {
+ while (flock(lock_fd, LOCK_EX) != 0) {
+ if (errno != EINTR) {
+ ewarnv("flock() failed: %s", strerror(errno));
+ break;
+ }
+ }
+ }
+ else
+ ewarnv("Couldn't open the prefix lock, please make sure you have enough permissions");
+
+ 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 "/openrc-run.sh")) {
+ execl(RC_SVCDIR "/openrc-run.sh",
+ RC_SVCDIR "/openrc-run.sh",
+ service, arg1, arg2, (char *) NULL);
+ eerror("%s: exec `" RC_SVCDIR "/openrc-run.sh': %s",
+ service, strerror(errno));
+ _exit(EXIT_FAILURE);
+ } else {
+ execl(RC_LIBEXECDIR "/sh/openrc-run.sh",
+ RC_LIBEXECDIR "/sh/openrc-run.sh",
+ service, arg1, arg2, (char *) NULL);
+ eerror("%s: exec `" RC_LIBEXECDIR "/sh/openrc-run.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 will start when %s has started", applet, tmp);
+ free(tmp);
+ }
+
+ rc_stringlist_free(tmplist);
+ tmplist = NULL;
+ rc_stringlist_free(services);
+ services = NULL;
+}
+
+static void svc_start_real()
+{
+ bool started;
+ RC_STRING *svc, *svc2;
+
+ if (ibsave)
+ setenv("IN_BACKGROUND", ibsave, 1);
+ hook_out = RC_HOOK_SERVICE_START_DONE;
+ rc_plugin_run(RC_HOOK_SERVICE_START_NOW, applet);
+ started = (svc_exec("start", NULL) == 0);
+ if (ibsave)
+ unsetenv("IN_BACKGROUND");
+
+ if (rc_service_state(service) & RC_SERVICE_INACTIVE)
+ ewarnx("WARNING: %s has started, but is inactive", applet);
+ else if (!started)
+ eerrorx("ERROR: %s failed to start", applet);
+
+ rc_service_mark(service, RC_SERVICE_STARTED);
+ exclusive_fd = svc_unlock(applet, exclusive_fd);
+ hook_out = RC_HOOK_SERVICE_START_OUT;
+ rc_plugin_run(RC_HOOK_SERVICE_START_DONE, applet);
+
+ /* Now start any scheduled services */
+ services = rc_services_scheduled(service);
+ TAILQ_FOREACH(svc, services, entries)
+ if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
+ service_start(svc->value);
+ rc_stringlist_free(services);
+ services = NULL;
+
+ /* Do the same for any services we provide */
+ if (deptree) {
+ tmplist = rc_deptree_depend(deptree, "iprovide", applet);
+ TAILQ_FOREACH(svc, tmplist, entries) {
+ services = rc_services_scheduled(svc->value);
+ TAILQ_FOREACH(svc2, services, entries)
+ if (rc_service_state(svc2->value) &
+ RC_SERVICE_STOPPED)
+ service_start(svc2->value);
+ rc_stringlist_free(services);
+ services = NULL;
+ }
+ rc_stringlist_free(tmplist);
+ tmplist = NULL;
+ }
+
+ hook_out = 0;
+ rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet);
+}
+
+static void
+svc_start(void)
+{
+ if (dry_run)
+ einfon("start:");
+ else
+ svc_start_check();
+ if (deps)
+ svc_start_deps();
+ if (dry_run)
+ printf(" %s\n", applet);
+ else
+ svc_start_real();
+}
+
+static int
+svc_stop_check(RC_SERVICE *state)
+{
+ *state = rc_service_state(service);
+
+ if (rc_runlevel_stopping() && *state & RC_SERVICE_FAILED)
+ exit(EXIT_FAILURE);
+
+ if (in_background &&
+ !(*state & RC_SERVICE_STARTED) &&
+ !(*state & RC_SERVICE_INACTIVE))
+ exit(EXIT_FAILURE);
+
+ if (exclusive_fd == -1)
+ exclusive_fd = svc_lock(applet);
+ if (exclusive_fd == -1) {
+ if (errno == EACCES)
+ eerrorx("%s: superuser access required", applet);
+ if (*state & RC_SERVICE_STOPPING)
+ ewarnx("WARNING: %s is already stopping", applet);
+ eerrorx("ERROR: %s stopped by something else", applet);
+ }
+ fcntl(exclusive_fd, F_SETFD,
+ fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC);
+
+ if (*state & RC_SERVICE_STOPPED) {
+ ewarn("WARNING: %s is already stopped", applet);
+ return 1;
+ }
+
+ rc_service_mark(service, RC_SERVICE_STOPPING);
+ hook_out = RC_HOOK_SERVICE_STOP_OUT;
+ rc_plugin_run(RC_HOOK_SERVICE_STOP_IN, applet);
+
+ if (!rc_runlevel_stopping()) {
+ if (rc_service_in_runlevel(service, RC_LEVEL_SYSINIT))
+ ewarn("WARNING: you are stopping a sysinit service");
+ else if (rc_service_in_runlevel(service, RC_LEVEL_BOOT))
+ ewarn("WARNING: you are stopping a boot service");
+ }
+
+ return 0;
+}
+
+static void
+svc_stop_deps(RC_SERVICE state)
+{
+ int depoptions = RC_DEP_TRACE;
+ RC_STRING *svc;
+ pid_t pid;
+
+ if (state & RC_SERVICE_WASINACTIVE)
+ return;
+
+ errno = 0;
+ if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT)
+ depoptions |= RC_DEP_STRICT;
+
+ if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL))
+ eerrorx("failed to load deptree");
+
+ if (!types_m)
+ setup_types();
+
+ services = rc_deptree_depends(deptree, types_m, applet_list,
+ runlevel, depoptions);
+ tmplist = rc_stringlist_new();
+ TAILQ_FOREACH_REVERSE(svc, services, rc_stringlist, entries) {
+ state = rc_service_state(svc->value);
+ /* Don't stop failed services again.
+ * If you remove this check, ensure that the
+ * exclusive file isn't created. */
+ if (state & RC_SERVICE_FAILED &&
+ rc_runlevel_stopping())
+ continue;
+ if (state & RC_SERVICE_STARTED ||
+ state & RC_SERVICE_INACTIVE)
+ {
+ if (dry_run) {
+ printf(" %s", svc->value);
+ continue;
+ }
+ svc_wait(svc->value);
+ state = rc_service_state(svc->value);
+ if (state & RC_SERVICE_STARTED ||
+ state & RC_SERVICE_INACTIVE)
+ {
+ pid = service_stop(svc->value);
+ if (!rc_conf_yesno("rc_parallel"))
+ rc_waitpid(pid);
+ rc_stringlist_add(tmplist, svc->value);
+ }
+ }
+ }
+ rc_stringlist_free(services);
+ services = NULL;
+ if (dry_run)
+ return;
+
+ TAILQ_FOREACH(svc, tmplist, entries) {
+ if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
+ continue;
+ svc_wait(svc->value);
+ if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
+ continue;
+ if (rc_runlevel_stopping()) {
+ /* If shutting down, we should stop even
+ * if a dependant failed */
+ if (runlevel &&
+ (strcmp(runlevel,
+ RC_LEVEL_SHUTDOWN) == 0 ||
+ strcmp(runlevel,
+ RC_LEVEL_SINGLE) == 0))
+ continue;
+ rc_service_mark(service, RC_SERVICE_FAILED);
+ }
+ eerrorx("ERROR: cannot stop %s as %s "
+ "is still up", applet, svc->value);
+ }
+ rc_stringlist_free(tmplist);
+ tmplist = NULL;
+
+ /* We now wait for other services that may use us and are
+ * stopping. This is important when a runlevel stops */
+ services = rc_deptree_depends(deptree, types_mua, applet_list,
+ runlevel, depoptions);
+ TAILQ_FOREACH(svc, services, entries) {
+ if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
+ continue;
+ svc_wait(svc->value);
+ }
+ rc_stringlist_free(services);
+ services = NULL;
+}
+
+static void
+svc_stop_real(void)
+{
+ bool stopped;
+
+ /* If we're stopping localmount, set LC_ALL=C so that
+ * bash doesn't load anything blocking the unmounting of /usr */
+ if (strcmp(applet, "localmount") == 0)
+ setenv("LC_ALL", "C", 1);
+
+ if (ibsave)
+ setenv("IN_BACKGROUND", ibsave, 1);
+ hook_out = RC_HOOK_SERVICE_STOP_DONE;
+ rc_plugin_run(RC_HOOK_SERVICE_STOP_NOW, applet);
+ stopped = (svc_exec("stop", NULL) == 0);
+ if (ibsave)
+ unsetenv("IN_BACKGROUND");
+
+ if (!stopped)
+ eerrorx("ERROR: %s failed to stop", applet);
+
+ if (in_background)
+ rc_service_mark(service, RC_SERVICE_INACTIVE);
+ else
+ rc_service_mark(service, RC_SERVICE_STOPPED);
+
+ hook_out = RC_HOOK_SERVICE_STOP_OUT;
+ rc_plugin_run(RC_HOOK_SERVICE_STOP_DONE, applet);
+ hook_out = 0;
+ rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet);
+}
+
+static int
+svc_stop(void)
+{
+ RC_SERVICE state;
+
+ state = 0;
+ if (dry_run)
+ einfon("stop:");
+ else
+ if (svc_stop_check(&state) == 1)
+ return 1; /* Service has been stopped already */
+ if (deps)
+ svc_stop_deps(state);
+ if (dry_run)
+ printf(" %s\n", applet);
+ else
+ svc_stop_real();
+
+ return 0;
+}
+
+static void
+svc_restart(void)
+{
+ /* This is hairly and a better way needs to be found I think!
+ * The issue is this - openvpn need net and dns. net can restart
+ * dns via resolvconf, so you could have openvpn trying to restart
+ * dnsmasq which in turn is waiting on net which in turn is waiting
+ * on dnsmasq.
+ * The work around is for resolvconf to restart its services with
+ * --nodeps which means just that.
+ * The downside is that there is a small window when our status is
+ * invalid.
+ * One workaround would be to introduce a new status,
+ * or status locking. */
+ if (!deps) {
+ RC_SERVICE state = rc_service_state(service);
+ if (state & RC_SERVICE_STARTED || state & RC_SERVICE_INACTIVE)
+ svc_exec("stop", "start");
+ else
+ svc_exec("start", NULL);
+ return;
+ }
+
+ if (!(rc_service_state(service) & RC_SERVICE_STOPPED)) {
+ get_started_services();
+ svc_stop();
+ if (dry_run)
+ ewarn("Cannot calculate restart start dependencies"
+ " on a dry-run");
+ }
+
+ svc_start();
+ start_services(restart_services);
+ rc_stringlist_free(restart_services);
+ restart_services = NULL;
+}
+
+static bool
+service_plugable(void)
+{
+ char *list, *p, *token;
+ bool allow = true, truefalse;
+ char *match = rc_conf_value("rc_hotplug");
+
+ if (!match)
+ match = rc_conf_value("rc_plug_services");
+ if (!match)
+ return false;
+
+ list = xstrdup(match);
+ p = list;
+ while ((token = strsep(&p, " "))) {
+ if (token[0] == '!') {
+ truefalse = false;
+ token++;
+ } else
+ truefalse = true;
+
+ if (fnmatch(token, applet, 0) == 0) {
+ allow = truefalse;
+ break;
+ }
+ }
+#ifdef DEBUG_MEMORY
+ free(list);
+#endif
+ return allow;
+}
+
+#include "_usage.h"
+#define getoptstring "dDsSvl:Z" getoptstring_COMMON
+#define extraopts "stop | start | restart | describe | zap"
+static const struct option longopts[] = {
+ { "debug", 0, NULL, 'd'},
+ { "dry-run", 0, NULL, 'Z'},
+ { "ifstarted", 0, NULL, 's'},
+ { "ifstopped", 0, NULL, 'S'},
+ { "nodeps", 0, NULL, 'D'},
+ { "lockfd", 1, NULL, 'l'},
+ longopts_COMMON
+};
+static const char *const longopts_help[] = {
+ "set xtrace when running the script",
+ "show what would be done",
+ "only run commands when started",
+ "only run commands when stopped",
+ "ignore dependencies",
+ "fd of the exclusive lock from rc",
+ longopts_help_COMMON
+};
+#include "_usage.c"
+
+int
+openrc_run(int argc, char **argv)
+{
+ bool doneone = false;
+ int retval, opt, depoptions = RC_DEP_TRACE;
+ RC_STRING *svc;
+ char path[PATH_MAX], lnk[PATH_MAX];
+ char *dir, *save = NULL, *saveLnk = NULL;
+ char pidstr[10];
+ size_t l = 0, ll;
+ const char *file;
+ struct stat stbuf;
+
+ /* Show help if insufficient args */
+ if (argc < 2 || !exists(argv[1])) {
+ fprintf(stderr, "openrc-run should not be run directly\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (stat(argv[1], &stbuf) != 0) {
+ fprintf(stderr, "openrc-run `%s': %s\n",
+ argv[1], strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ atexit(cleanup);
+
+ /* We need to work out the real full path to our service.
+ * This works fine, provided that we ONLY allow multiplexed services
+ * to exist in the same directory as the master link.
+ * Also, the master link as to be a real file in the init dir. */
+ if (!realpath(argv[1], path)) {
+ fprintf(stderr, "realpath: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ memset(lnk, 0, sizeof(lnk));
+ if (readlink(argv[1], lnk, sizeof(lnk)-1)) {
+ dir = dirname(path);
+ if (strchr(lnk, '/')) {
+ save = xstrdup(dir);
+ saveLnk = xstrdup(lnk);
+ dir = dirname(saveLnk);
+ if (strcmp(dir, save) == 0)
+ file = basename_c(argv[1]);
+ else
+ file = basename_c(lnk);
+ dir = save;
+ } else
+ file = basename_c(argv[1]);
+ ll = strlen(dir) + strlen(file) + 2;
+ service = xmalloc(ll);
+ snprintf(service, ll, "%s/%s", dir, file);
+ if (stat(service, &stbuf) != 0) {
+ free(service);
+ service = xstrdup(lnk);
+ }
+ free(save);
+ free(saveLnk);
+ }
+ if (!service)
+ service = xstrdup(path);
+ applet = basename_c(service);
+
+ if (argc < 3)
+ usage(EXIT_FAILURE);
+
+ /* Change dir to / to ensure all init scripts don't use stuff in pwd */
+ if (chdir("/") == -1)
+ eerror("chdir: %s", strerror(errno));
+
+ if ((runlevel = xstrdup(getenv("RC_RUNLEVEL"))) == NULL) {
+ env_filter();
+ env_config();
+ runlevel = rc_runlevel_get();
+ }
+
+ setenv("EINFO_LOG", service, 1);
+ setenv("RC_SVCNAME", applet, 1);
+
+ /* Set an env var so that we always know our pid regardless of any
+ subshells the init script may create so that our mark_service_*
+ functions can always instruct us of this change */
+ snprintf(pidstr, sizeof(pidstr), "%d", (int) getpid());
+ setenv("RC_OPENRC_PID", pidstr, 1);
+ /*
+ * RC_RUNSCRIPT_PID is deprecated, but we will keep it for a while
+ * for safety.
+ */
+ 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);
+ }
+
+ /* Ok, we are ready to go, so setup selinux if applicable */
+ selinux_setup(argv);
+
+ deps = true;
+
+ /* Punt the first arg as its our service name */
+ argc--;
+ argv++;
+
+ /* Right then, parse any options there may be */
+ while ((opt = getopt_long(argc, argv, getoptstring,
+ longopts, (int *)0)) != -1)
+ switch (opt) {
+ case 'd':
+ setenv("RC_DEBUG", "YES", 1);
+ break;
+ case 'l':
+ exclusive_fd = atoi(optarg);
+ fcntl(exclusive_fd, F_SETFD,
+ fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC);
+ break;
+ case 's':
+ if (!(rc_service_state(service) & RC_SERVICE_STARTED))
+ exit(EXIT_FAILURE);
+ break;
+ case 'S':
+ if (!(rc_service_state(service) & RC_SERVICE_STOPPED))
+ exit(EXIT_FAILURE);
+ break;
+ case 'D':
+ deps = false;
+ break;
+ case 'Z':
+ dry_run = true;
+ break;
+ case_RC_COMMON_GETOPT
+ }
+
+ /* If we're changing runlevels and not called by rc then we cannot
+ work with any dependencies */
+ if (deps && getenv("RC_PID") == NULL &&
+ (rc_runlevel_starting() || rc_runlevel_stopping()))
+ deps = false;
+
+ /* Save the IN_BACKGROUND env flag so it's ONLY passed to the service
+ that is being called and not any dependents */
+ if (getenv("IN_BACKGROUND")) {
+ ibsave = xstrdup(getenv("IN_BACKGROUND"));
+ in_background = rc_yesno(ibsave);
+ unsetenv("IN_BACKGROUND");
+ }
+
+ if (rc_yesno(getenv("IN_HOTPLUG"))) {
+ if (!service_plugable())
+ eerrorx("%s: not allowed to be hotplugged", applet);
+ in_background = true;
+ }
+
+ /* Setup a signal handler */
+ signal_setup(SIGHUP, handle_signal);
+ signal_setup(SIGINT, handle_signal);
+ signal_setup(SIGQUIT, handle_signal);
+ signal_setup(SIGTERM, handle_signal);
+ signal_setup(SIGCHLD, handle_signal);
+
+ /* Load our plugins */
+ rc_plugin_load();
+
+ applet_list = rc_stringlist_new();
+ rc_stringlist_add(applet_list, applet);
+
+ /* Now run each option */
+ retval = EXIT_SUCCESS;
+ while (optind < argc) {
+ optarg = argv[optind++];
+
+ /* Abort on a sighup here */
+ if (sighup)
+ exit (EXIT_FAILURE);
+
+ /* Export the command we're running.
+ This is important as we stamp on the restart function now but
+ some start/stop routines still need to behave differently if
+ restarting. */
+ unsetenv("RC_CMD");
+ setenv("RC_CMD", optarg, 1);
+
+ doneone = true;
+
+ if (strcmp(optarg, "describe") == 0 ||
+ strcmp(optarg, "help") == 0 ||
+ strcmp(optarg, "depend") == 0)
+ {
+ save = prefix;
+ eprefix(NULL);
+ prefix = NULL;
+ svc_exec(optarg, NULL);
+ eprefix(save);
+ prefix = save;
+ } else if (strcmp(optarg, "ineed") == 0 ||
+ strcmp(optarg, "iuse") == 0 ||
+ strcmp(optarg, "needsme") == 0 ||
+ strcmp(optarg, "usesme") == 0 ||
+ strcmp(optarg, "iafter") == 0 ||
+ strcmp(optarg, "ibefore") == 0 ||
+ strcmp(optarg, "iprovide") == 0)
+ {
+ errno = 0;
+ if (rc_conf_yesno("rc_depend_strict") ||
+ errno == ENOENT)
+ depoptions |= RC_DEP_STRICT;
+
+ if (!deptree &&
+ ((deptree = _rc_deptree_load(0, NULL)) == NULL))
+ eerrorx("failed to load deptree");
+
+ tmplist = rc_stringlist_new();
+ rc_stringlist_add(tmplist, optarg);
+ services = rc_deptree_depends(deptree, tmplist,
+ applet_list,
+ runlevel, depoptions);
+ rc_stringlist_free(tmplist);
+ TAILQ_FOREACH(svc, services, entries)
+ printf("%s ", svc->value);
+ printf ("\n");
+ rc_stringlist_free(services);
+ services = NULL;
+ } else if (strcmp (optarg, "status") == 0) {
+ save = prefix;
+ eprefix(NULL);
+ prefix = NULL;
+ retval = svc_exec("status", NULL);
+ } else {
+ if (strcmp(optarg, "conditionalrestart") == 0 ||
+ strcmp(optarg, "condrestart") == 0)
+ {
+ if (rc_service_state(service) &
+ RC_SERVICE_STARTED)
+ svc_restart();
+ } else if (strcmp(optarg, "restart") == 0) {
+ svc_restart();
+ } else if (strcmp(optarg, "start") == 0) {
+ svc_start();
+ } else if (strcmp(optarg, "stop") == 0 || strcmp(optarg, "pause") == 0) {
+ if (strcmp(optarg, "pause") == 0) {
+ ewarn("WARNING: 'pause' is deprecated; please use '--nodeps stop'");
+ deps = false;
+ }
+ if (deps && in_background)
+ get_started_services();
+ if (svc_stop() == 1)
+ continue; /* Service has been stopped already */
+ if (deps) {
+ if (!in_background &&
+ !rc_runlevel_stopping() &&
+ rc_service_state(service) &
+ RC_SERVICE_STOPPED)
+ unhotplug();
+
+ if (in_background &&
+ rc_service_state(service) &
+ RC_SERVICE_INACTIVE)
+ {
+ TAILQ_FOREACH(svc,
+ restart_services,
+ entries)
+ if (rc_service_state(svc->value) &
+ RC_SERVICE_STOPPED)
+ rc_service_schedule_start(service, svc->value);
+ }
+ }
+ } else if (strcmp(optarg, "zap") == 0) {
+ einfo("Manually resetting %s to stopped state",
+ applet);
+ if (!rc_service_mark(applet,
+ RC_SERVICE_STOPPED))
+ eerrorx("rc_service_mark: %s",
+ strerror(errno));
+ unhotplug();
+ } else
+ retval = svc_exec(optarg, NULL);
+
+ /* We should ensure this list is empty after
+ * an action is done */
+ rc_stringlist_free(restart_services);
+ restart_services = NULL;
+ }
+
+ if (!doneone)
+ usage(EXIT_FAILURE);
+ }
+
+ return retval;
+}
+
+int
+runscript(int argc, char **argv)
+{
+ ewarnv("runscript is deprecated; please use openrc-run instead.");
+ return (openrc_run(argc, argv));
+}