/* ISC license. */

#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>

#include <skalibs/posixplz.h>
#include <skalibs/uint32.h>
#include <skalibs/types.h>
#include <skalibs/allreadwrite.h>
#include <skalibs/envexec.h>
#include <skalibs/tai.h>
#include <skalibs/iopause.h>
#include <skalibs/devino.h>
#include <skalibs/cspawn.h>
#include <skalibs/djbunix.h>
#include <skalibs/direntry.h>
#include <skalibs/sig.h>
#include <skalibs/selfpipe.h>
#include <skalibs/bitarray.h>
#include <skalibs/genset.h>
#include <skalibs/avltreen.h>

#include <s6/config.h>
#include <s6/supervise.h>

#include <skalibs/posixishard.h>

#define USAGE "s6-svscan [ -C services_max ] [ -L name_max ] [ -t timeout ] [ -d notif ] [ -X consoleholder ] scandir"
#define dieusage() strerr_dieusage(100, USAGE)

#define CTL S6_SVSCAN_CTLDIR "/control"
#define LCK S6_SVSCAN_CTLDIR "/lock"
#define FINISH_PROG S6_SVSCAN_CTLDIR "/finish"
#define CRASH_PROG S6_SVSCAN_CTLDIR "/crash"
#define SIGNAL_PROG S6_SVSCAN_CTLDIR "/SIG"
#define SIGNAL_PROG_LEN (sizeof(SIGNAL_PROG) - 1)
#define SPECIAL_LOGGER_SERVICE "s6-svscan-log"

enum gola_e
{
  GOLA_MAX,
  GOLA_NAMEMAX,
  GOLA_TIMEOUT,
  GOLA_NOTIF,
  GOLA_CONSOLE,
  GOLA_N
} ;

typedef struct service_s service, *service_ref ;
struct service_s
{
  devino devino ;
  pid_t pid ;
  tain start ;
  int p ;
  uint32_t peer ;
} ;

struct flags_s
{
  uint8_t cont : 1 ;
  uint8_t waitall : 1 ;
} ;

static unsigned int consoleholder = 0 ;
static struct flags_s flags = { .cont = 1, .waitall = 0 } ;

static uint32_t namemax = 251 ;
static char *names ;
#define NAME(i) (names + (i) * (namemax + 5))

static genset *services ;
#define SERVICE(i) genset_p(service, services, (i))
static uint32_t max = 1000 ;
static uint32_t special ;

static avltreen *by_pid ;
static avltreen *by_devino ;
static char *active ;

static tain scan_deadline = TAIN_EPOCH ;
static tain start_deadline = TAIN_INFINITE ;
static tain scantto = TAIN_INFINITE_RELATIVE ;


 /* Tree management */

static void *bydevino_dtok (uint32_t d, void *aux)
{
  genset *g = aux ;
  return &genset_p(service, g, d)->devino ;
}

static int bydevino_cmp (void const *a, void const *b, void *aux)
{
  (void)aux ;
  return devino_cmp(a, b) ;
}

static void *bypid_dtok (uint32_t d, void *aux)
{
  genset *g = aux ;
  return &genset_p(service, g, d)->pid ;
}

static int bypid_cmp (void const *a, void const *b, void *aux)
{
  (void)aux ;
  pid_t const *aa = a ;
  pid_t const *bb = b ;
  return *aa < *bb ? -1 : *aa > *bb ;
}


 /* On-exit utility */

static void restore_console (void)
{
  if (consoleholder)
  {
    fd_move(2, consoleholder) ;
    if (fd_copy(1, 2) < 0) strerr_warnwu1sys("restore stdout") ;
  }
}

static void panicnosp (char const *) gccattr_noreturn ;
static void panicnosp (char const *errmsg)
{
  char const *eargv[2] = { CRASH_PROG, 0 } ;
  strerr_warnwu1sys(errmsg) ;
  strerr_warnw2x("executing into ", eargv[0]) ;
  execv(eargv[0], (char *const *)eargv) ;
  strerr_dieexec(errno == ENOENT ? 127 : 126, eargv[0]) ;
}

static void panic (char const *) gccattr_noreturn ;
static void panic (char const *errmsg)
{
  int e = errno ;
  selfpipe_finish() ;
  restore_console() ;
  errno = e ;
  panicnosp(errmsg) ;
}

static int close_pipes_iter (void *data, void *aux)
{
  service *sv = data ;
  if (sv->p >= 0) close(sv->p) ;
  (void)aux ;
  return 1 ;
}

