// This tests handling of signals sent from outside the process in the
// following combinations:  sync and async signals, caught and uncaught
// signals, and while blocking or not blocking in a syscall.  This exercises
// various different paths in Valgrind's signal handling.
//
// It does this by installing signal handlers for one signal S, spawning
// another process P, sending S from P multiple times (all caught), then
// sending another signal from P (not caught).

#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

static const struct timespec bip = { 0, 1000000000 / 5 };   // 0.2 seconds.

static void handler(int sig)
{
}

static void install_handler(int sig, void (*sig_handler)(int))
{
   struct sigaction sa;
   sa.sa_handler = sig_handler;
   sigemptyset(&sa.sa_mask);
   sa.sa_flags = 0;
   sigaction(sig, &sa, 0);
}

/* Kill our child, but use a separate kill command.  This is so that
   it's running independently of Valgrind, and so is async with
   respect to thread scheduling. */
static void do_kill(int pid, int sig)
{
   int status;
   int killer;
   int ret;

   killer = vfork();
   if (killer == -1) {
      perror("killer/vfork");
      exit(1);
   }

   // In the child, exec 'kill' in order to send the signal.
   if (killer == 0) {
      char sigbuf[20];
      char pidbuf[20];
      sprintf(sigbuf, "-%d", sig);
      sprintf(pidbuf, "%d", pid);
      execl("/bin/kill", "kill", sigbuf, pidbuf, (char *) NULL);
      perror("exec failed");
      exit(1);
   }

   // In the parent, just wait for the child and then check it ran ok.
   do 
      ret = waitpid(killer, &status, 0);
   while (ret == -1 && errno == EINTR);

   if (ret != killer) {
      perror("kill/waitpid");
      exit(1);
   }

   if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
      fprintf(stderr, "kill %d failed status=%s %d\n", killer, 
             WIFEXITED(status) ? "exit" : "signal", 
             WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status));
      exit(1);
   }
}

static void test(int block, int caughtsig, int fatalsig)
{
   int pid;
   int status;
   int i;

   fprintf(stderr, "testing: blocking=%d caught=%d fatal=%d... ",
      block, caughtsig, fatalsig);

   pid = fork();
   if (pid == -1) {
      perror("fork");
      exit(1);
   }

   // In the child, install the signal handler, then wait for the signal to
   // arrive:
   // - if 'block' is set, wait on a system call;
   // - otherwise, wait in client code (by spinning).
   // The alarm() calls is so that if something breaks, we don't get stuck.
   if (pid == 0) {
      install_handler(caughtsig, handler);
      alarm(10);

      for (;;)
         if (block) {
            pause();
         }
   }

   // In the parent, send the signals.
   nanosleep(&bip, 0);           // Wait for child to get going.

   for (i = 0; i < 5; i++) {
      do_kill(pid, caughtsig);   // Should be caught.
      nanosleep(&bip, 0);
      do_kill(pid, caughtsig);   // Ditto.
      do_kill(pid, caughtsig);   // Ditto.
   }

   nanosleep(&bip, 0);

   do_kill(pid, fatalsig);       // Should kill it.
   
   // Check that the child behaved as expected when it received the signals.
   if (waitpid(pid, &status, 0) != pid) {
      fprintf(stderr, "FAILED: waitpid failed: %s\n", strerror(errno));

   } else if (!WIFSIGNALED(status) || WTERMSIG(status) != fatalsig) {
      fprintf(stderr, "FAILED: child exited with unexpected status %s %d\n",
             WIFEXITED(status) ? "exit" : "signal", 
             WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status));

   } else {
      fprintf(stderr, "PASSED\n");
   }
}

int main()
{
   /* Restore default behaviour of SIGHUP when forked from cron. */
   install_handler(SIGHUP, SIG_DFL);

   test(/*non-blocked*/0, /* sync*/SIGSEGV, /* sync*/SIGBUS);
   test(/*non-blocked*/0, /* sync*/SIGSEGV, /*async*/SIGHUP);
   test(/*non-blocked*/0, /*async*/SIGUSR1, /* sync*/SIGBUS);
   test(/*non-blocked*/0, /*async*/SIGUSR1, /*async*/SIGHUP);
   test(/*    blocked*/1, /* sync*/SIGSEGV, /* sync*/SIGBUS);
   test(/*    blocked*/1, /* sync*/SIGSEGV, /*async*/SIGHUP);
   test(/*    blocked*/1, /*async*/SIGUSR1, /* sync*/SIGBUS);
   test(/*    blocked*/1, /*async*/SIGUSR1, /*async*/SIGHUP);

   return 0;
}
