
/*
 * DUMA - Red-Zone memory allocator.
 * Copyright (C) 2002-2008 Hayati Ayguen <h_ayguen@web.de>, Procitec GmbH
 * Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>
 * License: GNU GPL (GNU General Public License, see COPYING-GPL)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *
 * FILE CONTENTS:
 * DUMA confidence tests.
 * Make sure all of the various functions of DUMA work correctly.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#if !defined(WIN32) || defined(__CYGWIN__)
  #include <unistd.h>
#else
  #include <io.h>
#endif
#include <setjmp.h>
#include <signal.h>

#include "duma.h"


#ifndef  PAGE_PROTECTION_VIOLATED_SIGNAL
/* changed default in code below to use two signals: SIGSEGV and SIGBUS */
/* #define  PAGE_PROTECTION_VIOLATED_SIGNAL  SIGSEGV */
#endif

struct diagnostic
{
  int           (*test)(void);      /* pointer to some test function returning int */
  int           expectedStatus;     /* expected return value/status */
  const char  * explanation;        /* explanation of that test */
};


#if !defined(WIN32) || defined(__CYGWIN__)
static sigjmp_buf  env;
#else
static jmp_buf  env;
#endif

/*
 * There is still too little standardization of the arguments and return
 * type of signal handler functions.
 */
static void
segmentationFaultHandler(
  int signalNumber
#if ( defined(_AIX) )
  , ...
#endif
)
{
  (void)signalNumber;
#if !defined(WIN32) || defined(__CYGWIN__)
  siglongjmp(env, 1);
#else
  longjmp(env, 1);
#endif
}


static int
gotSegmentationFault(int (*test)(void))
{
#if !defined(WIN32) || defined(__CYGWIN__)
  sigset_t newmask, oldmask;
  int savemask;
#endif
#ifdef PAGE_PROTECTION_VIOLATED_SIGNAL
  void (*oldhandler)(int)         = SIG_ERR;
#else
  void (*oldSIGSEGVhandler)(int)  = SIG_ERR;

#if !defined(WIN32) || defined(__CYGWIN__)
  void (*oldSIGBUShandler)(int)   = SIG_ERR;
#endif
#endif
  int status;


#if !defined(WIN32) || defined(__CYGWIN__)
  if ( 0 == sigsetjmp(env, savemask) )
#else
  if ( 0 == setjmp(env) )
#endif
  {
#if !defined(WIN32) || defined(__CYGWIN__)
    /* unblock signal and save previous signal mask */
    sigemptyset(&newmask);
  #ifdef PAGE_PROTECTION_VIOLATED_SIGNAL
    sigaddset(&newmask, PAGE_PROTECTION_VIOLATED_SIGNAL);
  #else
    sigaddset(&newmask, SIGSEGV);
    sigaddset(&newmask, SIGBUS);
  #endif
    sigprocmask(SIG_UNBLOCK, &newmask, &oldmask);
#endif

#ifdef PAGE_PROTECTION_VIOLATED_SIGNAL
    oldhandler = signal(PAGE_PROTECTION_VIOLATED_SIGNAL, segmentationFaultHandler);
#else
    oldSIGSEGVhandler = signal(SIGSEGV, segmentationFaultHandler);
  #if !defined(WIN32) || defined(__CYGWIN__)
    oldSIGBUShandler  = signal(SIGBUS, segmentationFaultHandler);
  #endif
#endif

    status = (*test)();
  }
  else
    status = 1;

  /* install previous signal handler */

#ifdef PAGE_PROTECTION_VIOLATED_SIGNAL
  if (SIG_ERR != oldhandler)
    signal(PAGE_PROTECTION_VIOLATED_SIGNAL, oldhandler);
#else
  if (SIG_ERR != oldSIGSEGVhandler)
    signal(SIGSEGV, oldSIGSEGVhandler);
#if !defined(WIN32) || defined(__CYGWIN__)
  if (SIG_ERR != oldSIGBUShandler)
    signal(SIGBUS, oldSIGBUShandler);
#endif
#endif

#if !defined(WIN32) || defined(__CYGWIN__)
  /* restore signal mask */
  sigprocmask(SIG_SETMASK, &oldmask, NULL);
#endif
  return status;
}


static char *  allocation = (char*)0;
/* c is global so that assignments to it won't be optimized out. */
char  c;

