#include "test_fault_helper.h"
#include "fail.h"
#include <sys/mman.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <TargetConditionals.h>

#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
#define MEMSIZE (1L<<28)
#else
#define MEMSIZE (1L<<30)
#endif

static char* memblock;

int
test_fault_setup()
{
	char *ptr;
	int pgsz = getpagesize();
	int retval;

	memblock = (char *)mmap(NULL, MEMSIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
	VERIFY(memblock != MAP_FAILED, "mmap failed");

	/* make sure memory is paged */
	for (ptr = memblock; ptr < memblock + MEMSIZE; ptr += pgsz) {
		*ptr = 1;
	}

	/* set to read only, then back to read write so it faults on first write */
	retval = mprotect(memblock, MEMSIZE, PROT_READ);
	VERIFY(retval == 0, "mprotect failed");

	retval = mprotect(memblock, MEMSIZE, PROT_READ | PROT_WRITE);
	VERIFY(retval == 0, "mprotect failed");

	return PERFINDEX_SUCCESS;
}

int
test_fault_helper(int thread_id, int num_threads, long long length, testtype_t testtype)
{
	char *ptr;
	int pgsz = getpagesize();
	int retval;

	long long num_pages = MEMSIZE / pgsz;
	long long region_len = num_pages / num_threads;
	long long region_start = region_len * thread_id;
	long long region_end;

	if (thread_id < num_pages % num_threads) {
		region_start += thread_id;
		region_len++;
	} else {
		region_start += num_pages % num_threads;
	}

	region_start *= pgsz;
	region_len *= pgsz;
	region_end = region_start + region_len;

	long long left = length;

	while (1) {
		for (ptr = memblock + region_start; ptr < memblock + region_end; ptr += pgsz) {
			*ptr = 1;
			left--;
			if (left == 0) {
				break;
			}
		}

		if (left == 0) {
			break;
		}

		if (testtype == TESTFAULT) {
			retval = mprotect(memblock + region_start, region_len, PROT_READ) == 0;
			VERIFY(retval == 0, "mprotect failed");
			retval = mprotect(memblock + region_start, region_len, PROT_READ | PROT_WRITE) == 0;
			VERIFY(retval == 0, "mprotect failed");
		} else if (testtype == TESTZFOD) {
			retval = munmap(memblock + region_start, region_len) == 0;
			VERIFY(retval == 0, "munmap failed");
			ptr = mmap(memblock + region_start, region_len, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
			VERIFY(ptr != 0, "mmap failed");
		}
	}
	return PERFINDEX_SUCCESS;
}