static inline void close_pipes (void)
{
  genset_iter(services, &close_pipes_iter, 0) ;
  if (special < max)
  {
    fd_close(1) ;
    if (open2("/dev/null", O_WRONLY) < 0)
      strerr_warnwu1sys("open /dev/null") ;
    if (fd_copy(2, 1) == -1)
      strerr_warnwu1sys("redirect stderr to /dev/null") ;
  }
}

static inline void waitthem (void)
{
  while (avltreen_len(by_pid))
  {
    int wstat ;
    pid_t pid = wait_nointr(&wstat) ;
    if (pid < 0)
    {
      strerr_warnwu1sys("wait for all s6-supervise processes") ;
      break ;
    }
    avltreen_delete(by_pid, &pid) ;
  }
}


 /* Misc utility */

 /*
    what:
     1 -> kill all services
     2 -> kill inactive services
     4 -> send a SIGTERM to loggers instead of SIGHUP
     8 -> reap
    16 -> delay killing until the next scan
 */

static inline int is_logger (uint32_t i)
{
  return !!strchr(NAME(i), '/') ;
}

 /* Triggered actions: config */

static inline void chld (unsigned int *what)
{
  *what |= 8 ;
}

static inline void alrm (unsigned int *what)
{
  *what |= 16 ;
  tain_copynow(&scan_deadline) ;
}

static inline void abrt (void)
{
  flags.cont = 0 ;
  flags.waitall = 0 ;
}

static void hup (unsigned int *what)
{
  *what |= 18 ;
  tain_copynow(&scan_deadline) ;
}

static void term (unsigned int *what)
{
  flags.cont = 0 ;
  flags.waitall = 1 ;
  *what |= 3 ;
}

static void quit (unsigned int *what)
{
  flags.cont = 0 ;
  flags.waitall = 1 ;
  *what |= 7 ;
}

static void handle_signals (unsigned int *what)
{
  for (;;)
  {
    int sig = selfpipe_read() ;
    switch (sig)
    {
      case -1 : panic("selfpipe_read") ;
      case 0 : return ;
      case SIGCHLD : chld(what) ; break ;
      case SIGALRM : alrm(what) ; break ;
      case SIGABRT : abrt() ; break ;
      default :
      {
        int usebuiltin = 1 ;
        char const *name = sig_name(sig) ;
        size_t len = strlen(name) ;
        char fn[SIGNAL_PROG_LEN + len + 1] ;
        char const *const newargv[2] = { fn, 0 } ;
        memcpy(fn, SIGNAL_PROG, SIGNAL_PROG_LEN) ;
        memcpy(fn + SIGNAL_PROG_LEN, name, len + 1) ;
        if (access(newargv[0], X_OK) == 0)  /* avoids a spawn, don't care about the toctou */
        {
          if (cspawn(newargv[0], newargv, (char const **)environ, CSPAWN_FLAGS_SELFPIPE_FINISH, 0, 0))
            usebuiltin = 0 ;
          else if (errno != ENOENT) strerr_warnwu2sys("spawn ", newargv[0]) ;
        }
        if (usebuiltin) switch (sig)
        {
          case SIGHUP : hup(what) ; break ;
          case SIGINT :
          case SIGTERM : term(what) ; break ;
          case SIGQUIT : quit(what) ; break ;
        }
      }
    }
  }
}

static void handle_control (int fd, unsigned int *what)
{
  for (;;)
  {
    char c ;
    ssize_t r = sanitize_read(fd_read(fd, &c, 1)) ;
    if (r < 0) panic("read control pipe") ;
    else if (!r) break ;
    switch (c)
    {
      case 'z' : chld(what) ; break ;
      case 'a' : alrm(what) ; break ;
      case 'b' : abrt() ; break ;
      case 'h' : hup(what) ; break ;
      case 'i' :
      case 't' : term(what) ; break ;
      case 'q' : quit(what) ; break ;
      case 'n' : *what |= 2 ; break ;
      case 'N' : *what |= 6 ; break ;
      default :
      {
        char s[2] = { c, 0 } ;
        strerr_warnw2x("received unknown control command: ", s) ;
      }
    }
  }
}


 /* Triggered action: killer */

