/* timeout.c - Run command line with a timeout
 *
 * Copyright 2013 Rob Landley <rob@landley.net>
 *
 * No standard

USE_TIMEOUT(NEWTOY(timeout, "<2^(foreground)(preserve-status)vk:s(signal):i", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(125)))

config TIMEOUT
  bool "timeout"
  default y
  help
    usage: timeout [-i] [-k DURATION] [-s SIGNAL] DURATION COMMAND...

    Run command line as a child process, sending child a signal if the
    command doesn't exit soon enough.

    DURATION can be a decimal fraction. An optional suffix can be "m"
    (minutes), "h" (hours), "d" (days), or "s" (seconds, the default).

    -i	Only kill for inactivity (restart timeout when command produces output)
    -k	Send KILL signal if child still running this long after first signal
    -s	Send specified signal (default TERM)
    -v	Verbose
    --foreground       Don't create new process group
    --preserve-status  Exit with the child's exit status
*/

#define FOR_timeout
#include "toys.h"

GLOBALS(
  char *s, *k;

  struct pollfd pfd;
  sigjmp_buf sj;
  int fds[2], pid;
)

static void handler(int sig)
{
  siglongjmp(TT.sj, 1);
}

static long nantomil(struct timespec *ts)
{
  return ts->tv_sec*1000+ts->tv_nsec/1000000;
}

static void callback(char *argv[])
{
  xsignal(SIGCHLD, SIG_DFL);
  if (!FLAG(foreground)) setpgid(0, 0);
}

void timeout_main(void)
{
  int ii, ms, nextsig = SIGTERM;
  struct timespec tts, kts;

  // Use same ARGFAIL value for any remaining parsing errors
  toys.exitval = 125;
  xparsetimespec(*toys.optargs, &tts);
  if (TT.k) xparsetimespec(TT.k, &kts);
  if (TT.s && -1==(nextsig = sig_to_num(TT.s))) error_exit("bad -s: '%s'",TT.s);

  toys.exitval = 0;
  TT.pfd.events = POLLIN;
  TT.fds[1] = -1;
  if (sigsetjmp(TT.sj, 1)) goto done;
  xsignal_flags(SIGCHLD, handler, SA_NOCLDSTOP);

  TT.pid = xpopen_setup(toys.optargs+1, FLAG(i) ? TT.fds : 0, callback);
  xsignal(SIGTTIN, SIG_IGN);
  xsignal(SIGTTOU, SIG_IGN);
  xsignal(SIGTSTP, SIG_IGN);
  if (!FLAG(i)) xpipe(TT.fds);
  TT.pfd.fd = TT.fds[1];
  ms = nantomil(&tts);
  for (;;) {
    if (1 != xpoll(&TT.pfd, 1, ms)) {
      if (FLAG(v))
        perror_msg("sending signal %s to command %s", num_to_sig(nextsig),
          toys.optargs[1]);
      toys.exitval = (nextsig==9) ? 137 : 124;
      kill(FLAG(foreground) ? TT.pid : -TT.pid, nextsig);
      if (!TT.k || nextsig==SIGKILL) break;
      nextsig = SIGKILL;
      ms = nantomil(&kts);

      continue;
    }
    if (TT.pfd.revents&POLLIN) {
      errno = 0;
      if (1>(ii = read(TT.fds[1], toybuf, sizeof(toybuf)))) {
        if (errno==EINTR) continue;
        break;
      }
      writeall(1, toybuf, ii);
    }
    if (TT.pfd.revents&POLLHUP) break;
  }
done:
  xsignal(SIGCHLD, SIG_DFL);
  ii = xpclose_both(TT.pid, TT.fds);

  if (FLAG(preserve_status) || !toys.exitval) toys.exitval = ii;
}
