#define _GNU_SOURCE

#include <stdio.h>

#include "tests/sys_mman.h"
#include <assert.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <syscall.h>


#ifndef REMAP_FIXED
#define MREMAP_FIXED 2
#endif


static int PAGE;

void mapanon_fixed ( void* start, size_t length )
{
  void* r = mmap(start, length, PROT_NONE, 
                 MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, 0,0);
  assert(r != MAP_FAILED);
  assert(r == start);
}

void unmap_and_check ( void* start, size_t length )
{
   int r = munmap( start, length );
   assert(r == 0);
}

char* workingarea = NULL;
char* try_dst     = NULL;

// set up working area so expansion limit is 20*PAGE
//
//   |   10   |   20   |   10   |   60   |
//   |  pre   |  src   |  FREE  |  post  |
//
//  A suitable attempted fixed dst is workingarea + 150*PAGE.

char* setup ( void* other_stuff, int other_len )
{
  if (!workingarea) {
     workingarea = mmap(0, 200*PAGE, PROT_NONE, 
                           MAP_ANONYMOUS|MAP_PRIVATE, 0,0);
     assert(workingarea);
     try_dst = workingarea + 150*PAGE;
     unmap_and_check(workingarea, 200*PAGE);
  }

  if (other_stuff) {
    unmap_and_check(other_stuff, other_len);
  }

  // get rid of the old working area
  unmap_and_check( workingarea, 200*PAGE);

  // pre block
  mapanon_fixed( workingarea + 0*PAGE, 9*PAGE);

  // the area
  mapanon_fixed( workingarea + 10*PAGE, 20*PAGE );

  // upper half
  mapanon_fixed( workingarea + 40*PAGE, 60*PAGE );

  return workingarea + 10*PAGE;
}

/* show the working area */
void show ( void )
{
  int i,r __attribute__((unused));
  for (i = 0; i < 200; i++) {
    r = mprotect( workingarea + i * PAGE, PAGE, PROT_NONE );
    // We used to print 'X' or '.' according to the mprotect result, but the
    // results are too variable and the test was never reliable.  So now we
    // just always print '.'.  At least this test gives mremap a thorough
    // working out and so will detect egregious problems like crashes.
    //printf("%c", r == 0 ? 'X' : '.');
    printf(".");
    if (i == 49 || i == 99 || i == 149) printf("\n");
  }
  printf("\n");
}


char* dst = NULL;
char* src = NULL;
char* dst_impossible = NULL;


char* identify ( char* p )
{
  if (p == dst)            return "dst";
  if (p == src)            return "src";
  if (p == dst_impossible) return "dst_imp!";
  if (p == try_dst)        return "dst_poss";
  return "other";
}

int main ( void )
{
  int alocal, maymove, fixed, nsi, dstpossible;
  int newsizes[6] = { 19, 20, 21, 29, 30, 31 };

  char* tidythis = NULL;
  int  tidylen = 0;
  int firsttime = 1;
  char buf[100];

  dst_impossible = (char*)(&alocal) + 500 * 1000 * 1000;

  PAGE = sysconf(_SC_PAGESIZE);

  for (maymove = 0; maymove <= 1 ; maymove++) {
  for (fixed = 0; fixed <= 1; fixed++) {
    printf("\n");
  for (nsi = 0; nsi < 6; nsi++) {
  for (dstpossible = 0; dstpossible <= 1; dstpossible++) {

    char* r;
    int newsize = newsizes[nsi] * PAGE;
    int flags = (maymove ? MREMAP_MAYMOVE : 0)  |
                (fixed ? MREMAP_FIXED : 0);
    dst = dstpossible ? try_dst : dst_impossible;
    src = setup( tidythis, tidylen );

    if (firsttime) {
       printf("dst_possible   = %p\n", try_dst );
       printf("dst_impossible = %p\n", dst_impossible );
       printf("           src = %p\n", src);
       printf("\n");
       sprintf(buf, "cat /proc/%d/maps", getpid());
       if (0) system(buf);
       firsttime = 0;
    }

    printf("maymv %d   fixed %d   newsz %2d   dstpo %d  dst %p ->  ",
	   maymove, fixed, newsizes[nsi], dstpossible, dst );
    r = (char*)
        syscall(__NR_mremap, src, 20*PAGE, newsize, flags, dst, 0 );
    // We used to print the address or error, but that was also unreliable.
    //if (r == MAP_FAILED)
    //  printf("error %d\n", errno);
    //else
    //  printf("%p (== %s)\n", r, identify(r));
    printf("\n");

    if (1) {
       show();
       printf("\n");
    }

    if (r != MAP_FAILED) {
      if (r != src && r != try_dst && r != dst_impossible) {
	tidythis = r;
	tidylen = newsize;
      }
    }

  }
  }
  }
  }
  return 0;
}