+/* It is most convenient to handle the recheck timeout, as well as
+ * child death, in signal handlers. Our signals all block each other,
+ * and the main program has signals blocked except in sigsuspend, so
+ * we don't need to worry about async-signal-safety, or errno. */
+
+static struct stat baseline_time;
+static pid_t script_child, stage2_pgrp;
+static bool out_of_date;
+static int errpipe;
+
+static void record_baseline_time(void) {
+ stab_mtimenow(&baseline_time);
+}
+
+static void become_pgrp(void) {
+ int r;
+
+ stage2_pgrp = getpid();
+
+ r = setpgid(0,0);
+ if (r) diee("(stage2) setpgid");
+}
+
+static void atexit_handler(void) {
+ int r;
+
+ sighandler_t sigr = signal(SIGTERM,SIG_IGN);
+ if (sigr == SIG_ERR) warninge("(stage2) signal(SIGTERM,SIG_IGN)");
+
+ r = killpg(stage2_pgrp,SIGTERM);
+ if (r) warninge("(stage) killpg failed");
+}
+
+static void alarm_handler(int dummy) {
+ if (out_of_date)
+ /* second timeout */
+ exit(0); /* transfers control to atexit_handler */
+
+ out_of_date = check_garbage_vs(&baseline_time);
+ queue_alarm();
+}
+
+static void child_handler(int dummy) {
+ for (;;) {
+ int status;
+ pid_t got = waitpid(-1, &status, WNOHANG);
+ if (got == (pid_t)-1) diee("(stage2) waitpid");
+ if (got != script_child) {
+ warning("(stage2) waitpid got status %d for unknown child [%lu]",
+ status, (unsigned long)got);
+ continue;
+ }
+ if (WIFEXITED(status)) {
+ int v = WEXITSTATUS(status);
+ if (v) warning("program failed with error exit status %d", v);
+ exit(status);
+ } else if (WIFSIGNALED(status)) {
+ int s = WTERMSIG(status);
+ warning("program died due to fatal signal %s%s",
+ strsignal(s), WCOREDUMP(status) ? " (core dumped" : "");
+ assert(status & 0xff);
+ exit(status & 0xff);
+ } else {
+ die("program failed with crazy wait status %#x", status);
+ }
+ }
+ exit(127);
+}
+
+static void setup_handlers(void) {
+ struct sigaction sa;
+ int r;
+
+ r = atexit(atexit_handler);
+ if (r) diee("(stage2) atexit");
+
+ sigemptyset(&sa.sa_mask);
+ sigaddset(&sa.sa_mask, SIGALRM);
+ sigaddset(&sa.sa_mask, SIGCHLD);
+ sa.sa_flags = 0;
+
+ r = sigprocmask(SIG_BLOCK, &sa.sa_mask, 0);
+ if (r) diee("(stage2) sigprocmask(SIG_BLOCK,)");
+
+ sa.sa_handler = alarm_handler;
+ r = sigaction(SIGALRM, &sa, 0);
+ if (r) diee("(stage2) sigaction SIGALRM");
+
+ sa.sa_flags |= SA_NOCLDSTOP;
+ sa.sa_handler = child_handler;
+ r = sigaction(SIGCHLD, &sa, 0);
+ if (r) diee("(stage2) sigaction SIGCHLD");
+}
+
+static void spawn_script(void) {
+ int r;
+ int errpipes[2];
+
+ r = pipe(errpipes);
+ if (r) diee("(stage2) pipe");
+
+ script_child = fork();
+ if (script_child == (pid_t)-1) diee("(stage2) fork");
+ if (!script_child) {
+ r = close(errpipes[0]);
+ if (r) diee("(stage2 child) close errpipes[0]");
+
+ r = dup2(errpipes[1], 2);
+ if (r != 2) diee("(stage2 child) dup2 stderr");
+
+ execlp(interp,
+ interp, script, (char*)0);
+ diee("(stage2) exec interpreter (`%s', for `%s')\n",interp,script);
+ }
+
+ r = close(errpipes[1]);
+ if (r) diee("(stage2) close errpipes[1]");
+
+ errpipe = errpipes[0];
+ r = fcntl(errpipe, F_SETFL, O_NONBLOCK);
+ if (r) diee("(stage2) set errpipe nonblocking");
+}
+
+static void queue_alarm(void) {
+ alarm(check_interval);
+}
+
+static void start_logging(void) {
+ int r;
+
+ openlog(script, LOG_NOWAIT|LOG_PID, LOG_USER);
+ logging = 1;
+ r = dup2(1,2);
+ if (r!=2) diee("dup2 stdout to stderr");
+}
+
+static void errpipe_readable(void) {
+ static char buf[1024];
+ static int pending;
+
+ /* %: does not contain newlines
+ * _: empty (garbage)
+ */
+
+ /* %%%%%%%%%%%__________________ */
+ /* ^ pending */
+
+ for (;;) {
+ int avail = sizeof(buf) - pending;
+ ssize_t got = read(errpipe, buf+pending, avail);
+ if (got==-1) {
+ if (errno==EINTR) continue;
+ else if (errno==EWOULDBLOCK || errno==EAGAIN) return;
+ else diee("(stage2) errpipe read");
+ got = 0;
+ } else if (got==0) {
+ warning("program closed its stderr fd");
+ errpipe = -1;
+ return;
+ }
+ int scanned = pending;
+ pending += got;
+ int eaten = 0;
+ for (;;) {
+ const char *newline = memchr(buf+scanned, '\n', pending-scanned);
+ int printupto, eat;
+ if (newline) {
+ printupto = newline-buf;
+ eat = printupto + 1;
+ } else if (!eaten && pending==sizeof(buf)) { /* overflow */
+ printupto = pending;
+ eat = printupto;
+ } else {
+ break;
+ }
+ syslog(LOG_ERR,"stderr: %.*s", printupto-eaten, buf+eaten);
+ eaten += eat;
+ scanned = eaten;
+ }
+ pending -= eaten;
+ memmove(buf, buf+eaten, pending);
+ }
+}
+
+static void await_something(void) {
+ int r;
+ sigset_t mask;
+ sigemptyset(&mask);
+
+ for (;;) {
+ fd_set rfds;
+ FD_ZERO(&rfds);
+ if (errpipe >= 0)
+ FD_SET(errpipe, &rfds);
+ r = pselect(errpipe+1, &rfds,0,0, 0, &mask);
+ if (r==-1) {
+ if (errno != EINTR) diee("(stage2) sigsuspend");
+ continue;
+ }
+ assert(r>0);
+ assert(FD_ISSET(errpipe, &rfds));
+ errpipe_readable();
+ }