/*----------------------------------------------------------------------------
 * (C) 1997-2003 Armin Biere 
 *
 *     $Id: wrapper.c,v 1.5 2003/02/03 08:03:56 biere Exp $
 *----------------------------------------------------------------------------
 */

#include "config.h"

/* ------------------------------------------------------------------------ */

/* Uncomment the next line to get a raw logging of all calls to malloc
 * and free.
 */

/*
#define LOG_EXTERNAL_MALLOC
*/

/* ------------------------------------------------------------------------ */

#include <dirent.h>
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#ifndef NDEBUG
#include <signal.h>
#endif

extern void *ccmalloc_malloc (size_t);
extern void ccmalloc_free (void *);
extern int ccmalloc_size (void *, size_t *);
extern void ccmalloc_die_or_continue (char *fmt, ...);

#ifndef RTLD_NOW
#define RTLD_NOW 1
#endif

#define MALLOC_SYMBOL "malloc"
#define FREE_SYMBOL "free"

/* ------------------------------------------------------------------------ */
/* This should work. But sometimes it is not enough. Then you have to set
 * this to a higher value and recompile ccmalloc.
 */

#define MAX_STATIC_MEM 100000

/* ------------------------------------------------------------------------ */

enum WrapperState
{
  WRAPPER_UNINITIALIZED,
  WRAPPER_INITIALIZING,
  WRAPPER_INITIALIZED
};

/* ------------------------------------------------------------------------ */

static void *(*libc_malloc) (size_t) = 0;
static void (*libc_free) (void *) = 0;
static enum WrapperState wrapper_state = WRAPPER_UNINITIALIZED;
static int semaphore = 0;

static char static_mem[MAX_STATIC_MEM];
static char *bottom_static_mem = static_mem;
static char *top_static_mem = static_mem + MAX_STATIC_MEM;

static long wrapper_bytes_allocated = 0,
  wrapper_num_allocated = 0, wrapper_num_deallocated = 0;

/* ------------------------------------------------------------------------ */

static int
wrapper_strlen (const char *str)
{
  const char *p;
  for (p = str; *p; p++)
    ;

  return p - str;
}

/* ------------------------------------------------------------------------ */
/* can not call `printf' because it calls malloc itself */

static void
print (const char *str)
{
  write (2, str, wrapper_strlen (str));
}

/* ------------------------------------------------------------------------ */
#ifdef LOG_EXTERNAL_MALLOC
/* ------------------------------------------------------------------------ */
/* unfortunately we can not use `printf' since `printf' calls malloc itself
 */
static char print_hex_tab[16] = {
  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
  'f'
};

/* 32Bit: sizeof(unsigned) == 4 */

static void
print_hex (unsigned d)
{
  char buffer[9];

  buffer[7] = print_hex_tab[d % 16];
  d /= 16;
  buffer[6] = print_hex_tab[d % 16];
  d /= 16;
  buffer[5] = print_hex_tab[d % 16];
  d /= 16;
  buffer[4] = print_hex_tab[d % 16];
  d /= 16;
  buffer[3] = print_hex_tab[d % 16];
  d /= 16;
  buffer[2] = print_hex_tab[d % 16];
  d /= 16;
  buffer[1] = print_hex_tab[d % 16];
  d /= 16;
  buffer[0] = print_hex_tab[d % 16];
  d /= 16;

  buffer[8] = 0;

  print (buffer);
}

static void
print_dec (unsigned d)
{
  if (d)
    {
      char buffer[11];		/* also 32 bit !! */
      int i = 10;

      buffer[11] = 0;

      while (1)
	{
	  buffer[i] = print_hex_tab[d % 10];
	  d /= 10;
	  if (d)
	    i--;
	  else
	    break;
	}

      print (buffer + i);
    }
  else
    print ("0");
}

/* ------------------------------------------------------------------------ */
#endif
/* ------------------------------------------------------------------------ */