static int killthem_iter (void *data, void *aux)
{
  service *sv = data ;
  unsigned int *what = aux ;
  uint32_t i = sv - SERVICE(0) ;
  if ((*what & 1 || !bitarray_peek(active, i)) && sv->pid)
    kill(sv->pid, *what & (2 << (i == special || is_logger(i))) ? SIGTERM : SIGHUP) ;
  return 1 ;
}

static inline void killthem (unsigned int *what)
{
  if (*what & 16 || !(*what & 7)) return ;
  genset_iter(services, &killthem_iter, what) ;
  *what &= ~7 ;
}


 /* Triggered action: reaper */

 /*
   sv->p values:
   0+ : this end of the pipe
   -1 : not a logged service
   -2 : inactive and peer dead, do not reactivate
   -3 : reactivation wanted, trigger rescan on death
 */

static void remove_service (service *sv)
{
  if (sv->peer < max)
  {
    service *peer = SERVICE(sv->peer) ;
    if (peer->p >= 0)
    {
      close(peer->p) ;
      peer->p = -2 ;
    }
    peer->peer = max ;
  }
  if (sv->p == -3) tain_earliest1(&scan_deadline, &sv->start) ;
  else if (sv->p >= 0) close(sv->p) ;
  avltreen_delete(by_devino, &sv->devino) ;
  genset_delete(services, sv - SERVICE(0)) ;
}

static void reap (unsigned int *what)
{
  if (!(*what & 8)) return ;
  *what &= ~8 ;
  for (;;)
  {
    uint32_t i ;
    int wstat ;
    pid_t pid = wait_nohang(&wstat) ;
    if (pid < 0)
      if (errno != ECHILD) panic("wait_nohang") ;
      else break ;
    else if (!pid) break ;
    else if (avltreen_search(by_pid, &pid, &i))
     {
      service *sv = SERVICE(i) ;
      avltreen_delete(by_pid, &pid) ;
      sv->pid = 0 ;
      if (bitarray_peek(active, i)) tain_earliest1(&start_deadline, &sv->start) ;
      else remove_service(sv) ;
    }
  }
}


 /*
    On-timeout action: scanner.
    (This can be triggered, but the trigger just sets the timeout to 0.)
    It's on-timeout because it can fail and get rescheduled for later.
 */

static int check (char const *name, uint32_t prod, char *act)
{
  struct stat st ;
  devino di ;
  uint32_t i ;
  service *sv ;
  if (stat(name, &st) == -1)
  {
    if (prod < max && errno == ENOENT)
    {
      if (SERVICE(prod)->peer < max)
        strerr_warnw3x("logger for service ", NAME(prod), " has been moved") ;
      return max ;
    }
    strerr_warnwu2sys("stat ", name) ;
    return -4 ;
  }
  if (!S_ISDIR(st.st_mode)) return max ;
  di.dev = st.st_dev ;
  di.ino = st.st_ino ;
  if (avltreen_search(by_devino, &di, &i))
  {
    sv = SERVICE(i) ;
    if (sv->peer < max)
    {
      if (prod < max && prod != sv->peer)
      {
        strerr_warnw3x("old service ", name, " still exists, waiting") ;
        return -10 ;
      }
      if (sv->p == -1)
      {
        sv->p = -2 ;
        return i ;
      }
    }
  }
  else
  {
    i = genset_new(services) ;
    if (i >= max)
    {
      strerr_warnwu3x("start supervisor for ", name, ": too many services") ;
      return -60 ;
    }
    sv = SERVICE(i) ;
    sv->devino = di ;
    sv->pid = 0 ;
    tain_copynow(&sv->start) ;
    tain_copynow(&start_deadline) ; /* XXX: may cause a superfluous start if logger fails, oh well */
    if (prod >= max)
    {
      sv->peer = max ;
      sv->p = -1 ;
      if (special >= max && !strcmp(name, SPECIAL_LOGGER_SERVICE)) special = i ;
    }
    else
    {
      int p[2] ;
      if (pipecoe(p) == -1)
      {
        strerr_warnwu2sys("create pipe for ", name) ;
        genset_delete(services, i) ;
        return -3 ;
      }
      sv->peer = prod ;
      sv->p = p[0] ;
      SERVICE(prod)->peer = i ;
      SERVICE(prod)->p = p[1] ;
    }
    avltreen_insert(by_devino, i) ;
  }
  strcpy(NAME(i), name) ;
  bitarray_set(act, i) ;
  return i ;
}

