#include <stdio.h>
#include "tests/sys_mman.h"
#include <stdlib.h>
#include <unistd.h>

/* The code testing MAP_HUGETLB huge pages is disabled by default,
   as many distros do not have huge pages configured
   by default.
   To have e.g. 20 huge pages configured, do (as root)
      echo 20 > /proc/sys/vm/nr_hugepages
  Once this is done, uncomment the below, and recompile.
*/
//#define TEST_MAP_HUGETLB 1

/* Similarly, testing SHM_HUGETLB huge pages is disabled by default.
   To have shmget/shmat big pages working, do (as root)
      echo 500 > /proc/sys/vm/hugetlb_shm_group
   where 500 is the groupid of the user that runs this test
  Once this is done, uncomment the below, and recompile.
*/
//#define TEST_SHM_HUGETLB 1

// Size to use for huge pages
#define HUGESZ (4 * 1024 * 1024)

#ifdef TEST_MAP_HUGETLB
/* Ensure this compiles on pre 2.6 systems, or on glibc missing MAP_HUGETLB */
#ifndef MAP_HUGETLB
/* The below works for me on an f12/x86 linux */
#define MAP_HUGETLB 0x40000
#endif

#endif /* TEST_MAP_HUGETLB */

#ifdef TEST_SHM_HUGETLB
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#ifndef SHM_HUGETLB
#define SHM_HUGETLB 04000
#endif
#endif  /* TEST_SHM_HUGETLB */

static unsigned int pagesize;

#define PAGES	1024u
#define LEN	(PAGES*pagesize)

static void *domap(size_t len, int addflags)
{
	void *ret = mmap(0, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|addflags, -1, 0);

	if (ret == (void *)-1) {
		perror("mmap");
		exit(1);
	}

	return ret;
}

/* unmap in pieces to exercise munmap more */
static void nibblemap(void *p)
{
	int off;
	int i;

	off = (random() % LEN) & ~(pagesize-1);
	
	for(i = 0; i < PAGES; i++) {
		/* printf("unmapping off=%d\n", off/pagesize); */
		munmap((char *)p + off, pagesize);
		off += 619*pagesize;
		off %= LEN;
	}
}

static void prmaps()
{
	char buf[100];
	sprintf(buf, "/bin/cat /proc/%d/maps", getpid());
	system(buf);
	exit(1);
}

int main()
{
	int i;
	void *expect1, *expect2;

	pagesize = getpagesize();

	expect1 = domap(LEN, 0);
	expect2 = domap(LEN, 0);
	munmap(expect1, LEN);
	munmap(expect2, LEN);

	for(i = 0; i < 5; i++) {
		void *m1, *m2;

		m1 = domap(LEN, 0);
		if (m1 != expect1) {
			printf("FAIL i=%d: m1=%p expect1=%p\n",
			       i, m1, expect1);
			prmaps();
			return 1;
		}
		m2 = domap(LEN, 0);
		if (m2 != expect2) {
			printf("FAIL i=%d: m2=%p expect2=%p\n",
			       i, m2, expect2);
			prmaps();
			return 1;
		}
		nibblemap(m2);
		munmap(m1, LEN);
	}

#ifdef  TEST_MAP_HUGETLB
        {
           void *expect3;
           expect3 = domap(HUGESZ, MAP_HUGETLB);
           munmap(expect3, HUGESZ);
        }
#endif

#ifdef TEST_SHM_HUGETLB
        {
           int shmid;
           void *expect4;


           shmid = shmget(IPC_PRIVATE, HUGESZ, 
                          IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR | SHM_HUGETLB);
           if (shmid == -1) {
              perror("shmget");
              exit(1);
           }
           expect4 = shmat(shmid, NULL, 0);
           if (expect4 == (void*) -1){
              perror("shmat");
              exit(1);
           }
           if (shmdt(expect4) != 0) {
              perror("shmdt");
              exit(1);
           }
           if (shmctl(shmid, IPC_RMID, 0) != 0) {
              perror("shmctl IPC_RMID");
              exit(1);
           }         
        }
#endif

	printf("PASS\n");
	return 0;
}