/* Test of external malloc, calloc, realloc and free capability */

#if 1
#include "test.h"
#include "usctest.h"
#else
enum {TPASS, TFAIL, TBROK, TINFO};
#define tst_resm(xxx, yyy...) printf(yyy), printf(" RES %d\n", xxx)
#define tst_brkm(xxx, yyy, zzz...) printf(zzz), printf(" RES %d\n", xxx)
#define tst_exit()
int Tst_count;
#endif

#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>

const char *TCID = "malloc";    /* Test program identifier. */
int TST_TOTAL = 2;              /* Total number of test cases. */
extern int Tst_count;           /* Test Case counter for tst_* routines */

/* Main test.
   Verbose mode if argc > 0
   Note that malloc and friends are also called by Cygwin before main,
   and that malloc can call getenv. */

int malloc_error = 0, realloc_error = 0, free_error = 0; 
int calloc_count = 0, malloc_count = 0, realloc_count = 0, free_count = 0;

void
cleanup (void)
{
  tst_exit(); 
}

int
syncwithchild (pid_t pid, int expected_exit_status)
{
  int status;

  if (waitpid (pid, &status, 0) != pid)
    {
      tst_brkm (TBROK, cleanup, "Wait for child: %s", strerror (errno));
      return 1;
    }
  if (!WIFEXITED (status))
    {
      tst_brkm (TBROK, cleanup, "Child had abnormal exit");
      return 1;
    }
  if (WEXITSTATUS (status) != expected_exit_status)
    {
      tst_brkm (TFAIL, cleanup, "Child had exit status %d != %d",
		WEXITSTATUS (status), expected_exit_status);
      return 1;
    }
  return 0;
}

#if 0
void * mallocX (size_t size);
void * callocX (size_t nmemb, size_t size);
void * reallocX (void * ptr, size_t size);
void freeX(void *);

#define malloc mallocX
#define calloc callocX
#define realloc reallocX
#define free freeX
#endif

int main (int argc, char * argv[])
{ 
  void * ptr;
  int error = 0;
  pid_t pid;

  Tst_count = 0;

  tst_resm(TINFO, "Testing if external malloc works. ppid %d", getppid());

  ptr = malloc (16);
  ptr = calloc (1, 16);
  ptr = realloc (ptr, 24);
  free (ptr);

  error = (malloc_count == 0 || calloc_count == 0 || realloc_count == 0 || free_count == 0);

  if (error || argc > 1)
    {
      printf ("malloc_count %d, calloc_count %d, realloc_count %d, free_count %d\n", 
	      malloc_count, calloc_count, realloc_count, free_count);
      printf ("malloc_error %d, realloc_error %d, free_error %d\n",
	      malloc_error, realloc_error, free_error);
    }
  tst_resm (!error ? TPASS : TFAIL, "Running in pid %d", getpid());

  /* If run from Windows, run also from Cygwin */
  if (getppid() == 1)
    {
      tst_resm(TINFO, "Ready to test if malloc works from Cygwin");

      if ((pid = fork()) == 0)
	{
	  tst_resm(TINFO, "Ready to exec with pid %d\n", getpid());
	  error = execl(argv[0], argv[0], argc > 1? argv[1]:NULL, NULL);
	  exit(error);
	}
      else if (pid < 0)
	tst_brkm (TBROK, cleanup, "Fork failed: %s", strerror (errno));
      else
	{
	  error = syncwithchild (pid, 0);
	  tst_resm (!error ? TPASS : TFAIL, "Running in pid %d", pid);
	}
    }

  tst_exit ();
}

/****************************************
Actual malloc & friends implementation 
****************************************/

typedef unsigned long long ull;
 
#define SIZE (1024*1024ULL) /* long long */ 
ull buffer[SIZE]; 
ull * current = buffer;

static int is_valid (void * ptr)
{
  uintptr_t iptr = (uintptr_t) ptr;
  ull * ullptr = (ull *) ptr;

  iptr = (iptr / sizeof(ull)) * sizeof(ull);
  if (iptr != (uintptr_t) ptr)
    return 0;
  if (--ullptr < buffer || ullptr[0] > SIZE || ullptr  + ullptr[0]  > current)
    return 0;
  return 1;
}

void * malloc (size_t size)
{
  ull llsize = (size + 2 * sizeof (ull) - 1) / sizeof (ull);
  static char * envptr;
  void * ret;
  
  /* Make sure getenv works */
  if (!envptr)
    envptr = getenv ("PATH");

  malloc_count++;
  if (current + llsize >= buffer + SIZE) 
    {
      malloc_error++;
      errno = ENOMEM;
      return NULL;
    }
  *current = llsize;
  ret = (void *) (current + 1);
  current += llsize;

  return ret;
}

void * calloc (size_t nmemb, size_t size) 
{
  calloc_count++;
  void * ptr = malloc (nmemb * size);
  malloc_count--;
  if (ptr)
    memset(ptr, 0, nmemb * size);
  return ptr;
}

void * realloc (void * ptr, size_t size)
{
  const ull ullsize = (size + 2 * sizeof (ull) - 1) / sizeof (ull);
  ull * const ullptr = (ull *) ptr;
  void * newptr;
  
  realloc_count++;
  
  if (ptr)
    {
      if (!is_valid (ptr))
	{
	  realloc_error++;
	  errno = ENOMEM;
	  return NULL;
	}  
      if (ullptr[-1] >= ullsize)
	return ptr;
    }

  newptr = malloc (size);
  malloc_count--;
  
  if (ptr && newptr)
    memcpy (newptr, ptr, size);
  
  return newptr;
}

void free (void * x)
{
  free_count++;
  if (x && ! is_valid (x))
      free_error++;
}

