#define _GNU_SOURCE		/* To get strsignal() */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <wait.h>
#include <assert.h>
#include <string.h>

#define NUM_PROC 8
#define NUM_ITERATIONS 10
#define WRITE_FRACTION 0.3	/* Percentage of write accesses */

/* A structure to be shared by the processes */
struct object_s {
  int* curr_ptr;
  int values[1000];
};

int proccode;

/* A shared object, to be allocated using mmap in main() */
/* "volatile" hints the compiler that the value in it can be modified
 * by other threads, so don't cache it.
 */
volatile struct object_s *shared_object;

/* Randomly sleep, for at least lower and at most upper microseconds */
void randsleep(long lower, long upper) {
  usleep(lower + rand() % (upper - lower + 1));
}
void idlesleep()  { randsleep( 10000, 400000); }
void readsleep()  { randsleep(  1000,  20000); }
void writesleep() { randsleep( 20000, 100000); }

/* Perform one cycle of reading */
int do_read() {
  int i, value, newvalue;
  value = *shared_object->curr_ptr;
  for (i = 0; i < 10; ++i) {
    readsleep();
    assert(*shared_object->curr_ptr == value);
  }
  return value;
}

/* Perform one cycle of writing */
int do_write() {
  int value;
  value = *shared_object->curr_ptr; /* segfault if another write in progress */
  shared_object->curr_ptr = NULL; /* make later read and write segfault */
  writesleep(); /* longer write makes the effect more visible */
  shared_object->curr_ptr = (int*) &shared_object->values[rand() % 1000];
  value = *shared_object->curr_ptr;
  return value;
}

/* The work function of all processes */
void do_work() {
  int i;
  for (i = 0; i < NUM_ITERATIONS; ++i) {
    int r = rand() % 1000;
    int result;
    idlesleep();
    if (r < WRITE_FRACTION * 1000) {
      printf("Process %d starts to write\n", proccode);
      fflush(stdout);
      result = do_write();
      printf("Process %d completes to write %d\n", proccode, result);
      fflush(stdout);
    } else {
      printf("Process %d starts to read\n", proccode);
      fflush(stdout);
      result = do_read();
      printf("Process %d completes to read, got %d\n", proccode, result);
      fflush(stdout);
    }
  }
}

/* Create a new process */
int create_proc(int proc_no) {
  int pid;
  /* Set a new random seed, so that processes have different behaviour */
  srand(proc_no);
  pid = fork();
  if (pid) {
    printf("Process %d created, with PID %d\n", proc_no, pid);
    fflush(stdout);
  } else {
    proccode = proc_no;
    do_work();
    exit(0);
  }
  return pid;
}

int main() {
  int i, exitcode = 0, pids[NUM_PROC];
  /* Create shared memory and initialize it */
  shared_object = mmap(0, sizeof(shared_object), PROT_READ|PROT_WRITE,
		       MAP_SHARED|MAP_ANONYMOUS, 0, 0);
  shared_object->curr_ptr = (int*) &shared_object->values[0];
  for (i = 0; i < 1000; ++i)
    shared_object->values[i] = i;
  /* Start processes */
  for (i = 0; i < NUM_PROC; ++i)
    pids[i] = create_proc(i);
  /* Wait for their completions, and quit */
  for (i = 0; i < NUM_PROC; ++i) {
    int result;
    int pid = wait(&result);
    int pcode;
    for (pcode = 0; pcode < NUM_PROC; ++pcode)
      if (pids[pcode] == pid)
	break;
    printf("Main: ");
    /* Show what happened to the terminating process: exited or killed */
    if (WIFEXITED(result)) {
      printf("Process %d exited, exit code = %d\n", pcode,
	     WEXITSTATUS(result));
      fflush(stdout);
    } else if (WIFSIGNALED(result)) {
      exitcode = 1;
      printf("Process %d killed, signal = %d (%s)\n", pcode,
	     WTERMSIG(result),
	     strsignal(WTERMSIG(result)));
      fflush(stdout);
    }
  }
  if (exitcode) {
    printf("Main: some process(es) died unexpectedly.\n");
  } else {
    printf("Main: all processes completed normally.\n");
  }
  return exitcode;
}

