From 8b9349208696e5caed1a8b34f066cec4ee642194 Mon Sep 17 00:00:00 2001 From: William Hubbs Date: Sun, 26 Apr 2015 13:31:49 -0500 Subject: convert all references from runscript to openrc-run --- src/librc/librc-daemon.c | 12 +- src/rc/Makefile | 4 +- src/rc/openrc-run.c | 1396 ++++++++++++++++++++++++++++++++++++++++++++++ src/rc/rc-applets.c | 12 +- src/rc/runscript.c | 1391 --------------------------------------------- 5 files changed, 1410 insertions(+), 1405 deletions(-) create mode 100644 src/rc/openrc-run.c delete mode 100644 src/rc/runscript.c (limited to 'src') diff --git a/src/librc/librc-daemon.c b/src/librc/librc-daemon.c index 3811a043..4986f70d 100644 --- a/src/librc/librc-daemon.c +++ b/src/librc/librc-daemon.c @@ -99,7 +99,7 @@ rc_find_pids(const char *exec, const char *const *argv, uid_t uid, pid_t pid) pid_t p; char buffer[PATH_MAX]; struct stat sb; - pid_t runscript_pid = 0; + pid_t openrc_pid = 0; char *pp; RC_PIDLIST *pids = NULL; RC_PID *pi; @@ -108,7 +108,7 @@ rc_find_pids(const char *exec, const char *const *argv, uid_t uid, pid_t pid) return NULL; /* - We never match RC_RUNSCRIPT_PID if present so we avoid the below + We never match RC_OPENRC_PID if present so we avoid the below scenario /etc/init.d/ntpd stop does @@ -118,9 +118,9 @@ rc_find_pids(const char *exec, const char *const *argv, uid_t uid, pid_t pid) nasty */ - if ((pp = getenv("RC_RUNSCRIPT_PID"))) { - if (sscanf(pp, "%d", &runscript_pid) != 1) - runscript_pid = 0; + if ((pp = getenv("RC_OPENRC_PID"))) { + if (sscanf(pp, "%d", &openrc_pid) != 1) + openrc_pid = 0; } /* @@ -146,7 +146,7 @@ rc_find_pids(const char *exec, const char *const *argv, uid_t uid, pid_t pid) while ((entry = readdir(procdir)) != NULL) { if (sscanf(entry->d_name, "%d", &p) != 1) continue; - if (runscript_pid != 0 && runscript_pid == p) + if (openrc_pid != 0 && openrc_pid == p) continue; if (pid != 0 && pid != p) continue; diff --git a/src/rc/Makefile b/src/rc/Makefile index fc88ff28..65b58113 100644 --- a/src/rc/Makefile +++ b/src/rc/Makefile @@ -1,8 +1,8 @@ PROG= openrc -SRCS= checkpath.c fstabinfo.c mountinfo.c start-stop-daemon.c \ +SRCS= checkpath.c fstabinfo.c mountinfo.c openrc-run.c \ rc-applets.c rc-depend.c rc-logger.c \ rc-misc.c rc-plugin.c rc-service.c rc-status.c rc-update.c \ - runscript.c rc.c swclock.c + rc.c start-stop-daemon.c swclock.c ifeq (${MKSELINUX},yes) SRCS+= rc-selinux.c 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 + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) || (defined(__FreeBSD_kernel__) && \ + defined(__GLIBC__)) +# include +#elif defined(__NetBSD__) || defined(__OpenBSD__) +# include +#else +# include +#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)); +} diff --git a/src/rc/rc-applets.c b/src/rc/rc-applets.c index 8fe2d223..9b84ce4b 100644 --- a/src/rc/rc-applets.c +++ b/src/rc/rc-applets.c @@ -329,7 +329,7 @@ do_mark_service(int argc, char **argv) bool ok = false; char *svcname = getenv("RC_SVCNAME"); char *service = NULL; - char *runscript_pid; + char *openrc_pid; /* char *mtime; */ pid_t pid; RC_SERVICE bit; @@ -350,7 +350,7 @@ do_mark_service(int argc, char **argv) eerrorx("%s: unknown applet", applet); /* If we're marking ourselves then we need to inform our parent - runscript process so they do not mark us based on our exit code */ + openrc-run process so they do not mark us based on our exit code */ /* * FIXME: svcname and service are almost always equal except called from a * shell with just argv[1] - So that doesn't seem to do what Roy initially @@ -359,8 +359,8 @@ do_mark_service(int argc, char **argv) * openrc@gentoo.org). */ if (ok && svcname && strcmp(svcname, service) == 0) { - runscript_pid = getenv("RC_RUNSCRIPT_PID"); - if (runscript_pid && sscanf(runscript_pid, "%d", &pid) == 1) + openrc_pid = getenv("RC_OPENRC_PID"); + if (openrc_pid && sscanf(openrc_pid, "%d", &pid) == 1) if (kill(pid, SIGHUP) != 0) eerror("%s: failed to signal parent %d: %s", applet, pid, strerror(errno)); @@ -369,10 +369,10 @@ do_mark_service(int argc, char **argv) in control as well */ /* l = strlen(RC_SVCDIR "/exclusive") + strlen(svcname) + - strlen(runscript_pid) + 4; + strlen(openrc_pid) + 4; mtime = xmalloc(l); snprintf(mtime, l, RC_SVCDIR "/exclusive/%s.%s", - svcname, runscript_pid); + svcname, openrc_pid); if (exists(mtime) && unlink(mtime) != 0) eerror("%s: unlink: %s", applet, strerror(errno)); free(mtime); diff --git a/src/rc/runscript.c b/src/rc/runscript.c deleted file mode 100644 index e59c6aed..00000000 --- a/src/rc/runscript.c +++ /dev/null @@ -1,1391 +0,0 @@ -/* - * runscript.c - * Handle launching of init scripts. - */ - -/* - * Copyright (c) 2007-2009 Roy Marples - * - * 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 -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(__linux__) || (defined(__FreeBSD_kernel__) && \ - defined(__GLIBC__)) -# include -#elif defined(__NetBSD__) || defined(__OpenBSD__) -# include -#else -# include -#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 "/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 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_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)); -} -- cgit v1.2.3