#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <sys/resource.h>

volatile pid_t cpid_cmd = 0;

void onsigchld(int sig)
{
  int exitstatus;
  pid_t pid;

  pid = wait(&exitstatus);
  if (pid == -1) {
    perror("wait");
    exit(2);
  }

  // fprintf(stderr, "pid %d returned %d\n", pid, exitstatus);

  if (WIFSIGNALED(exitstatus)) {
    /* Emulate signal exit, but don't dump core */
    struct rlimit rlim;
    rlim.rlim_cur = 0;
    rlim.rlim_max = 0;
    
    if (setrlimit(RLIMIT_CORE, &rlim) == -1) {
      perror("rlimit - continuing");
    }
    kill(getpid(), WTERMSIG(exitstatus));
  } else {
    exit(WEXITSTATUS(exitstatus));
  }
}

void on_timeout(int sig)
{
  int ret;

  if (cpid_cmd == 0) {
    fprintf(stderr, "cratimeout internal error 3\n");
    exit(3);
  } else {
    /*
     * Ignore exit status, might be a race condition and it exited
     * between triggering the timeout and now.
     */
    ret = kill(cpid_cmd, SIGTERM);
    if (ret == -1) {
      perror("kill");
    } else {
      /*
       * This will not be executed if the child gracefully exits.
       * Because the sigchld handler will exit this process.
       */
      sleep(10);
      kill(cpid_cmd, SIGKILL);
    }

    /* Wait for sigchld handler to pick up the debris */
    for (;;)
      pause();
  }
}

int main(int argc, char *argv[])
{
  int timeout;
  char *cmd;

  if (argc < 3) {
    fprintf(stderr, "Usage: timeout_in_msec cmd args\n");
    exit(1);
  }
  timeout = atoi(argv[1]);
  if (timeout < 1) {
    fprintf(stderr, "cratimeout: timeout < 1 doesn't make sense\n");
    exit(1);
  }
  argc--;
  argv++;

  cmd = argv[1];
  argc--;
  argv++;
 
  if (signal(SIGCHLD, onsigchld) == SIG_ERR) {
    perror("signal\n");
    exit(1);
  }
 
  cpid_cmd = fork();
  if (cpid_cmd == -1) {
    perror("fork");
    exit(2);
  }

  if (cpid_cmd == 0) {
    // Child become cmd
    if (execvp(cmd, argv) == -1) {
      perror("execvp");
      exit(2);
    } else {
      fprintf(stderr, "cratimeout internal error 1\n");
      exit(3);
    }
  } else {
    // Parent sets timeout
    struct itimerval itv;
    signal(SIGALRM, on_timeout);
    itv.it_interval.tv_sec = timeout / 1000;
    itv.it_interval.tv_usec = (timeout % 1000) * 1000;
    itv.it_value.tv_sec = timeout / 1000;
    itv.it_value.tv_usec = (timeout % 1000) * 1000;
    setitimer(ITIMER_REAL, &itv, NULL);
    for (;;)
      pause();
    fprintf(stderr, "cratimeout internal error 2\n");
    exit(3);
  }
}
