#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

typedef int LifeSign[2];

/* LifeSign owned only by the parent.
 * Watched by backends to let them exit once the parent dies
 */
static LifeSign LifeSignParent;

/* LifeSign owned by the parent and backends.
 * Watched by utilities to let them exit once the parent and all backends
 * have died
 */
static LifeSign LifeSignParentBackends;

static void launch();
static int backend();
static int utility();
static int lifesign(LifeSign lifesign, int wait_ms);

/* 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();

  /* Close owning fd of life sign we aren't supposed to own */

  if (close(LifeSignParent[LIFESIGN_FD_OWN])) {
    fprintf(stderr, "Failed to close writing end of parent life sign due to %s\n", strerror(errno));
    exit(1);
  }
  
  /* Wait for parent to exit */

  printf("Launched backend %d\n", my_pid);
  fflush(stdout);

  while (lifesign(LifeSignParent, 5 * 1000)) {
    printf("Backend %d detected live parent\n", my_pid);
  }

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

  return 0;
}

/* utility()
 * Wait for parent and backends to exit, then exit too
 */
static int utility()
{
  int my_pid = getpid();
  
  /* Close owning fd of life sign we aren't supposed to own */

  if (close(LifeSignParent[LIFESIGN_FD_OWN])) {
    fprintf(stderr, "Failed to close writing end of parent life sign due to %s\n", strerror(errno));
    exit(1);
  }

  if (close(LifeSignParentBackends[LIFESIGN_FD_OWN])) {
    fprintf(stderr, "Failed to close writing end of parent+backens life sign due to %s\n", strerror(errno));
    exit(1);
  }
  
  /* Wait for parent to exit */

  printf("Launched utility %d\n", my_pid);
  fflush(stdout);

  while (lifesign(LifeSignParentBackends, 5 * 1000)) {
    printf("Utility %d detected live parent or backend\n", my_pid);
  }

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

  return 0;
}

/* lifesign()
 * Waits for wait_ms seconds for all processes owning the lifesign to exit.
 * Returns 0 if they did, 1 otherwise
 */
static int lifesign(LifeSign lifesign, int wait_ms)
{
  fd_set set_rd;
  struct timeval timeout;
  int r;

  FD_ZERO(&set_rd);
  FD_SET(lifesign[LIFESIGN_FD_WATCH], &set_rd);
  
  timeout.tv_sec = wait_ms / 1000;
  timeout.tv_usec = (wait_ms % 1000) * 1000;

  r = select(FD_SETSIZE, &set_rd, NULL, NULL, &timeout);
  if (r < 0) {
    fprintf(stderr, "Unable to select from life sign pipe due to %s", strerror(errno));
    exit(1);
  }
  else if ((r == 0) || !FD_ISSET(lifesign[LIFESIGN_FD_WATCH], &set_rd)) {
    /* fd is not readable */
    return 1;
  }
  else {
    /* fd is readable. Can only mean EOF since nobody ever writes to the channel */
    return 0;
  }
}

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

  if (pipe(LifeSignParent)) {
    fprintf(stderr, "Unable to create parent life sign pipe due to %s", strerror(errno));
    return 1;
  }

  if (pipe(LifeSignParentBackends)) {
    fprintf(stderr, "Unable to create parent+backends life sign pipe due to %s", strerror(errno));
    return 1;
  }

  /* Launch utilities */
  launch(utility);
  launch(utility);

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