diff options
Diffstat (limited to 'src/rc/openrc-run.c')
-rw-r--r-- | src/rc/openrc-run.c | 1396 |
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)); +} |