/* * 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 } static int write_prefix(const char *buffer, size_t bytes, bool *prefixed) { size_t i, j; const char *ec = ecolor(ECOLOR_HILITE); const char *ec_normal = ecolor(ECOLOR_NORMAL); ssize_t ret = 0; int fd = fileno(stdout), lock_fd = -1; /* Spin until we lock the prefix */ for (;;) { lock_fd = open(PREFIX_LOCK, O_WRONLY | O_CREAT, 0664); if (lock_fd != -1) if (flock(lock_fd, LOCK_EX) == 0) break; close(lock_fd); } for (i = 0; i < bytes; i++) { /* We don't prefix eend calls (cursor up) */ if (buffer[i] == '\033' && !*prefixed) { for (j = i + 1; j < bytes; j++) { if (buffer[j] == 'A') *prefixed = true; if (isalpha((unsigned int)buffer[j])) break; } } if (!*prefixed) { ret += write(fd, ec, strlen(ec)); ret += write(fd, prefix, strlen(prefix)); ret += write(fd, ec_normal, strlen(ec_normal)); ret += write(fd, "|", 1); *prefixed = true; } if (buffer[i] == '\n') *prefixed = false; ret += write(fd, buffer + i, 1); } /* Release the lock */ close(lock_fd); return ret; } static int svc_exec(const char *arg1, const char *arg2) { int ret, fdout = fileno(stdout); struct termios tt; struct winsize ws; int i; int flags = 0; struct pollfd fd[2]; int s; char *buffer; size_t bytes; bool prefixed = false; int slave_tty; sigset_t sigchldmask; sigset_t oldmask; /* Setup our signal pipe */ if (pipe(signal_pipe) == -1) eerrorx("%s: pipe: %s", service, applet); for (i = 0; i < 2; i++) if ((flags = fcntl(signal_pipe[i], F_GETFD, 0) == -1 || fcntl(signal_pipe[i], F_SETFD, flags | FD_CLOEXEC) == -1)) eerrorx("%s: fcntl: %s", service, strerror(errno)); /* Open a pty for our prefixed output * We do this instead of mapping pipes to stdout, stderr so that * programs can tell if they're attached to a tty or not. * The only loss is that we can no longer tell the difference * between the childs stdout or stderr */ master_tty = slave_tty = -1; if (prefix && isatty(fdout)) { tcgetattr(fdout, &tt); ioctl(fdout, TIOCGWINSZ, &ws); /* If the below call fails due to not enough ptys then we don't * prefix the output, but we still work */ openpty(&master_tty, &slave_tty, NULL, &tt, &ws); if (master_tty >= 0 && (flags = fcntl(master_tty, F_GETFD, 0)) == 0) fcntl(master_tty, F_SETFD, flags | FD_CLOEXEC); if (slave_tty >=0 && (flags = fcntl(slave_tty, F_GETFD, 0)) == 0) fcntl(slave_tty, F_SETFD, flags | FD_CLOEXEC); } service_pid = fork(); if (service_pid == -1) eerrorx("%s: fork: %s", service, strerror(errno)); if (service_pid == 0) { if (slave_tty >= 0) { dup2(slave_tty, STDOUT_FILENO); dup2(slave_tty, STDERR_FILENO); } if (exists(RC_SVCDIR "/runscript.sh")) { execl(RC_SVCDIR "/runscript.sh", RC_SVCDIR "/runscript.sh", service, arg1, arg2, (char *) NULL); eerror("%s: exec `" RC_SVCDIR "/runscript.sh': %s", service, strerror(errno)); _exit(EXIT_FAILURE); } else { execl(RC_LIBEXECDIR "/sh/runscript.sh", RC_LIBEXECDIR "/sh/runscript.sh", service, arg1, arg2, (char *) NULL); eerror("%s: exec `" RC_LIBEXECDIR "/sh/runscript.sh': %s", service, strerror(errno)); _exit(EXIT_FAILURE); } } buffer = xmalloc(sizeof(char) * BUFSIZ); fd[0].fd = signal_pipe[0]; fd[0].events = fd[1].events = POLLIN; fd[0].revents = fd[1].revents = 0; if (master_tty >= 0) { fd[1].fd = master_tty; fd[1].events = POLLIN; fd[1].revents = 0; } for (;;) { if ((s = poll(fd, master_tty >= 0 ? 2 : 1, -1)) == -1) { if (errno != EINTR) { eerror("%s: poll: %s", service, strerror(errno)); break; } } if (s > 0) { if (fd[1].revents & (POLLIN | POLLHUP)) { bytes = read(master_tty, buffer, BUFSIZ); write_prefix(buffer, bytes, &prefixed); } /* Only SIGCHLD signals come down this pipe */ if (fd[0].revents & (POLLIN | POLLHUP)) break; } } free(buffer); sigemptyset (&sigchldmask); sigaddset (&sigchldmask, SIGCHLD); sigprocmask (SIG_BLOCK, &sigchldmask, &oldmask); close(signal_pipe[0]); close(signal_pipe[1]); signal_pipe[0] = signal_pipe[1] = -1; sigprocmask (SIG_SETMASK, &oldmask, NULL); if (master_tty >= 0) { /* Why did we do this? */ /* signal (SIGWINCH, SIG_IGN); */ close(master_tty); master_tty = -1; } ret = rc_waitpid(service_pid); ret = WEXITSTATUS(ret); if (ret != 0 && errno == ECHILD) /* killall5 -9 could cause this */ ret = 0; service_pid = 0; return ret; } static bool svc_wait(const char *svc) { char file[PATH_MAX]; int fd; bool forever = false; RC_STRINGLIST *keywords; struct timespec interval, timeout, warn; /* Some services don't have a timeout, like fsck */ keywords = rc_deptree_depend(deptree, svc, "keyword"); if (rc_stringlist_find(keywords, "-timeout") || rc_stringlist_find(keywords, "notimeout")) forever = true; rc_stringlist_free(keywords); snprintf(file, sizeof(file), RC_SVCDIR "/exclusive/%s", basename_c(svc)); interval.tv_sec = 0; interval.tv_nsec = WAIT_INTERVAL; timeout.tv_sec = WAIT_TIMEOUT; timeout.tv_nsec = 0; warn.tv_sec = WARN_TIMEOUT; warn.tv_nsec = 0; for (;;) { fd = open(file, O_RDONLY | O_NONBLOCK); if (fd != -1) { if (flock(fd, LOCK_SH | LOCK_NB) == 0) { close(fd); return true; } close(fd); } if (errno == ENOENT) return true; if (errno != EWOULDBLOCK) eerrorx("%s: open `%s': %s", applet, file, strerror(errno)); if (nanosleep(&interval, NULL) == -1) { if (errno != EINTR) return false; } if (!forever) { timespecsub(&timeout, &interval, &timeout); if (timeout.tv_sec <= 0) return false; timespecsub(&warn, &interval, &warn); if (warn.tv_sec <= 0) { ewarn("%s: waiting for %s (%d seconds)", applet, svc, (int)timeout.tv_sec); warn.tv_sec = WARN_TIMEOUT; warn.tv_nsec = 0; } } } return false; } static void get_started_services(void) { RC_STRINGLIST *tmp = rc_services_in_state(RC_SERVICE_INACTIVE); rc_stringlist_free(restart_services); restart_services = rc_services_in_state(RC_SERVICE_STARTED); TAILQ_CONCAT(restart_services, tmp, entries); free(tmp); } static void setup_types(void) { types_b = rc_stringlist_new(); rc_stringlist_add(types_b, "broken"); types_n = rc_stringlist_new(); rc_stringlist_add(types_n, "ineed"); types_nu = rc_stringlist_new(); rc_stringlist_add(types_nu, "ineed"); rc_stringlist_add(types_nu, "iuse"); types_nua = rc_stringlist_new(); rc_stringlist_add(types_nua, "ineed"); rc_stringlist_add(types_nua, "iuse"); rc_stringlist_add(types_nua, "iafter"); types_m = rc_stringlist_new(); rc_stringlist_add(types_m, "needsme"); types_mua = rc_stringlist_new(); rc_stringlist_add(types_mua, "needsme"); rc_stringlist_add(types_mua, "usesme"); rc_stringlist_add(types_mua, "beforeme"); } static void svc_start_check(void) { RC_SERVICE state; state = rc_service_state(service); if (in_background) { if (!(state & (RC_SERVICE_INACTIVE | RC_SERVICE_STOPPED))) exit(EXIT_FAILURE); if (rc_yesno(getenv("IN_HOTPLUG"))) rc_service_mark(service, RC_SERVICE_HOTPLUGGED); if (strcmp(runlevel, RC_LEVEL_SYSINIT) == 0) ewarnx("WARNING: %s will be started in the" " next runlevel", applet); } if (exclusive_fd == -1) exclusive_fd = svc_lock(applet); if (exclusive_fd == -1) { if (errno == EACCES) eerrorx("%s: superuser access required", applet); if (state & RC_SERVICE_STOPPING) ewarnx("WARNING: %s is stopping", applet); else ewarnx("WARNING: %s is already starting", applet); } fcntl(exclusive_fd, F_SETFD, fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC); if (state & RC_SERVICE_STARTED) { ewarn("WARNING: %s has already been started", applet); exit(EXIT_SUCCESS); } else if (state & RC_SERVICE_INACTIVE && !in_background) ewarnx("WARNING: %s has already started, but is inactive", applet); rc_service_mark(service, RC_SERVICE_STARTING); hook_out = RC_HOOK_SERVICE_START_OUT; rc_plugin_run(RC_HOOK_SERVICE_START_IN, applet); } static void svc_start_deps(void) { bool first; RC_STRING *svc, *svc2; RC_SERVICE state; int depoptions = RC_DEP_TRACE, n; size_t len; char *p, *tmp; pid_t pid; errno = 0; if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT) depoptions |= RC_DEP_STRICT; if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL)) eerrorx("failed to load deptree"); if (!types_b) setup_types(); services = rc_deptree_depends(deptree, types_b, applet_list, runlevel, 0); if (TAILQ_FIRST(services)) { eerrorn("ERROR: %s needs service(s) ", applet); first = true; TAILQ_FOREACH(svc, services, entries) { if (first) first = false; else fprintf(stderr, ", "); fprintf(stderr, "%s", svc->value); } fprintf(stderr, "\n"); exit(EXIT_FAILURE); } rc_stringlist_free(services); services = NULL; need_services = rc_deptree_depends(deptree, types_n, applet_list, runlevel, depoptions); use_services = rc_deptree_depends(deptree, types_nu, applet_list, runlevel, depoptions); if (!rc_runlevel_starting()) { TAILQ_FOREACH(svc, use_services, entries) { state = rc_service_state(svc->value); /* Don't stop failed services again. * If you remove this check, ensure that the * exclusive file isn't created. */ if (state & RC_SERVICE_FAILED && rc_runlevel_starting()) continue; if (state & RC_SERVICE_STOPPED) { if (dry_run) { printf(" %s", svc->value); continue; } pid = service_start(svc->value); if (!rc_conf_yesno("rc_parallel")) rc_waitpid(pid); } } } if (dry_run) return; /* Now wait for them to start */ services = rc_deptree_depends(deptree, types_nua, applet_list, runlevel, depoptions); /* We use tmplist to hold our scheduled by list */ tmplist = rc_stringlist_new(); TAILQ_FOREACH(svc, services, entries) { state = rc_service_state(svc->value); if (state & RC_SERVICE_STARTED) continue; /* Don't wait for services which went inactive but are * now in starting state which we are after */ if (state & RC_SERVICE_STARTING && state & RC_SERVICE_WASINACTIVE) { if (!rc_stringlist_find(need_services, svc->value) && !rc_stringlist_find(use_services, svc->value)) continue; } if (!svc_wait(svc->value)) eerror("%s: timed out waiting for %s", applet, svc->value); state = rc_service_state(svc->value); if (state & RC_SERVICE_STARTED) continue; if (rc_stringlist_find(need_services, svc->value)) { if (state & RC_SERVICE_INACTIVE || state & RC_SERVICE_WASINACTIVE) { rc_stringlist_add(tmplist, svc->value); } else if (!TAILQ_FIRST(tmplist)) eerrorx("ERROR: cannot start %s as" " %s would not start", applet, svc->value); } } if (TAILQ_FIRST(tmplist)) { /* Set the state now, then unlink our exclusive so that our scheduled list is preserved */ rc_service_mark(service, RC_SERVICE_STOPPED); rc_stringlist_free(use_services); use_services = NULL; len = 0; n = 0; TAILQ_FOREACH(svc, tmplist, entries) { rc_service_schedule_start(svc->value, service); use_services = rc_deptree_depend(deptree, "iprovide", svc->value); TAILQ_FOREACH(svc2, use_services, entries) rc_service_schedule_start(svc2->value, service); rc_stringlist_free(use_services); use_services = NULL; len += strlen(svc->value) + 2; n++; } len += 5; tmp = p = xmalloc(sizeof(char) * len); TAILQ_FOREACH(svc, tmplist, entries) { if (p != tmp) p += snprintf(p, len, ", "); p += snprintf(p, len - (p - tmp), "%s", svc->value); } rc_stringlist_free(tmplist); tmplist = NULL; ewarnx("WARNING: %s is scheduled to start when " "%s has started", applet, tmp); free(tmp); } rc_stringlist_free(tmplist); tmplist = NULL; rc_stringlist_free(services); services = NULL; } static void svc_start_real() { bool started; RC_STRING *svc, *svc2; if (ibsave) setenv("IN_BACKGROUND", ibsave, 1); hook_out = RC_HOOK_SERVICE_START_DONE; rc_plugin_run(RC_HOOK_SERVICE_START_NOW, applet); started = (svc_exec("start", NULL) == 0); if (ibsave) unsetenv("IN_BACKGROUND"); if (rc_service_state(service) & RC_SERVICE_INACTIVE) ewarnx("WARNING: %s has started, but is inactive", applet); else if (!started) eerrorx("ERROR: %s failed to start", applet); rc_service_mark(service, RC_SERVICE_STARTED); exclusive_fd = svc_unlock(applet, exclusive_fd); hook_out = RC_HOOK_SERVICE_START_OUT; rc_plugin_run(RC_HOOK_SERVICE_START_DONE, applet); /* Now start any scheduled services */ services = rc_services_scheduled(service); TAILQ_FOREACH(svc, services, entries) if (rc_service_state(svc->value) & RC_SERVICE_STOPPED) service_start(svc->value); rc_stringlist_free(services); services = NULL; /* Do the same for any services we provide */ if (deptree) { tmplist = rc_deptree_depend(deptree, "iprovide", applet); TAILQ_FOREACH(svc, tmplist, entries) { services = rc_services_scheduled(svc->value); TAILQ_FOREACH(svc2, services, entries) if (rc_service_state(svc2->value) & RC_SERVICE_STOPPED) service_start(svc2->value); rc_stringlist_free(services); services = NULL; } rc_stringlist_free(tmplist); tmplist = NULL; } hook_out = 0; rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); } static void svc_start(void) { if (dry_run) einfon("start:"); else svc_start_check(); if (deps) svc_start_deps(); if (dry_run) printf(" %s\n", applet); else svc_start_real(); } static void svc_stop_check(RC_SERVICE *state) { *state = rc_service_state(service); if (rc_runlevel_stopping() && *state & RC_SERVICE_FAILED) exit(EXIT_FAILURE); if (in_background && !(*state & RC_SERVICE_STARTED) && !(*state & RC_SERVICE_INACTIVE)) exit(EXIT_FAILURE); if (exclusive_fd == -1) exclusive_fd = svc_lock(applet); if (exclusive_fd == -1) { if (errno == EACCES) eerrorx("%s: superuser access required", applet); if (*state & RC_SERVICE_STOPPING) ewarnx("WARNING: %s is already stopping", applet); eerrorx("ERROR: %s stopped by something else", applet); } fcntl(exclusive_fd, F_SETFD, fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC); if (*state & RC_SERVICE_STOPPED) { ewarn("WARNING: %s is already stopped", applet); exit(EXIT_SUCCESS); } rc_service_mark(service, RC_SERVICE_STOPPING); hook_out = RC_HOOK_SERVICE_STOP_OUT; rc_plugin_run(RC_HOOK_SERVICE_STOP_IN, applet); if (!rc_runlevel_stopping()) { if (rc_service_in_runlevel(service, RC_LEVEL_SYSINIT)) ewarn("WARNING: you are stopping a sysinit service"); else if (rc_service_in_runlevel(service, RC_LEVEL_BOOT)) ewarn("WARNING: you are stopping a boot service"); } } static void svc_stop_deps(RC_SERVICE state) { int depoptions = RC_DEP_TRACE; RC_STRING *svc; pid_t pid; if (state & RC_SERVICE_WASINACTIVE) return; errno = 0; if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT) depoptions |= RC_DEP_STRICT; if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL)) eerrorx("failed to load deptree"); if (!types_m) setup_types(); services = rc_deptree_depends(deptree, types_m, applet_list, runlevel, depoptions); tmplist = rc_stringlist_new(); TAILQ_FOREACH_REVERSE(svc, services, rc_stringlist, entries) { state = rc_service_state(svc->value); /* Don't stop failed services again. * If you remove this check, ensure that the * exclusive file isn't created. */ if (state & RC_SERVICE_FAILED && rc_runlevel_stopping()) continue; if (state & RC_SERVICE_STARTED || state & RC_SERVICE_INACTIVE) { if (dry_run) { printf(" %s", svc->value); continue; } svc_wait(svc->value); state = rc_service_state(svc->value); if (state & RC_SERVICE_STARTED || state & RC_SERVICE_INACTIVE) { pid = service_stop(svc->value); if (!rc_conf_yesno("rc_parallel")) rc_waitpid(pid); rc_stringlist_add(tmplist, svc->value); } } } rc_stringlist_free(services); services = NULL; if (dry_run) return; TAILQ_FOREACH(svc, tmplist, entries) { if (rc_service_state(svc->value) & RC_SERVICE_STOPPED) continue; svc_wait(svc->value); if (rc_service_state(svc->value) & RC_SERVICE_STOPPED) continue; if (rc_runlevel_stopping()) { /* If shutting down, we should stop even * if a dependant failed */ if (runlevel && (strcmp(runlevel, RC_LEVEL_SHUTDOWN) == 0 || strcmp(runlevel, RC_LEVEL_SINGLE) == 0)) continue; rc_service_mark(service, RC_SERVICE_FAILED); } eerrorx("ERROR: cannot stop %s as %s " "is still up", applet, svc->value); } rc_stringlist_free(tmplist); tmplist = NULL; /* We now wait for other services that may use us and are * stopping. This is important when a runlevel stops */ services = rc_deptree_depends(deptree, types_mua, applet_list, runlevel, depoptions); TAILQ_FOREACH(svc, services, entries) { if (rc_service_state(svc->value) & RC_SERVICE_STOPPED) continue; svc_wait(svc->value); } rc_stringlist_free(services); services = NULL; } static void svc_stop_real(void) { bool stopped; /* If we're stopping localmount, set LC_ALL=C so that * bash doesn't load anything blocking the unmounting of /usr */ if (strcmp(applet, "localmount") == 0) setenv("LC_ALL", "C", 1); if (ibsave) setenv("IN_BACKGROUND", ibsave, 1); hook_out = RC_HOOK_SERVICE_STOP_DONE; rc_plugin_run(RC_HOOK_SERVICE_STOP_NOW, applet); stopped = (svc_exec("stop", NULL) == 0); if (ibsave) unsetenv("IN_BACKGROUND"); if (!stopped) eerrorx("ERROR: %s failed to stop", applet); if (in_background) rc_service_mark(service, RC_SERVICE_INACTIVE); else rc_service_mark(service, RC_SERVICE_STOPPED); hook_out = RC_HOOK_SERVICE_STOP_OUT; rc_plugin_run(RC_HOOK_SERVICE_STOP_DONE, applet); hook_out = 0; rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); } static void svc_stop(void) { RC_SERVICE state; state = 0; if (dry_run) einfon("stop:"); else svc_stop_check(&state); if (deps) svc_stop_deps(state); if (dry_run) printf(" %s\n", applet); else svc_stop_real(); } static void svc_restart(void) { /* This is hairly and a better way needs to be found I think! * The issue is this - openvpn need net and dns. net can restart * dns via resolvconf, so you could have openvpn trying to restart * dnsmasq which in turn is waiting on net which in turn is waiting * on dnsmasq. * The work around is for resolvconf to restart its services with * --nodeps which means just that. * The downside is that there is a small window when our status is * invalid. * One workaround would be to introduce a new status, * or status locking. */ if (!deps) { RC_SERVICE state = rc_service_state(service); if (state & RC_SERVICE_STARTED || state & RC_SERVICE_INACTIVE) svc_exec("stop", "start"); else svc_exec("start", NULL); return; } if (!(rc_service_state(service) & RC_SERVICE_STOPPED)) { get_started_services(); svc_stop(); if (dry_run) ewarn("Cannot calculate restart start dependencies" " on a dry-run"); } svc_start(); start_services(restart_services); rc_stringlist_free(restart_services); restart_services = NULL; } static bool service_plugable(void) { char *list, *p, *token; bool allow = true, truefalse; char *match = rc_conf_value("rc_hotplug"); if (!match) match = rc_conf_value("rc_plug_services"); if (!match) return false; list = xstrdup(match); p = list; while ((token = strsep(&p, " "))) { if (token[0] == '!') { truefalse = false; token++; } else truefalse = true; if (fnmatch(token, applet, 0) == 0) { allow = truefalse; break; } } #ifdef DEBUG_MEMORY free(list); #endif return allow; } #include "_usage.h" #define getoptstring "dDsvl:Z" getoptstring_COMMON #define extraopts "stop | start | restart | describe | zap" static const struct option longopts[] = { { "debug", 0, NULL, 'd'}, { "dry-run", 0, NULL, 'Z'}, { "ifstarted", 0, NULL, 's'}, { "nodeps", 0, NULL, 'D'}, { "lockfd", 1, NULL, 'l'}, longopts_COMMON }; static const char *const longopts_help[] = { "set xtrace when running the script", "show what would be done", "only run commands when started", "ignore dependencies", "fd of the exclusive lock from rc", longopts_help_COMMON }; #include "_usage.c" int runscript(int argc, char **argv) { bool doneone = false; int retval, opt, depoptions = RC_DEP_TRACE; RC_STRING *svc; char 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 'D': deps = false; break; case 'Z': dry_run = true; break; case_RC_COMMON_GETOPT; } /* If we're changing runlevels and not called by rc then we cannot work with any dependencies */ if (deps && getenv("RC_PID") == NULL && (rc_runlevel_starting() || rc_runlevel_stopping())) deps = false; /* Save the IN_BACKGROUND env flag so it's ONLY passed to the service that is being called and not any dependents */ if (getenv("IN_BACKGROUND")) { ibsave = xstrdup(getenv("IN_BACKGROUND")); in_background = rc_yesno(ibsave); unsetenv("IN_BACKGROUND"); } if (rc_yesno(getenv("IN_HOTPLUG"))) { if (!service_plugable()) eerrorx("%s: not allowed to be hotplugged", applet); in_background = true; } /* Setup a signal handler */ signal_setup(SIGHUP, handle_signal); signal_setup(SIGINT, handle_signal); signal_setup(SIGQUIT, handle_signal); signal_setup(SIGTERM, handle_signal); signal_setup(SIGCHLD, handle_signal); /* Load our plugins */ rc_plugin_load(); applet_list = rc_stringlist_new(); rc_stringlist_add(applet_list, applet); /* Now run each option */ retval = EXIT_SUCCESS; while (optind < argc) { optarg = argv[optind++]; /* Abort on a sighup here */ if (sighup) exit (EXIT_FAILURE); /* Export the command we're running. This is important as we stamp on the restart function now but some start/stop routines still need to behave differently if restarting. */ unsetenv("RC_CMD"); setenv("RC_CMD", optarg, 1); doneone = true; if (strcmp(optarg, "describe") == 0 || strcmp(optarg, "help") == 0 || strcmp(optarg, "depend") == 0) { save = prefix; eprefix(NULL); prefix = NULL; svc_exec(optarg, NULL); eprefix(save); prefix = save; } else if (strcmp(optarg, "ineed") == 0 || strcmp(optarg, "iuse") == 0 || strcmp(optarg, "needsme") == 0 || strcmp(optarg, "usesme") == 0 || strcmp(optarg, "iafter") == 0 || strcmp(optarg, "ibefore") == 0 || strcmp(optarg, "iprovide") == 0) { errno = 0; if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT) depoptions |= RC_DEP_STRICT; if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL)) eerrorx("failed to load deptree"); tmplist = rc_stringlist_new(); rc_stringlist_add(tmplist, optarg); services = rc_deptree_depends(deptree, tmplist, applet_list, runlevel, depoptions); rc_stringlist_free(tmplist); TAILQ_FOREACH(svc, services, entries) printf("%s ", svc->value); printf ("\n"); rc_stringlist_free(services); services = NULL; } else if (strcmp (optarg, "status") == 0) { save = prefix; eprefix(NULL); prefix = NULL; retval = svc_exec("status", NULL); } else { if (strcmp(optarg, "conditionalrestart") == 0 || strcmp(optarg, "condrestart") == 0) { if (rc_service_state(service) & RC_SERVICE_STARTED) svc_restart(); } else if (strcmp(optarg, "restart") == 0) { svc_restart(); } else if (strcmp(optarg, "start") == 0) { svc_start(); } else if (strcmp(optarg, "stop") == 0 || strcmp(optarg, "pause") == 0) { if (strcmp(optarg, "pause") == 0) { ewarn("WARNING: 'pause' is deprecated; please use '--nodeps stop'"); deps = false; } if (deps && in_background) get_started_services(); svc_stop(); if (deps) { if (!in_background && !rc_runlevel_stopping() && rc_service_state(service) & RC_SERVICE_STOPPED) unhotplug(); if (in_background && rc_service_state(service) & RC_SERVICE_INACTIVE) { TAILQ_FOREACH(svc, restart_services, entries) if (rc_service_state(svc->value) & RC_SERVICE_STOPPED) rc_service_schedule_start(service, svc->value); } } } else if (strcmp(optarg, "zap") == 0) { einfo("Manually resetting %s to stopped state", applet); if (!rc_service_mark(applet, RC_SERVICE_STOPPED)) eerrorx("rc_service_mark: %s", strerror(errno)); unhotplug(); } else 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; }