static int
testSizes(void)
{
  /*
   * If DUMA_ADDR can't hold all of the bits of a void *,
   * have the user call createconf.
   */
  size_t sd = sizeof(DUMA_ADDR);
  size_t sv = sizeof(void *);
  return ( sd < sv );
}

static int
allocateMemory(void)
{
  allocation = (char *)malloc(1);

  if ( allocation != (char*)0 )
    return 0;
  else
    return 1;
}

static int
freeMemory(void)
{
  free(allocation);
  allocation = (char*)0;
  return 0;
}

static int
protectBelow(void)
{
  DUMA_SET_PROTECT_BELOW(1);
  return 0;
}

static int
protectAbove(void)
{
  DUMA_SET_PROTECT_BELOW(0);
  return 0;
}

static int
read0(void)
{
  c = *allocation;
  return 0;
}

static int
write0(void)
{
  *allocation = 1;
  return 0;
}

static int
read1(void)
{
  c = allocation[1];
  return 0;
}

static int
readMinus1(void)
{
  c = allocation[-1];
  return 0;
}


static struct diagnostic diagnostics[] =
{
  {
    testSizes, 0,      "Please add -DLONG_LONG to the compiler flags and recompile."
  },
#if 1
  {
    protectAbove, 0,   "Protect above: This sets DUMA to protect\n"
                       "the upper boundary of a malloc buffer, rather than the lower boundary."
  },
  {
    allocateMemory, 0, "Allocation 1: This test allocates a single byte of memory."
  },
  {
    read0, 0,          "Read valid memory 1: This test reads the allocated memory."
  },
  {
    write0, 0,         "Write valid memory 1: This test writes the allocated memory."
  },
  {
    readMinus1, 0,     "Read underrun: This test reads before the beginning of the buffer."
  },
  {
    read1, 1,          "Read overrun: This test reads beyond the end of the buffer."
  },
  {
    freeMemory, 0,     "Free memory 1: This test frees the allocated memory."
  },
#endif
#if 1
  {
    protectBelow, 0,   "Protect below: This sets DUMA to protect\n"
                       "the lower boundary of a malloc buffer, rather than the upper boundary."
  },
  {
    allocateMemory, 0, "Allocation 2: This allocates memory with the lower boundary protected."
  },
  {
    read0, 0,          "Read valid memory 2: This test reads the allocated memory."
  },
  {
    write0, 0,         "Write valid memory 2: This test writes the allocated memory."
  },
  {
    readMinus1, 1,     "Read underrun: This test reads before the beginning of the buffer."
  },
  {
    freeMemory, 0,     "Free memory 2: This test frees the allocated memory."
  },
#endif
  {
    0, 0, 0
  }
};


static const char  failedTest[] = "DUMA confidence test failed.\n";
static const char  newline = '\n';


int
main(int argc, char * * argv)
{
  static const struct diagnostic *  diag = diagnostics;
  int testno;

  (void)argc;
  (void)argv;

#ifdef DUMA_EXPLICIT_INIT
  duma_init();
#endif

  DUMA_SET_PROTECT_BELOW(0);
  DUMA_SET_ALIGNMENT(DUMA_MIN_ALIGNMENT);

  allocation = 0;

  for (testno=0; diag->explanation != 0; ++testno, ++diag)
  {
    int status;

#if 0
    write(0, diag->explanation, strlen(diag->explanation));
    write(0, &newline, 1);
#endif

    status = gotSegmentationFault(diag->test);

    if ( status != diag->expectedStatus )
    {
      /*
       * Don't use stdio to print here, because stdio
       * uses malloc() and we've just proven that malloc()
       * is broken. Also, use _exit() instead of exit(),
       * because _exit() doesn't flush stdio.
       */
      write(2, failedTest, sizeof(failedTest) - 1);
      write(2, diag->explanation, strlen(diag->explanation));
      write(2, &newline, 1);
      _exit(-1);
    }
  }

  /* avoid memory leak */
  if (allocation)
    freeMemory();

#if 0
  {
    char * dynmemA;
    char * dynmemB;

    /* test for DUMA_CHECK_FREQ */
    printf("0\n");
    protectAbove();
    printf("1\n");
    dynmemA = (char*)malloc( 10 * sizeof(char) );
    printf("2\n");
    dynmemA[-1 ] = 0;
    printf("3\n");
    dynmemB = (char*)malloc( 11 * sizeof(char) );
    printf("4\n");
    free( dynmemB );
    printf("5\n");
    free( dynmemA );
    printf("6\n");
  }
#endif

  return 0;
}
