#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <fcntl.h>

#define LIFESIGN_FD_WATCH 0
#define LIFESIGN_FD_OWN 1

/*
 * The pipe representing the life sign and the infrastructure
 * which turns SIGIO into SIGTERM for processes who watch the
 * life sign
 */

static int LifeSignFDs[2];
static int LifeSignSendSIGTERM = 0;

static void LifeSignSIGIOHandler(int signal)
{
  if (LifeSignSendSIGTERM)
    kill(getpid(), SIGTERM);
}

/*
 * LifeSignCreate - Create a life sign object.
 *
 * The calling process and all its future child processes become owner
 * of the life sign.
 */
void LifeSignInitialize()
{
  int flags;

  /* Create pipe. The life sign is deemed to have vanished of all
   * no process has the writing end (LIFESIGN_FD_OWN) open
   */
  if (pipe(LifeSignFDs)) {
    fprintf(stderr, "Failed to create life sign due to %s\n", strerror(errno));
    exit(1);
  }
  
  /* Set FNONBLOCK to allow checking for the life sign's presence by a simply
   * read() and FASYNC to deliver a signal to our process group if the life
   * sign vanishes
   */
  flags = fcntl(LifeSignFDs[LIFESIGN_FD_WATCH], F_GETFL);
  if (flags < 0) {
    fprintf(stderr, "Failed to get the life sign's watching end's flags due to %s\n", strerror(errno));
    exit(1);
  }
  flags |= FNONBLOCK | FASYNC;
  if (fcntl(LifeSignFDs[LIFESIGN_FD_WATCH], F_SETFL, flags)) {
    fprintf(stderr, "Failed to set the life sign's watching end's flags due to %s\n", strerror(errno));
    exit(1);
  }
  
  /* Send SIGIO signal to the whole process group */
  if (fcntl(LifeSignFDs[LIFESIGN_FD_WATCH], F_SETOWN, -getpgrp())) {
    fprintf(stderr, "Failed to set the life sign's watching end's notification pid due to %s\n", strerror(errno));
    exit(1);
  }

  /* Register signal handler */
  if (signal(SIGIO, LifeSignSIGIOHandler)) {
    fprintf(stderr, "Failed to register SIGIO handler %s\n", strerror(errno));
    exit(1);
  }
}

/*
 * LifeSignPresent - Check if the life sign vanished
 *
 * Returns 1 if the life sign is present, 0 otherwise. Doesn't require the
 * calling process to watch the life sign
 */
int LifeSignPresent()
{
  int r, b;
  
  r = read(LifeSignFDs[LIFESIGN_FD_WATCH], &b, sizeof(b));
  if ((r < 0) && (errno == EAGAIN))
    /* Life sign present */
    return 1;
  else if (r == 0)
    /* EOF. Life sign vanished */
    return 0;
  else {
    fprintf(stderr, "Unexpected state of life sign's reading end. read() returned %d\n", r);
    exit(1);
  }
}

/*
 * LifeSignWatch - Make the calling process a watcher of the life sign
 *
 * Arranges for the calling process to receive a SIGTERM once the
 * last owner of the life sign vanishes.
 */
void LifeSignWatch()
{
  LifeSignSendSIGTERM = 1;
  
  /* Prevent race condition */
  if (!LifeSignPresent())
    kill(getpid(), SIGTERM);
}

/*
 * LifeSignRelease - The calling process gives up ownership of the life sign
 */
void LifeSignRelease()
{
  if (close(LifeSignFDs[LIFESIGN_FD_OWN])) {
    fprintf(stderr, "Failed to release life sign due to %s\n", strerror(errno));
    exit(1);
  }
  LifeSignFDs[LIFESIGN_FD_OWN] = -1;
}

static void launch();
static int backend();

/*
 * launch - Run function in new process, exit when done
 */
static void launch(int (*proc)())
{
  pid_t pid;
  
  pid = fork();
  if (pid < 0) {
    fprintf(stderr, "Failed to fork due to %s\n", strerror(errno));
    exit(1);
  }
  else if (pid > 0) {
    /* Parent. Do nothing */
  }
  else {
    /* Child. */
    exit(proc());
  }
}

/*
 * backend - Wait for parent to exit, then exit too
 */
static int backend()
{
  int my_pid = getpid();
  int first_run = 1;
  
  printf("Launched backend %d\n", my_pid);
  fflush(stdout);
  
  /* Ignore SIGTERM */
  if (signal(SIGTERM, SIG_IGN)) {
    fprintf(stderr, "Unable to ignore SIGTERM due to %s",strerror(errno));
    exit(1);
  }

  /* Release life sign and watch it to receive a SIGTERM if it vanishes */
  LifeSignRelease();
  LifeSignWatch();
  
  /* To make the output more deterministic */
  sleep(1);

  /* Wait for parent to exit */
  while (LifeSignPresent()) {
    printf("Backend %d detected live parent\n", my_pid);
    sleep(first_run ? 2: 10);
    first_run = 0;
  }

  printf("Backend %d exiting after parent died\n", my_pid);
  fflush(stdout);

  return 0;
}

int main(int argc, char** argv)
{
  /* Create life signs */
  LifeSignInitialize();

  /* Launch backends */
  launch(backend);
  launch(backend);
  launch(backend);
  
  /* Exit after a while */
  sleep(5);
  fprintf(stdout, "Parent exiting\n");
  fflush(stdout);
  
  return 0;
}
