#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.2	/* Percentage of write accesses */

/* A shared object, to be allocated using mmap in main() */
struct object_s {
	int* curr_ptr;
	int value;
} *shared_object;

int proccode = -1;

/* 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;
	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 = *shared_object->curr_ptr; /* SEGV if write in progress */
	shared_object->curr_ptr = NULL; /* make later read and write SEGV */
	writesleep(); /* longer write makes the effect more visible */
	shared_object->curr_ptr = &shared_object->value;
	shared_object->value = value + (rand() % 10);
	return *shared_object->curr_ptr;
}

/* 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(struct object_s), PROT_READ|PROT_WRITE,
			     MAP_SHARED|MAP_ANONYMOUS, -1, 0);
	shared_object->curr_ptr = (int*) &shared_object->value;
	shared_object->value = 0;
	/* Start processes */
	for (i = 0; i < NUM_PROC; ++i)
		pids[i] = create_proc(i);
	/* Wait for their completions */
	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 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;
}

