/* * runscript.c * Handle launching of init scripts. */ /* * Copyright 2007-2008 Roy Marples * All rights reserved * 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 #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" /* usecs to wait while we poll the fifo */ #define WAIT_INTERVAL 20000000 /* max secs to wait until a service comes up */ #define WAIT_MAX 300 #define ONE_SECOND 1000000000 static const char *applet = NULL; static RC_STRINGLIST *applet_list = NULL; static RC_STRINGLIST *restart_services = NULL; static RC_STRINGLIST *need_services = NULL; static RC_STRINGLIST *use_services = NULL; static RC_STRINGLIST *services = NULL; static RC_STRINGLIST *tmplist = NULL; static char *service = NULL; static char exclusive[PATH_MAX] = { '\0' }; static char mtime_test[PATH_MAX] = { '\0' }; static RC_DEPTREE *deptree = NULL; static char *runlevel = NULL; static bool sighup = false; static char *ibsave = NULL; static bool in_background = false; static RC_HOOK hook_out = 0; static pid_t service_pid = 0; static char *prefix = NULL; static bool prefix_locked = false; static int signal_pipe[2] = { -1, -1 }; static int master_tty = -1; static RC_STRINGLIST *types_b = NULL; static RC_STRINGLIST *types_n = NULL; static RC_STRINGLIST *types_nu = NULL; static RC_STRINGLIST *types_nua = NULL; static RC_STRINGLIST *types_m = NULL; 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); 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 time_t get_mtime(const char *pathname, bool follow_link) { struct stat buf; int retval; if (! pathname) return 0; retval = follow_link ? stat(pathname, &buf) : lstat(pathname, &buf); if (! retval) return buf.st_mtime; errno = 0; return 0; } static const char *const tests[] = { "starting", "started", "stopping", "inactive", "wasinactive", NULL }; static bool in_control() { char file[PATH_MAX]; time_t m; time_t mtime; int i = 0; if (sighup) return false; if (! *mtime_test || ! exists(mtime_test)) return false; if (rc_service_state(applet) & RC_SERVICE_STOPPED) return false; if (! (mtime = get_mtime(mtime_test, false))) return false; while (tests[i]) { snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", tests[i], applet); if (exists(file)) { m = get_mtime(file, false); if (mtime < m && m != 0) return false; } i++; } return true; } static void uncoldplug() { char file[PATH_MAX]; snprintf(file, sizeof(file), RC_SVCDIR "/coldplugged/%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) { 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 || ! in_control()) 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); } if (*exclusive) { unlink(exclusive); *exclusive = '\0'; } } static void cleanup(void) { restore_state(); if (! rc_in_plugin) { if (prefix_locked) unlink(PREFIX_LOCK); 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_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_plugin_unload(); 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); if (*mtime_test && ! rc_in_plugin) unlink(mtime_test); } static int write_prefix(const char *buffer, size_t bytes, bool *prefixed) { unsigned int i; const char *ec = ecolor(ECOLOR_HILITE); const char *ec_normal = ecolor(ECOLOR_NORMAL); ssize_t ret = 0; int fd = fileno(stdout); for (i = 0; i < bytes; i++) { /* We don't prefix escape codes, like eend */ if (buffer[i] == '\033') *prefixed = true; 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); } return ret; } static bool svc_exec(const char *arg1, const char *arg2) { bool execok; int fdout = fileno(stdout); struct termios tt; struct winsize ws; int i; int flags = 0; fd_set rset; int s; char *buffer; size_t bytes; bool prefixed = false; int selfd; int slave_tty; /* 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, 1); dup2(slave_tty, 2); } 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_LIBDIR "/sh/runscript.sh", RC_LIBDIR "/sh/runscript.sh", service, arg1, arg2, (char *) NULL); eerror("%s: exec `" RC_LIBDIR "/sh/runscript.sh': %s", service, strerror(errno)); _exit(EXIT_FAILURE); } } selfd = MAX(master_tty, signal_pipe[0]) + 1; buffer = xmalloc(sizeof(char) * BUFSIZ); for (;;) { FD_ZERO(&rset); FD_SET(signal_pipe[0], &rset); if (master_tty >= 0) FD_SET(master_tty, &rset); if ((s = select(selfd, &rset, NULL, NULL, NULL)) == -1) { if (errno != EINTR) { eerror("%s: select: %s", service, strerror(errno)); break; } } if (s > 0) { if (master_tty >= 0 && FD_ISSET(master_tty, &rset)) { bytes = read(master_tty, buffer, BUFSIZ); write_prefix(buffer, bytes, &prefixed); } /* Only SIGCHLD signals come down this pipe */ if (FD_ISSET(signal_pipe[0], &rset)) break; } } free(buffer); close(signal_pipe[0]); close(signal_pipe[1]); signal_pipe[0] = signal_pipe[1] = -1; if (master_tty >= 0) { /* Why did we do this? */ /* signal (SIGWINCH, SIG_IGN); */ close(master_tty); master_tty = -1; } execok = rc_waitpid(service_pid) == 0 ? true : false; service_pid = 0; return execok; } static bool svc_wait(const char *svc) { char fifo[PATH_MAX]; struct timespec ts; int nloops = WAIT_MAX * (ONE_SECOND / WAIT_INTERVAL); bool retval = false; bool forever = false; RC_STRINGLIST *keywords; RC_STRING *s; /* Some services don't have a timeout, like fsck */ keywords = rc_deptree_depend(deptree, svc, "keyword"); if (keywords) { TAILQ_FOREACH(s, keywords, entries) { if (strcmp (s->value, "notimeout") == 0) { forever = true; break; } } rc_stringlist_free (keywords); } snprintf(fifo, sizeof(fifo), RC_SVCDIR "/exclusive/%s", basename_c(svc)); ts.tv_sec = 0; ts.tv_nsec = WAIT_INTERVAL; while (nloops) { if (! exists(fifo)) { retval = true; break; } if (nanosleep(&ts, NULL) == -1) { if (errno != EINTR) break; } if (! forever) nloops --; } if (! exists(fifo)) retval = true; return retval; } static RC_SERVICE svc_status(void) { char status[10]; int (*e) (const char *fmt, ...) = &einfo; RC_SERVICE state = rc_service_state(service); if (state & RC_SERVICE_STOPPING) { snprintf(status, sizeof(status), "stopping"); e = &ewarn; } else if (state & RC_SERVICE_STARTING) { snprintf(status, sizeof(status), "starting"); e = &ewarn; } else if (state & RC_SERVICE_INACTIVE) { snprintf(status, sizeof(status), "inactive"); e = &ewarn; } else if (state & RC_SERVICE_STARTED) { errno = 0; if (_rc_can_find_pids() && rc_service_daemons_crashed(service) && errno != EACCES) { snprintf(status, sizeof(status), "crashed"); e = &eerror; } else snprintf(status, sizeof(status), "started"); } else snprintf(status, sizeof(status), "stopped"); e("status: %s", status); return state; } static void make_exclusive(void) { /* We create a fifo so that other services can wait until we complete */ if (! *exclusive) snprintf(exclusive, sizeof(exclusive), RC_SVCDIR "/exclusive/%s", applet); if (mkfifo(exclusive, 0600) != 0 && errno != EEXIST && (errno != EACCES || geteuid () == 0)) eerrorx ("%s: unable to create fifo `%s': %s", applet, exclusive, strerror(errno)); snprintf(mtime_test, sizeof(mtime_test), RC_SVCDIR "/exclusive/%s.%d", applet, getpid()); if (exists(mtime_test) && unlink(mtime_test) != 0) { eerror("%s: unlink `%s': %s", applet, mtime_test, strerror(errno)); *mtime_test = '\0'; return; } if (symlink(service, mtime_test) != 0) { eerror("%s: symlink `%s' to `%s': %s", applet, service, mtime_test, strerror(errno)); *mtime_test = '\0'; } } static void unlink_mtime_test(void) { if (unlink(mtime_test) != 0) eerror("%s: unlink `%s': %s", applet, mtime_test, strerror(errno)); *mtime_test = '\0'; } 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); if (tmp) { if (restart_services) { TAILQ_CONCAT(restart_services, tmp, entries); free(tmp); } else restart_services = 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 bool in_list(RC_STRINGLIST *list, char *string) { RC_STRING *s; if (! list) return false; TAILQ_FOREACH(s, list, entries) if (strcmp(s->value, string) == 0) return true; return false; } static void svc_start(bool deps) { bool started; bool background = false; RC_STRING *svc; RC_STRING *svc2; int depoptions = RC_DEP_TRACE; RC_SERVICE state; bool first; int n; size_t len; char *p; char *tmp; state = rc_service_state(service); if (rc_yesno(getenv("IN_HOTPLUG")) || in_background) { if (! state & RC_SERVICE_INACTIVE && ! state & RC_SERVICE_STOPPED) exit(EXIT_FAILURE); background = true; } if (state & RC_SERVICE_STARTED) { ewarn("WARNING: %s has already been started", applet); return; } else if (state & RC_SERVICE_STARTING) ewarnx("WARNING: %s is already starting", applet); else if (state & RC_SERVICE_STOPPING) ewarnx("WARNING: %s is stopping", applet); else if (state & RC_SERVICE_INACTIVE && ! background) ewarnx("WARNING: %s has already started, but is inactive", applet); if (! rc_service_mark(service, RC_SERVICE_STARTING)) { if (errno == EACCES) eerrorx("%s: superuser access required", applet); eerrorx("ERROR: %s has been started by something else", applet); } make_exclusive(); hook_out = RC_HOOK_SERVICE_START_OUT; rc_plugin_run(RC_HOOK_SERVICE_START_IN, applet); errno = 0; if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT) depoptions |= RC_DEP_STRICT; if (deps) { if (! deptree && ((deptree = _rc_deptree_load(NULL)) == NULL)) eerrorx("failed to load deptree"); if (! types_b) setup_types(); services = rc_deptree_depends(deptree, types_b, applet_list, runlevel, 0); if (services && TAILQ_FIRST(services)) { eerrorn("ERROR: `%s' needs ", applet); first = true; TAILQ_FOREACH(svc, services, entries) { if (first) first = false; else fprintf(stderr, ", "); fprintf(stderr, "%s", svc->value); } 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() && use_services) 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) { pid_t pid = service_start(svc->value); if (! rc_conf_yesno("rc_parallel")) rc_waitpid(pid); } } /* Now wait for them to start */ services = rc_deptree_depends(deptree, types_nua, applet_list, runlevel, depoptions); if (services) { /* We use tmplist to hold our scheduled by list */ tmplist = NULL; 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 (!in_list(need_services, svc->value) && !in_list(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 (in_list(need_services, svc->value)) { if (state & RC_SERVICE_INACTIVE || state & RC_SERVICE_WASINACTIVE) { if (! tmplist) tmplist = rc_stringlist_new(); rc_stringlist_add(tmplist, svc->value); } else eerrorx("ERROR: cannot start %s as" " %s would not start", applet, svc->value); } } if (tmplist && 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); unlink_mtime_test(); 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); if (use_services) { 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(services); services = NULL; } } 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); if (ibsave) unsetenv("IN_BACKGROUND"); if (in_control()) { if (! started) eerrorx("ERROR: %s failed to start", applet); } else { if (rc_service_state(service) & RC_SERVICE_INACTIVE) ewarnx("WARNING: %s has started, but is inactive", applet); else ewarnx("WARNING: %s not under our control, aborting", applet); } rc_service_mark(service, RC_SERVICE_STARTED); unlink_mtime_test(); hook_out = RC_HOOK_SERVICE_START_OUT; rc_plugin_run(RC_HOOK_SERVICE_START_DONE, applet); unlink(exclusive); /* Now start any scheduled services */ services = rc_services_scheduled(service); if (services) { 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 */ tmplist = rc_deptree_depend(deptree, "iprovide", applet); if (tmplist) { TAILQ_FOREACH(svc, tmplist, entries) { services = rc_services_scheduled(svc->value); if (services) { 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_stop(bool deps) { bool stopped; RC_SERVICE state = rc_service_state(service); int depoptions = RC_DEP_TRACE; RC_STRING *svc; if (rc_runlevel_stopping() && state & RC_SERVICE_FAILED) exit (EXIT_FAILURE); if (rc_yesno(getenv("IN_HOTPLUG")) || in_background) if (! (state & RC_SERVICE_STARTED) && ! (state & RC_SERVICE_INACTIVE)) exit (EXIT_FAILURE); if (state & RC_SERVICE_STOPPED) { ewarn("WARNING: %s is already stopped", applet); return; } else if (state & RC_SERVICE_STOPPING) ewarnx("WARNING: %s is already stopping", applet); if (! rc_service_mark(service, RC_SERVICE_STOPPING)) { if (errno == EACCES) eerrorx("%s: superuser access required", applet); eerrorx("ERROR: %s has been stopped by something else", applet); } make_exclusive(); hook_out = RC_HOOK_SERVICE_STOP_OUT; rc_plugin_run(RC_HOOK_SERVICE_STOP_IN, applet); if (! rc_runlevel_stopping() && rc_service_in_runlevel(service, RC_LEVEL_BOOT)) ewarn ("WARNING: you are stopping a boot service"); if (deps && ! (state & RC_SERVICE_WASINACTIVE)) { errno = 0; if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT) depoptions |= RC_DEP_STRICT; if (! deptree && ((deptree = _rc_deptree_load(NULL)) == NULL)) eerrorx ("failed to load deptree"); if (! types_m) setup_types(); tmplist = NULL; services = rc_deptree_depends(deptree, types_m, applet_list, runlevel, depoptions); if (services) { 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) { svc_wait(svc->value); state = rc_service_state(svc->value); if (state & RC_SERVICE_STARTED || state & RC_SERVICE_INACTIVE) { pid_t pid = service_stop(svc->value); if (! rc_conf_yesno("rc_parallel")) rc_waitpid(pid); if (! tmplist) tmplist = rc_stringlist_new(); rc_stringlist_add(tmplist, svc->value); } } } rc_stringlist_free(services); services = NULL; } if (tmplist) { 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)) { 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_REBOOT) == 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); if (services) { 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; } } /* 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); if (ibsave) unsetenv("IN_BACKGROUND"); if (! in_control()) ewarnx("WARNING: %s not under our control, aborting", applet); 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); unlink_mtime_test(); hook_out = RC_HOOK_SERVICE_STOP_OUT; rc_plugin_run(RC_HOOK_SERVICE_STOP_DONE, applet); unlink(exclusive); hook_out = 0; rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); } static void svc_restart(bool deps) { /* 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 it's 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(deps); } svc_start(deps); start_services(restart_services); rc_stringlist_free(restart_services); restart_services = NULL; } #include "_usage.h" #define getoptstring "dDsv" getoptstring_COMMON #define extraopts "stop | start | restart | describe | zap" static const struct option longopts[] = { { "debug", 0, NULL, 'd'}, { "ifstarted", 0, NULL, 's'}, { "nodeps", 0, NULL, 'D'}, longopts_COMMON }; static const char * const longopts_help[] = { "set xtrace when running the script", "only run commands when started", "ignore dependencies", longopts_help_COMMON }; #include "_usage.c" int runscript(int argc, char **argv) { bool deps = true; bool doneone = false; char pidstr[10]; int retval; int opt; RC_STRING *svc; char path[PATH_MAX]; char lnk[PATH_MAX]; size_t l = 0; size_t ll; char *dir, *save; const char *file; int depoptions = RC_DEP_TRACE; 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 mulitplexed 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); dir = dirname(lnk); if (strcmp(dir, save) == 0) file = basename_c(argv[1]); else file = basename_c(lnk); free(save); dir = dirname(path); } 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); } } 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 */ chdir("/"); #ifdef __linux__ /* coldplug events can trigger init scripts, but we don't want to run * them until after rc sysinit has completed so we punt them to the * boot runlevel */ if (exists("/dev/.rcsysinit")) { eerror("%s: cannot run until sysvinit completes", applet); if (mkdir("/dev/.rcboot", 0755) != 0 && errno != EEXIST) eerrorx("%s: mkdir `/dev/.rcboot': %s", applet, strerror(errno)); snprintf(exclusive, sizeof(exclusive), "/dev/.rcboot/%s", applet); symlink(service, exclusive); exit (EXIT_FAILURE); } #endif 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; } /* 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 /* Punt the first arg as it's 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 's': if (! (rc_service_state(service) & RC_SERVICE_STARTED)) exit(EXIT_FAILURE); break; case 'D': deps = false; break; case_RC_COMMON_GETOPT } /* 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 (! rc_conf_yesno("rc_hotplug") || ! service_plugable(applet)) eerrorx("%s: not allowed to be hotplugged", applet); } /* 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) { save = prefix; eprefix(NULL); prefix = NULL; svc_exec(optarg, NULL); eprefix(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(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); tmplist = NULL; if (services) { TAILQ_FOREACH(svc, services, entries) printf("%s ", svc->value); printf ("\n"); rc_stringlist_free(services); services = NULL; } } else if (strcmp (optarg, "status") == 0) { RC_SERVICE r = svc_status(); retval = (int) r; if (retval & RC_SERVICE_STARTED) retval = 0; } else { if (strcmp(optarg, "conditionalrestart") == 0 || strcmp(optarg, "condrestart") == 0) { if (rc_service_state(service) & RC_SERVICE_STARTED) svc_restart(deps); } else if (strcmp(optarg, "restart") == 0) { svc_restart (deps); } else if (strcmp(optarg, "start") == 0) { svc_start(deps); } else if (strcmp(optarg, "stop") == 0) { if (deps && in_background) get_started_services(); svc_stop(deps); if (deps) { if (! in_background && ! rc_runlevel_stopping() && rc_service_state(service) & RC_SERVICE_STOPPED) uncoldplug(); 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)); uncoldplug(); } 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; }