static void set_scan_timeout (unsigned int n)
{
  tain a ;
  tain_addsec_g(&a, n) ;
  tain_earliest1(&scan_deadline, &a) ;
}

static int remove_deadinactive_iter (void *data, void *aux)
{
  service *sv = data ;
  if (!bitarray_peek(active, sv - SERVICE(0)) && !sv->pid) remove_service(sv) ;
  (void)aux ;
  return 1 ;
}

static inline void initial_cleanup (void)
{
  DIR *dir = opendir(".") ;
  if (!dir) strerr_diefu1sys(111, "opendir .") ;
  for (;;)
  {
    direntry *d ;
    errno = 0 ;
    d = readdir(dir) ;
    if (!d) break ;
    if (d->d_name[0] == '.') continue ;
    if (access(d->d_name, X_OK) == -1 && errno == ENOENT) unlink_void(d->d_name) ;
  }
  if (errno) strerr_diefu1sys(111, "readdir .") ;
  dir_close(dir) ;
}

static void scan (unsigned int *what)
{
  DIR *dir = opendir(".") ;
  char tmpactive[bitarray_div8(max)] ;
  tain_add_g(&scan_deadline, &scantto) ;
  memset(tmpactive, 0, bitarray_div8(max)) ;
  if (!dir)
  {
    strerr_warnwu1sys("opendir .") ;
    set_scan_timeout(5) ;
    return ;
  }
  for (;;)
  {
    int i ;
    size_t len ;
    direntry *d ;
    errno = 0 ;
    d = readdir(dir) ;
    if (!d) break ;
    if (d->d_name[0] == '.') continue ;
    len = strlen(d->d_name) ;
    if (len > namemax)
    {
      strerr_warnw2x("name too long - not spawning service: ", d->d_name) ;
      continue ;
    }
    i = check(d->d_name, max, tmpactive) ;
    if (i < 0)
    {
      dir_close(dir) ;
      set_scan_timeout(-i) ;
      return ;
    }
    if (i < max)
    {
      char logname[len + 5] ;
      memcpy(logname, d->d_name, len) ;
      memcpy(logname + len, "/log", 5) ;
      if (check(logname, i, tmpactive) < 0)
      {
        genset_delete(services, i) ;
        dir_close(dir) ;
        set_scan_timeout(-i) ;
        return ;
      }
    }
  }
  dir_close(dir) ;
  if (errno)
  {
    strerr_warnwu1sys("readdir .") ;
    set_scan_timeout(5) ;
    return ;
  }
  memcpy(active, tmpactive, bitarray_div8(max)) ;
  genset_iter(services, &remove_deadinactive_iter, 0) ;
  *what &= ~16 ;
}


 /*
    On-timeout action: starter.
    This cannot be user-triggered. It runs when a service needs to (re)start.
 */

static int start_iter (void *data, void *aux)
{
  service *sv = data ;
  uint32_t i = sv - SERVICE(0) ;
  char const *const cargv[3] = { "s6-supervise", NAME(i), 0 } ;
  cspawn_fileaction fa[2] =
  {
    [0] = { .type = CSPAWN_FA_MOVE },
    [1] = { .type = CSPAWN_FA_MOVE }
  } ;
  size_t j = 0 ;

  if (!bitarray_peek(active, i)
   || sv->pid
   || tain_future(&sv->start)) return 1 ;
  if (sv->peer < max)
  {
    fa[j].x.fd2[0] = !is_logger(i) ;
    fa[j].x.fd2[1] = sv->p ;
    j++ ;
  }
  if (consoleholder && i == special)
  {
    fa[j].x.fd2[0] = 2 ;
    fa[j].x.fd2[1] = consoleholder ;
    j++ ;
  }
  
  sv->pid = cspawn(S6_EXTBINPREFIX "s6-supervise", cargv, (char const *const *)environ, CSPAWN_FLAGS_SELFPIPE_FINISH, fa, j) ;
  if (!sv->pid)
  {
    strerr_warnwu2sys("spawn s6-supervise for ", NAME(i)) ;
    tain_addsec_g(&start_deadline, 10) ;
    return 0 ;
  }
  avltreen_insert(by_pid, i) ;
  tain_addsec_g(&sv->start, 1) ;
  (void)aux ;
  return 1 ;
}

static inline void start (void)
{
  start_deadline = tain_infinite ;
  genset_iter(services, &start_iter, 0) ;
}


 /* Main. */

