/* * 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 #include #ifdef __linux__ # include #elif defined(__NetBSD__) || defined(__OpenBSD__) # include #else # include #endif #include "builtins.h" #include "einfo.h" #include "rc.h" #include "rc-misc.h" #include "rc-plugin.h" #define SELINUX_LIB RC_LIBDIR "/runscript_selinux.so" #define PREFIX_LOCK RC_SVCDIR "/prefix.lock" #define WAIT_INTERVAL 20000000 /* usecs to poll the lock file */ #define WAIT_TIMEOUT 60 /* seconds until we timeout */ #define WARN_TIMEOUT 10 /* warn about this every N seconds */ static const char *applet; static char *service, *runlevel, *ibsave, *prefix; static RC_DEPTREE *deptree; static RC_STRINGLIST *applet_list, *services, *tmplist; static RC_STRINGLIST *restart_services, *need_services, *use_services; static RC_HOOK hook_out; static int exclusive_fd = -1, master_tty = -1; static bool sighup, in_background, deps, dry_run; static pid_t service_pid; static int signal_pipe[2] = { -1, -1 }; static RC_STRINGLIST *types_b, *types_n, *types_nu, *types_nua, *types_m; static RC_STRINGLIST *types_mua = NULL; #ifdef __linux__ static void (*selinux_run_init_old)(void); static void (*selinux_run_init_new)(int argc, char **argv); static void setup_selinux(int argc, char **argv) { void *lib_handle = NULL; if (! exists(SELINUX_LIB)) return; lib_handle = dlopen(SELINUX_LIB, RTLD_NOW | RTLD_GLOBAL); if (! lib_handle) { eerror("dlopen: %s", dlerror()); return; } selinux_run_init_old = (void (*)(void)) dlfunc(lib_handle, "selinux_runscript"); selinux_run_init_new = (void (*)(int, char **)) dlfunc(lib_handle, "selinux_runscript2"); /* Use new run_init if it exists, else fall back to old */ if (selinux_run_init_new) selinux_run_init_new(argc, argv); else if (selinux_run_init_old) selinux_run_init_old(); else /* This shouldnt happen... probably corrupt lib */ eerrorx("run_init is missing from runscript_selinux.so!"); dlclose(lib_handle); } #endif static void handle_signal(int sig) { int serrno = errno; char signame[10] = { '\0' }; struct winsize ws; switch (sig) { case SIGHUP: sighup = true; break; case SIGCHLD: if (signal_pipe[1] > -1) { if (write(signal_pipe[1], &sig, sizeof(sig)) == -1) eerror("%s: send: %s", service, strerror(errno)); } else rc_waitpid(-1); break; case SIGWINCH: if (master_tty >= 0) { ioctl(fileno(stdout), TIOCGWINSZ, &ws); ioctl(master_tty, TIOCSWINSZ, &ws); } break; case SIGINT: if (!signame[0]) snprintf(signame, sizeof(signame), "SIGINT"); /* FALLTHROUGH */ case SIGTERM: if (!signame[0]) snprintf(signame, sizeof(signame), "SIGTERM"); /* FALLTHROUGH */ case SIGQUIT: if (!signame[0]) snprintf(signame, sizeof(signame), "SIGQUIT"); /* Send the signal to our children too */ if (service_pid > 0) kill(service_pid, sig); eerrorx("%s: caught %s, aborting", applet, signame); /* NOTREACHED */ default: eerror("%s: caught unknown signal %d", applet, sig); } /* Restore errno */ errno = serrno; } static void unhotplug() { char file[PATH_MAX]; snprintf(file, sizeof(file), RC_SVCDIR "/hotplugged/%s", applet); if (exists(file) && unlink(file) != 0) eerror("%s: unlink `%s': %s", applet, file, strerror(errno)); } static void start_services(RC_STRINGLIST *list) { RC_STRING *svc; RC_SERVICE state = rc_service_state (service); if (!list) return; if (state & RC_SERVICE_INACTIVE || state & RC_SERVICE_WASINACTIVE || state & RC_SERVICE_STARTING || state & RC_SERVICE_STARTED) { TAILQ_FOREACH(svc, list, entries) { if (!(rc_service_state(svc->value) & RC_SERVICE_STOPPED)) continue; if (state & RC_SERVICE_INACTIVE || state & RC_SERVICE_WASINACTIVE) { rc_service_schedule_start(service, svc->value); ewarn("WARNING: %s is scheduled to started" " when %s has started", svc->value, applet); } else service_start(svc->value); } } } static void restore_state(void) { RC_SERVICE state; if (rc_in_plugin || exclusive_fd == -1) return; state = rc_service_state(applet); if (state & RC_SERVICE_STOPPING) { if (state & RC_SERVICE_WASINACTIVE) rc_service_mark(applet, RC_SERVICE_INACTIVE); else rc_service_mark(applet, RC_SERVICE_STARTED); if (rc_runlevel_stopping()) rc_service_mark(applet, RC_SERVICE_FAILED); } else if (state & RC_SERVICE_STARTING) { if (state & RC_SERVICE_WASINACTIVE) rc_service_mark(applet, RC_SERVICE_INACTIVE); else rc_service_mark(applet, RC_SERVICE_STOPPED); if (rc_runlevel_starting()) rc_service_mark(applet, RC_SERVICE_FAILED); } exclusive_fd = svc_unlock(applet, exclusive_fd); } static void cleanup(void) { restore_state(); if (!rc_in_plugin) { if (hook_out) { rc_plugin_run(hook_out, applet); if (hook_out == RC_HOOK_SERVICE_START_DONE) rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); else if (hook_out == RC_HOOK_SERVICE_STOP_DONE) rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); } if (restart_services) start_services(restart_services); } rc_plugin_unload(); #ifdef DEBUG_MEMORY rc_stringlist_free(types_b); rc_stringlist_free(types_n); rc_stringlist_free(types_nu); rc_stringlist_free(types_nua); rc_stringlist_free(types_m); rc_stringlist_free(types_mua); rc_deptree_free(deptree); rc_stringlist_free(restart_services); rc_stringlist_free(need_services); rc_stringlist_free(use_services); rc_stringlist_free(services); rc_stringlist_free(applet_list); rc_stringlist_free(tmplist); free(ibsave); free(service); free(prefix); free(runlevel); #endif } /* 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 is scheduled to start when " "%s has started", applet, tmp); free(tmp); } rc_stringlist_free(tmplist); tmplist = NULL; rc_stringlist_free(services); services = NULL; } static void svc_start_real() { bool started; RC_STRING *svc, *svc2; if (ibsave) setenv("IN_BACKGROUND", ibsave, 1); hook_out = RC_HOOK_SERVICE_START_DONE; rc_plugin_run(RC_HOOK_SERVICE_START_NOW, applet); started = (svc_exec("start", NULL) == 0); if (ibsave) unsetenv("IN_BACKGROUND"); if (rc_service_state(service) & RC_SERVICE_INACTIVE) ewarnx("WARNING: %s has started, but is inactive", applet); else if (!started) eerrorx("ERROR: %s failed to start", applet); rc_service_mark(service, RC_SERVICE_STARTED); exclusive_fd = svc_unlock(applet, exclusive_fd); hook_out = RC_HOOK_SERVICE_START_OUT; rc_plugin_run(RC_HOOK_SERVICE_START_DONE, applet); /* Now start any scheduled services */ services = rc_services_scheduled(service); TAILQ_FOREACH(svc, services, entries) if (rc_service_state(svc->value) & RC_SERVICE_STOPPED) service_start(svc->value); rc_stringlist_free(services); services = NULL; /* Do the same for any services we provide */ if (deptree) { tmplist = rc_deptree_depend(deptree, "iprovide", applet); TAILQ_FOREACH(svc, tmplist, entries) { services = rc_services_scheduled(svc->value); TAILQ_FOREACH(svc2, services, entries) if (rc_service_state(svc2->value) & RC_SERVICE_STOPPED) service_start(svc2->value); rc_stringlist_free(services); services = NULL; } rc_stringlist_free(tmplist); tmplist = NULL; } hook_out = 0; rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); } static void svc_start(void) { if (dry_run) einfon("start:"); else svc_start_check(); if (deps) svc_start_deps(); if (dry_run) printf(" %s\n", applet); else svc_start_real(); } static 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 runscript(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, "runscript should not be run directly\n"); exit(EXIT_FAILURE); } if (stat(argv[1], &stbuf) != 0) { fprintf(stderr, "runscript `%s': %s\n", argv[1], strerror(errno)); exit(EXIT_FAILURE); } atexit(cleanup); /* 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); } #ifdef __linux__ /* Ok, we are ready to go, so setup selinux if applicable */ setup_selinux(argc, argv); #endif deps = true; /* Punt the first arg as its our service name */ argc--; argv++; /* Right then, parse any options there may be */ while ((opt = getopt_long(argc, argv, getoptstring, longopts, (int *)0)) != -1) switch (opt) { case 'd': setenv("RC_DEBUG", "YES", 1); break; case 'l': exclusive_fd = atoi(optarg); fcntl(exclusive_fd, F_SETFD, fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC); break; case 's': if (!(rc_service_state(service) & RC_SERVICE_STARTED)) exit(EXIT_FAILURE); break; case '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; }