static void
load_libc ()
{
  void *handle;
  const char *err;
  char *libcname;

  libcname = NAME_OF_LIBC;

  handle = dlopen (libcname, RTLD_NOW);
  if ((err = dlerror ()))
    {
      print ("*** wrapper can not open `");
      print (libcname);
      print ("'!\n");
      print ("*** dlerror() reports: ");
      print (err);
      print ("\n");
      exit (1);
    }

  libc_malloc = (void *(*)(size_t)) dlsym (handle, MALLOC_SYMBOL);
  if ((err = dlerror ()))
    {
      print ("*** wrapper does not find `");
      print (MALLOC_SYMBOL);
      print ("' in `libc.so'!\n");
      exit (1);
    }

  libc_free = (void (*)(void *)) dlsym (handle, FREE_SYMBOL);
  if ((err = dlerror ()))
    {
      print ("*** wrapper does not find `");
      print (FREE_SYMBOL);
      print ("' in `libc.so'!\n");
      exit (1);
    }
}

/* ------------------------------------------------------------------------ */
/* just play it save and always do an 8 byte alignment
 */

#define WRAPPER_ALIGN_MASK 7

/* ------------------------------------------------------------------------ */

#define wrapper_isAligned(c) ((((unsigned)c) & WRAPPER_ALIGN_MASK) == 0)

#define wrapper_align(c)                                        \
{                                                               \
  if(!wrapper_isAligned(c))                                     \
    {                                                           \
      *(unsigned*)&c = (((unsigned)c) | WRAPPER_ALIGN_MASK);    \
      *(unsigned*)&c = (((unsigned)c) + 1);                     \
    }                                                           \
}

/* ------------------------------------------------------------------------ */

static void *
static_malloc (size_t n)
{
  char *res;
  int sizeof_size_t;
  size_t *where_to_store_size, aligned_size;

  aligned_size = n;
  sizeof_size_t = sizeof (sizeof_size_t);

  wrapper_align (aligned_size);
  wrapper_align (sizeof_size_t);
  wrapper_align (bottom_static_mem);

  where_to_store_size = (size_t *) bottom_static_mem;
  bottom_static_mem += sizeof_size_t;	/* aligned + aligned = aligned */
  res = bottom_static_mem;
  bottom_static_mem += aligned_size;

  if (bottom_static_mem >= top_static_mem)
    {
      print ("*** static memory in wrapper exceeded\n");
      exit (1);
    }

  *where_to_store_size = n;

  return (void *) res;
}

/* ------------------------------------------------------------------------ */

static void
static_free (void *p)
{
#ifndef NDEBUG
  {
    if (((char *) p) < static_mem || ((char *) p) >= top_static_mem)
      {
	print ("*** nonvalid arg for static_free() in wrapper\n");
	kill (0, SIGSEGV);	// simulate assert
	exit (1);
      }
  }
#else
  (void) p;
#endif
}

/* ------------------------------------------------------------------------ */

void
libcwrapper_inc_semaphore ()
{
  semaphore++;
}

void
libcwrapper_dec_semaphore ()
{
  semaphore--;
}

/* ------------------------------------------------------------------------ */

void *
malloc (size_t n)
{
  void *res;

  switch (wrapper_state)
    {
    case WRAPPER_UNINITIALIZED:

      wrapper_state = WRAPPER_INITIALIZING;
      load_libc ();
      wrapper_state = WRAPPER_INITIALIZED;

      /* !!!!!!! fall through !!!!!!! */

    case WRAPPER_INITIALIZED:

      if (semaphore)
	{
	  wrapper_num_allocated++;
	  wrapper_bytes_allocated += n;
	  res = (*libc_malloc) (n);
	}
      else
	{
	  semaphore++;
	  res = ccmalloc_malloc (n);
	  semaphore--;
	}
      break;

    default:
    case WRAPPER_INITIALIZING:

      res = static_malloc (n);
      break;
    }

# ifdef LOG_EXTERNAL_MALLOC
  {
    print ("::: 0x");
    print_hex ((unsigned) res);
    if (wrapper_state == WRAPPER_INITIALIZING)
      print (" static  ");
    else if (semaphore)
      print (" internal");
    else
      print (" external");
    print (" malloc(");
    print_dec (n);
    print (")\n");
  }
# endif

  /* Initialize with something weird
   */
  if (res)
    memset (res, 0x42, n);

  return res;
}

