#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <pthread.h>

static struct {
	pthread_mutex_t lock;
	pthread_cond_t client_cond, server_cond;
	volatile sig_atomic_t client_count, server_count;
	volatile sig_atomic_t die;
} *shmem;

static void setup(void)
{
	pthread_mutexattr_t mutexattr;
	pthread_condattr_t condattr;
	int shm_fd;
	long pagesize;
	unsigned shmsize;

	pagesize = sysconf(_SC_PAGESIZE);
	if(pagesize == -1) { perror("_SC_PAGESIZE"); goto error; }

	shmsize = (sizeof *shmem + pagesize - 1) / pagesize * pagesize;

	shm_fd = shm_open("/pingpong", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
	if(shm_fd == -1) { perror("shm_open"); goto error; }

	if(ftruncate(shm_fd, shmsize)) { perror("ftruncate"); goto error; }

	shmem = mmap(NULL, shmsize, PROT_READ | PROT_WRITE, MAP_SHARED,
		     shm_fd, 0);
	if(shmem == (void *)-1) {
		perror("mmap");
		goto error;
	}

	shmem->client_count = shmem->server_count = 0;
	shmem->die = 0;

	if( pthread_mutexattr_init(&mutexattr) ||
	    pthread_mutexattr_setpshared(&mutexattr, PTHREAD_PROCESS_SHARED) ||
	    pthread_mutex_init(&shmem->lock, &mutexattr) ) {
		fputs("Mutex initialisation failed.\n", stderr);
		goto error;
	}

	if( pthread_condattr_init(&condattr) ||
	    pthread_condattr_setpshared(&condattr, PTHREAD_PROCESS_SHARED) ||
	    pthread_cond_init(&shmem->client_cond, &condattr) ||
	    pthread_cond_init(&shmem->server_cond, &condattr) ) {
		fputs("Condvar initialisation failed.\n", stderr);
		goto error;
	}

	return;

error:
	exit(EXIT_FAILURE);
}

static int server(void)
{
	if(pthread_mutex_lock(&shmem->lock)) goto error;

	while(shmem->server_count < ITERATIONS) {
//		fprintf(stderr, "server: %d\n", (int)shmem->server_count);
		while(shmem->client_count < shmem->server_count && !shmem->die)
			if(pthread_cond_wait(&shmem->server_cond,
					     &shmem->lock)) goto error;

		if(shmem->die) return -1;

		if(shmem->client_count > shmem->server_count) {
			fputs("server: lost synchronisation with client!\n",
			      stderr);
			shmem->die = 1;
			pthread_cond_signal(&shmem->client_cond);
			goto error;
		}

		++shmem->server_count;
		if(pthread_cond_signal(&shmem->client_cond)) goto error;
	}

	if(pthread_mutex_unlock(&shmem->lock)) goto error;

	return 0;

error:
	pthread_mutex_unlock(&shmem->lock);
	fputs("Runtime threading error in server process.\n", stderr);

	return -1;
}

static int client(void)
{
	if(pthread_mutex_lock(&shmem->lock)) goto error;

	while(shmem->client_count < ITERATIONS) {
//		fprintf(stderr, "client: %d\n", (int)shmem->client_count);
		while(shmem->server_count <= shmem->client_count && !shmem->die)
			if(pthread_cond_wait(&shmem->client_cond,
					     &shmem->lock)) goto error;

		if(shmem->die) return -1;

		if(shmem->server_count > shmem->client_count + 1) {
			fputs("client: lost synchronisation with server!\n",
			      stderr);
			shmem->die = 1;
			pthread_cond_signal(&shmem->server_cond);
			goto error;
		}

		++shmem->client_count;
		if(pthread_cond_signal(&shmem->server_cond)) goto error;
	}

	if(pthread_mutex_unlock(&shmem->lock)) goto error;

	return 0;

error:
	pthread_mutex_unlock(&shmem->lock);
	fputs("Runtime threading error in client process.\n", stderr);

	return -1;
}

int main(void)
{
	pid_t child_pid;

	setup();

	child_pid = fork();
	if(child_pid == -1) { perror("fork"); goto error; }

	if(child_pid) {
		return server() ? EXIT_FAILURE : EXIT_SUCCESS;
	} else {
		if(client()) goto error;
	}

error:
	return EXIT_FAILURE;
}
