/* Test different kinds of addressability and definedness */
#include "../memcheck.h"
#include "tests/sys_mman.h"
#include <stdio.h>
#include <sys/resource.h>
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

static int pgsz;

static char *mm(char *addr, int size, int prot)
{
	int flags = MAP_PRIVATE | MAP_ANONYMOUS;
	char *ret;

	if (addr)
		flags |= MAP_FIXED;

	ret = mmap(addr, size, prot, flags, -1, 0);
	if (ret == (char *)-1) {
		perror("mmap failed");
		exit(1);
	}

	return ret;
}

/* Case 1 - mmaped memory is defined */
static void test1()
{
	char *m = mm(0, pgsz * 5, PROT_READ);

	VALGRIND_CHECK_MEM_IS_DEFINED(m, pgsz*5); /* all defined */
}

/* Case 2 - unmapped memory is unaddressable+undefined */
static void test2()
{
	char *m = mm(0, pgsz * 5, PROT_READ|PROT_WRITE);
	VALGRIND_CHECK_MEM_IS_DEFINED(m, pgsz*5); /* all OK */

	munmap(&m[pgsz*2], pgsz);

	VALGRIND_CHECK_MEM_IS_DEFINED(&m[pgsz*2], pgsz); /* undefined */

	/* XXX need a memcheck request to test addressability */
	m[pgsz*2] = 'x';	/* unmapped fault */
}

/* Case 3 - memory definedness doesn't survive remapping */
static void test3()
{
	char *m = mm(0, pgsz * 5, PROT_READ|PROT_WRITE);

	VALGRIND_MAKE_MEM_UNDEFINED(&m[pgsz], pgsz);
	mm(&m[pgsz], pgsz, PROT_READ);
	VALGRIND_CHECK_MEM_IS_DEFINED(&m[pgsz], pgsz); /* OK */
}

/* Case 4 - mprotect doesn't affect addressability */
static void test4()
{
	char *m = mm(0, pgsz * 5, PROT_READ|PROT_WRITE);

	mprotect(m, pgsz, PROT_WRITE);
	VALGRIND_CHECK_MEM_IS_DEFINED(m, pgsz); /* OK */
	m[44] = 'y';		/* OK */

	mprotect(m, pgsz*5, PROT_NONE);
	m[55] = 'x';		/* permission fault, but no tool complaint */
}

/* Case 5 - mprotect doesn't affect definedness */
static void test5()
{
	char *m = mm(0, pgsz * 5, PROT_READ|PROT_WRITE);
	
	VALGRIND_MAKE_MEM_UNDEFINED(m, pgsz*5);
	memset(m, 'x', 10);
	VALGRIND_CHECK_MEM_IS_DEFINED(m, 10);	/* OK */
	VALGRIND_CHECK_MEM_IS_DEFINED(m+10, 10); /* BAD */

	mprotect(m, pgsz*5, PROT_NONE);
	mprotect(m, pgsz*5, PROT_READ);

	VALGRIND_CHECK_MEM_IS_DEFINED(m, 10);	/* still OK */
	VALGRIND_CHECK_MEM_IS_DEFINED(m+20, 10); /* BAD */
}

static struct test {
	void (*test)(void);
	int faults;
} tests[] = {
	{ test1, 0 },
	{ test2, 1 },
	{ test3, 0 },
	{ test4, 1 },
	{ test5, 0 },
};
static const int n_tests = sizeof(tests)/sizeof(*tests);
	
int main()
{
	static const struct rlimit zero = { 0, 0 };
	int i;

	pgsz = getpagesize();
	setvbuf(stdout, NULL, _IOLBF, 0);

	setrlimit(RLIMIT_CORE, &zero);

	for(i = 0; i < n_tests; i++) {
		int pid;

		pid = fork();
		if (pid == -1) {
			perror("fork");
			exit(1);
		}
		if (pid == 0) {
			(*tests[i].test)();
			exit(0);
		} else {
			int status;
			int ret;
			
			printf("Test %d: ", i+1);
			fflush(stdout);

			while((ret = waitpid(pid, &status, 0)) != pid) {
				if (errno != EINTR) {
					perror("waitpid");
					exit(1);
				}
			}
			if (WIFSIGNALED(status)) {
				assert(WTERMSIG(status) != 0);

				if (1 == tests[i].faults &&
				    (WTERMSIG(status) == SIGSEGV ||
				     WTERMSIG(status) == SIGBUS))
					printf("PASS\n");
				else
					printf("died with unexpected signal %d\n", 
					       WTERMSIG(status));
			} else if (WIFEXITED(status)) {
				if (WEXITSTATUS(status) == 0) {
					if (tests[i].faults == 0)
						printf("PASS\n");
					else
						printf("exited without expected SIGSEGV or SIGBUS signal\n");
				} else
					printf("exited with unexpected status %d\n",
					       WEXITSTATUS(status));
			} else {
				printf("strange status %x?\n", status);
			}
		}
	}
	exit(0);
}