/* ------------------------------------------------------------------------ */

void
free (void *p)
{
# ifndef NDEBUG
  {
    if (wrapper_state != WRAPPER_INITIALIZING)
      {
	if (static_mem <= ((char *) p) && ((char *) p) < top_static_mem)
	  {
	    print ("*** free() in wrapper called for argument\n");
	    print ("*** that was allocated during initialization!\n");
	  }
      }
  }
# endif

  switch (wrapper_state)
    {
    case WRAPPER_UNINITIALIZED:

      print ("*** free() in wrapper called before malloc()\n");
      exit (1);
      break;

    case WRAPPER_INITIALIZED:

      if (semaphore)
	{
	  wrapper_num_deallocated++;
	  (*libc_free) (p);
	}
      else
	{
	  semaphore++;
	  ccmalloc_free (p);
	  semaphore--;
	}
      break;

    default:
    case WRAPPER_INITIALIZING:

      static_free (p);
      break;
    }

# ifdef LOG_EXTERNAL_MALLOC
  {
    print ("::: 0x");
    print_hex ((unsigned) p);
    if (semaphore)
      print (" internal");
    else
      print (" external");
    print (" free\n");
  }
# endif
}

/* ------------------------------------------------------------------------ */

void *
calloc (size_t nmemb, size_t size)
{
  void *res;
  size *= nmemb;
  res = malloc (size);
  if (res)
    memset (res, 0, size);
  return res;
}

/* ------------------------------------------------------------------------ */

static void *
_realloc (void *ptr, size_t size, size_t previous_size)
{
  void *res;

  res = (void *) malloc (size);
  if (size < previous_size)
    (void) memcpy (res, ptr, size);
  else
    (void) memcpy (res, ptr, previous_size);
  free (ptr);

  return res;
}

/* ------------------------------------------------------------------------ */

void *
realloc (void *ptr, size_t size)
{
  size_t previous_size, sizeof_size_t;
  char *where_size_was_stored;
  void *res;

  if (ptr == 0)
    res = malloc (size);
  else if (size == 0)
    {
      free (ptr);
      res = 0;
    }
  else
    {
      switch (wrapper_state)
	{
	default:
	  print ("*** ccmalloc: non valid wrapper state in realloc()\n");
	  exit (1);
	  res = 0;
	  break;

	case WRAPPER_UNINITIALIZED:
	  print ("*** realloc() in wrapper called before malloc()\n");
	  exit (1);
	  res = 0;
	  break;

	case WRAPPER_INITIALIZING:
	  sizeof_size_t = sizeof (size_t);
	  wrapper_align (sizeof_size_t);
	  where_size_was_stored = (char *) ptr;
	  where_size_was_stored -= sizeof_size_t;
	  previous_size = *(size_t *) where_size_was_stored;
	  res = _realloc (ptr, size, previous_size);
	  break;

	case WRAPPER_INITIALIZED:
	  if (ccmalloc_size (ptr, &previous_size))
	    {
	      res = _realloc (ptr, size, previous_size);
	    }
	  else
	    {
	      ccmalloc_die_or_continue
		("*** realloc(0x%x) called with non valid argument\n",
		 (unsigned) ptr);
	      res = malloc (size);
	    }
	  break;
	}
    }

  return res;
}

/* ------------------------------------------------------------------------ */

const char *
wrapper_stats ()
{
  static char buffer[200];
  sprintf (buffer,
	   "%ld allocations, %ld deallocations, %ld bytes allocated",
	   wrapper_num_allocated, wrapper_num_deallocated,
	   wrapper_bytes_allocated);
  return buffer;
}