static inline int control_init (void)
{
  mode_t m = umask(0) ;
  int fdctl, fdlck, r ;
  if (mkdir(S6_SVSCAN_CTLDIR, 0700) < 0)
  {
    struct stat st ;
    if (errno != EEXIST)
      strerr_diefu1sys(111, "mkdir " S6_SVSCAN_CTLDIR) ;
    if (stat(S6_SVSCAN_CTLDIR, &st) < 0)
      strerr_diefu1sys(111, "stat " S6_SVSCAN_CTLDIR) ;
    if (!S_ISDIR(st.st_mode))
      strerr_dief1x(100, S6_SVSCAN_CTLDIR " exists and is not a directory") ;
  }

  fdlck = open3(LCK, O_WRONLY | O_NONBLOCK | O_CREAT | O_CLOEXEC, 0600) ;
  if (fdlck < 0) strerr_diefu1sys(111, "open " LCK) ;
  r = fd_lock(fdlck, 1, 1) ;
  if (r < 0) strerr_diefu1sys(111, "lock " LCK) ;
  if (!r) strerr_dief1x(100, "another instance of s6-svscan is already running on the same directory") ;
 /* fdlck leaks but it's coe */

  if (mkfifo(CTL, 0600) < 0)
  {
    struct stat st ;
    if (errno != EEXIST)
      strerr_diefu1sys(111, "mkfifo " CTL) ;
    if (stat(CTL, &st) < 0)
      strerr_diefu1sys(111, "stat " CTL) ;
    if (!S_ISFIFO(st.st_mode))
      strerr_dief1x(100, CTL " is not a FIFO") ;
  }
  fdctl = openc_read(CTL) ;
  if (fdctl < 0)
    strerr_diefu1sys(111, "open " CTL " for reading") ;
  r = openc_write(CTL) ;
  if (r < 0)
    strerr_diefu1sys(111, "open " CTL " for writing") ;
 /* r leaks but it's coe */

  umask(m) ;
  return fdctl ;
}

