#define _GNU_SOURCE

#include <fcntl.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>

#define FLAGS (O_RDWR | O_CLOEXEC | O_DIRECT)
#define BUFFER_SIZE 8192
#define ALIGN 4096
#define LOOPS 1000000

#define PROCESS_WRITER 1
#define PROCESS_SCRIBBLER 2
#define PROCESS_VERIFIER 3

struct shared_data {
	uint8_t write_buffer[BUFFER_SIZE];
	int blockno;
	pthread_barrier_t barrier;
};

static int
run_process(int which, struct shared_data *shared)
{
	int fd;

	fd = open("file", FLAGS, 0644);
	if (fd < 0) {
		perror("open");
		return 1;
	}

	for (int i = 0; i < LOOPS; i++) {

		if (which == PROCESS_WRITER) {
			shared->blockno = rand() % 88;
			memset(shared->write_buffer, 0x00, sizeof(shared->write_buffer));
			if (pwrite(fd, shared->write_buffer, BUFFER_SIZE, BUFFER_SIZE * shared->blockno) < BUFFER_SIZE)
				perror("writer: initialization pwrite failed or short");
			memset(shared->write_buffer, 0xff, BUFFER_SIZE);
		}

		pthread_barrier_wait(&shared->barrier);

		if (which == PROCESS_WRITER) {
			if (pwrite(fd, shared->write_buffer, BUFFER_SIZE, BUFFER_SIZE * shared->blockno) < BUFFER_SIZE)
				perror("writer: main pwrite failed or short");
		} else if (which == PROCESS_SCRIBBLER) {
			for (int j = 0; j < 3; j++)
				shared->write_buffer[rand() % BUFFER_SIZE] = 0x42;
		}

		pthread_barrier_wait(&shared->barrier);

		if (which == PROCESS_VERIFIER) {
			uint8_t read_buffer[BUFFER_SIZE];

			if (pread(fd, read_buffer, BUFFER_SIZE, BUFFER_SIZE * shared->blockno) < BUFFER_SIZE) {
				perror("pread failed or short while verifying");
			} else {
				for (int i = 0; i < BUFFER_SIZE; i++) {
					if (read_buffer[i] == 0xff)
						continue;
					if (read_buffer[i] == 0x42)
						continue;
					printf("got unexpected byte %d at position %d in block %d (stopped looking after that)\n",
						read_buffer[i], i, shared->blockno);
					break;
				}
			}
		}

		pthread_barrier_wait(&shared->barrier);
	}

	return 0;
}

int
main(int argc, char *argv[])
{
	pid_t writer_pid;
	pid_t scribbler_pid;
	pid_t verifier_pid;
	char *memory;
	struct shared_data *shared;
    pthread_barrierattr_t barrier_attr;

	memory = mmap(NULL, sizeof(struct shared_data) + ALIGN,
		PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
	if (memory == MAP_FAILED) {
		perror("mmap");
		return EXIT_FAILURE;
	}

	shared = (struct shared_data *)
		((((uintptr_t) memory + ALIGN - 1) / ALIGN) * ALIGN);

    pthread_barrierattr_setpshared(&barrier_attr, PTHREAD_PROCESS_SHARED);
	pthread_barrier_init(&shared->barrier, &barrier_attr, 3);

	writer_pid = fork();
	if (writer_pid == 0)
		return run_process(PROCESS_WRITER, shared);
	verifier_pid = fork();
	if (verifier_pid == 0)
		return run_process(PROCESS_VERIFIER, shared);
	run_process(PROCESS_SCRIBBLER, shared);

	waitpid(verifier_pid, NULL, 0);
	waitpid(writer_pid, NULL, 0);
	return EXIT_SUCCESS;
}
