#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>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.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 _readcount;
};

#define readcount shared_object->_readcount
#define mutex 0
#define wrt 1
int rw_semid;

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); }

void WAIT(int sem) {
  struct sembuf buf;
  buf.sem_num = sem;
  buf.sem_op = -1;
  buf.sem_flg = 0;
  assert(semop(rw_semid, &buf, 1) == 0);
}

void SIGNAL(int sem) {
  struct sembuf buf;
  buf.sem_num = sem;
  buf.sem_op = 1;
  buf.sem_flg = 0;
  assert(semop(rw_semid, &buf, 1) == 0);
}

/* Perform one cycle of reading */
int do_read() {
  int i, value;
  WAIT(mutex);
  ++readcount;
  if (readcount == 1)
    WAIT(wrt);
  SIGNAL(mutex);
  value = *shared_object->curr_ptr;
  for (i = 0; i < 10; ++i) {
    readsleep();
    assert(*shared_object->curr_ptr == value);
  }
  WAIT(mutex);
  --readcount;
  if (readcount == 0)
    SIGNAL(wrt);
  SIGNAL(mutex);
  return value;
}

/* Perform one cycle of writing */
int do_write() {
  int value;
  WAIT(wrt);
  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;
  SIGNAL(wrt);
  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];
  readcount = 0;
  /* Create and initialize semaphores */
  assert((rw_semid = semget(IPC_PRIVATE, 2, 0700|IPC_CREAT)) >= 0);
  assert(semctl(rw_semid, mutex, SETVAL, 1) == 0);
  assert(semctl(rw_semid, wrt, SETVAL, 1) == 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");
  }
  /* deallocate the semaphore */
  assert(semctl(rw_semid, 0, IPC_RMID) == 0);
  return exitcode;
}