int main (int argc, char const *const *argv)
{
  static gol_arg const rgola[GOLA_N] =
  {
    { .so = 'C', .lo = "services-max", .i = GOLA_MAX },
    { .so = 'L', .lo = "name-max", .i = GOLA_NAMEMAX },
    { .so = 't', .lo = "timeout", .i = GOLA_TIMEOUT },
    { .so = 'd', .lo = "notification-fd", .i = GOLA_NOTIF },
    { .so = 'X', .lo = "console-holder", .i = GOLA_CONSOLE },
  } ;
  iopause_fd x[2] = { { .fd = -1, .events = IOPAUSE_READ }, { .fd = -1, .events = IOPAUSE_READ } } ;
  unsigned int notif = 0 ;
  sigset_t set ;
  char const *wgola[GOLA_N] = { 0 } ;
  unsigned int golc ;

  PROG = "s6-svscan" ;
  golc = gol_main(argc, argv, 0, 0, rgola, GOLA_N, 0, wgola) ;
  argc -= golc ; argv += golc ;
  if (!argc) dieusage() ;
  if (wgola[GOLA_MAX])
  {
    if (!uint320_scan(wgola[GOLA_MAX], &max))
      strerr_dief2x(100, "services_max", " must be an unsigned integer") ;
    if (max < 4) max = 4 ;
    if (max > 160000) max = 160000 ;
  }
  if (wgola[GOLA_NAMEMAX])
  {
    if (!uint320_scan(wgola[GOLA_NAMEMAX], &namemax))
      strerr_dief2x(100, "name_max", " must be an unsigned integer") ;
    if (namemax < 11) namemax = 11 ;
    if (namemax > 1019) namemax = 1019 ;
  }
  if (wgola[GOLA_TIMEOUT])
  {
    unsigned int t = 0 ;
    if (!uint0_scan(wgola[GOLA_TIMEOUT], &t))
      strerr_dief2x(100, "timeout", " must be an unsigned integer") ;
    if (t) tain_from_millisecs(&scantto, t) ;
  }
  if (wgola[GOLA_NOTIF])
  {
    if (!uint0_scan(wgola[GOLA_NOTIF], &notif))
      strerr_dief2x(100, "notif", " must be an unsigned integer") ;
    if (notif < 3) strerr_dief2x(100, "notif", " must be 3 or more") ;
    if (fcntl(notif, F_GETFD) == -1) strerr_dief1sys(100, "invalid notification fd") ;
  }
  if (wgola[GOLA_CONSOLE])
  {
    if (!uint0_scan(wgola[GOLA_CONSOLE], &consoleholder))
      strerr_dief2x(100, "consoleholder", " must be an unsigned integer") ;
    if (consoleholder < 3) strerr_dief2x(100, "consoleholder", " must be 3 or more") ;
    if (fcntl(consoleholder, F_GETFD) == -1) strerr_dief1sys(100, "invalid console holder fd") ;
    if (coe(consoleholder) == -1) strerr_diefu1sys(111, "coe console holder") ;
  }

  special = max ;
  if (!fd_sanitize()) strerr_diefu1x(100, "sanitize standard fds") ;
  if (chdir(argv[0]) == -1) strerr_diefu2sys(111, "chdir to ", argv[0]) ;
  x[1].fd = control_init() ;
  x[0].fd = selfpipe_init() ;
  if (x[0].fd == -1) strerr_diefu1sys(111, "selfpipe_init") ;
  if (!sig_altignore(SIGPIPE)) strerr_diefu1sys(111, "ignore SIGPIPE") ;

  sigemptyset(&set) ;
  sigaddset(&set, SIGCHLD) ;
  sigaddset(&set, SIGALRM) ;
  sigaddset(&set, SIGABRT) ;
  sigaddset(&set, SIGHUP) ;
  sigaddset(&set, SIGINT) ;
  sigaddset(&set, SIGTERM) ;
  sigaddset(&set, SIGQUIT) ;
  sigaddset(&set, SIGUSR1) ;
  sigaddset(&set, SIGUSR2) ;
#ifdef SIGPWR
  sigaddset(&set, SIGPWR) ;
#endif
#ifdef SIGWINCH
  sigaddset(&set, SIGWINCH) ;
#endif
  if (!selfpipe_trapset(&set)) strerr_diefu1sys(111, "trap signals") ;

  initial_cleanup() ;
  if (notif)
  {
    write(notif, "\n", 1) ;
    close(notif) ;
  }

  {
    unsigned int what = 0 ;
    service services_storage[max] ;
    uint32_t services_freelist[max] ;
    avlnode bydevino_storage[max] ;
    uint32_t bydevino_freelist[max] ;
    avlnode bypid_storage[max] ;
    uint32_t bypid_freelist[max] ;
    genset services_info ;
    avltreen bydevino_info ;
    avltreen bypid_info ;
    char name_storage[max * (namemax + 5)] ;
    char active_storage[bitarray_div8(max)] ;

    GENSET_init(&services_info, service, services_storage, services_freelist, max) ;
    avltreen_init(&bydevino_info, bydevino_storage, bydevino_freelist, max, &bydevino_dtok, &bydevino_cmp, &services_info) ;
    avltreen_init(&bypid_info, bypid_storage, bypid_freelist, max, &bypid_dtok, &bypid_cmp, &services_info) ;
    services = &services_info ;
    by_devino = &bydevino_info ;
    by_pid = &bypid_info ;
    names = name_storage ;
    active = active_storage ;

    tain_now_set_stopwatch_g() ;

    /* From now on, we must not die.
       Temporize on recoverable errors, and panic on serious ones. */

    while (flags.cont)
    {
      int r ;
      tain deadline = scan_deadline ;
      tain_earliest1(&deadline, &start_deadline) ;
      r = iopause_g(x, 2, &deadline) ;
      if (r == -1) panic("iopause") ;
      else if (!r)
      {
        if (!tain_future(&scan_deadline)) scan(&what) ;
        if (!tain_future(&start_deadline)) start() ;
      }
      else
      {
        if ((x[0].revents | x[1].revents) & IOPAUSE_EXCEPT)
        {
          errno = EIO ;
          panic("check internal pipes") ;
        }
        if (x[0].revents & IOPAUSE_READ) handle_signals(&what) ;
        if (x[1].revents & IOPAUSE_READ) handle_control(x[1].fd, &what) ;
      }
      killthem(&what) ;
      reap(&what) ;
    }

    /* Finish phase. */

    close_pipes() ;
    restore_console() ;
    if (flags.waitall) waitthem() ;
    selfpipe_finish() ;
  }
  {
    char const *eargv[2] = { FINISH_PROG, 0 } ;
    execv(eargv[0], (char **)eargv) ;
    if (errno != ENOENT) panicnosp("exec finish script " FINISH_PROG) ;
  }
  _exit(0) ;
}
