/**************************************************************************
*  File: zmalloc.c                                         Part of tbaMUD *
*  Usage: A simple memory allocation monitor.                             *
*                                                                         *
*  Version 2. Copyright 1996, 1998, 1999, 2000 Eric Murray ericm@lne.com  *
**************************************************************************/

/*  Usage: To run tbaMUD in debug mode change the flags line in your Makefile
 *  as below, make clean, and reboot.
 *
 *  Makefile: # Any special flags you want to pass to the compiler
 *  Makefile: MYFLAGS = -Wall -DMEMORY_DEBUG */

#include "conf.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define NUM_ZBUCKETS 256
#define GET_ZBUCKET(addr) (((long)(addr) >> 3) & 0xFF)

//#define NO_MEMORY_PADDING

#ifndef NO_MEMORY_PADDING
static unsigned char beginPad[4] = {
  0xde, 0xad, 0xde, 0xad };

static unsigned char endPad[4] = {
  0xde, 0xad, 0xde, 0xad };
#endif

static FILE *zfd = NULL;

typedef struct meminfo {
  struct meminfo *next;
  int size; /* number of bytes malloced */
  unsigned char *addr;  /* address of memory returned */
  int frees; /* number of times that 'free' was called on this memory */
  char *file; /* file where malloc was called */
  int line; /* line in the code where malloc was called */
} meminfo;

static meminfo *memlist[NUM_ZBUCKETS];

/*
 * 0 = only end summary
 * 1 = show errors
 * 2 = errors with dumps
 * 3 = all of the above plus all mallocs/frees
 */
static int zmalloclogging = 2;

/* functions: */
unsigned char *zmalloc(int len, char *file, int line);
unsigned char *zrealloc(unsigned char *what, int len, char *file, int line);
void zdump(meminfo *m);
void zfree(unsigned char *what, char *file, int line);
char *zstrdup(const char *src, char *file, int line);
void zmalloc_init(void);
void zmalloc_check(void);
void pad_check(meminfo *m);
void zmalloc_free_list(meminfo *m);


void zmalloc_init(void)
{
  int i;

  for (i = 0; i < NUM_ZBUCKETS; i++)
    memlist[i] = NULL;

  zfd = fopen("zmalloc.log","w+");
}


void zdump(meminfo *m)
{
#define MAX_ZDUMP_SIZE 32
  const unsigned char *hextab = (unsigned char *)"0123456789ABCDEF";
  unsigned char hexline[37], ascline[17], *hexp, *ascp, *inp;
  int len, c = 1;

  if (m->addr == NULL || m->size <= 0)
    return;

  hexp = hexline;
  ascp = ascline;
  inp = m->addr;
  len = (m->size > MAX_ZDUMP_SIZE ? MAX_ZDUMP_SIZE : m->size);

  for ( ; len > 0; len--, inp++, c++) {
    *(hexp++) = hextab[(int) (*inp & 0xF0) >> 4];	/* high 4 bit */
    *(hexp++) = hextab[(int) (*inp & 0x0F)];		/* low 4 bit */
    if (c % 4 == 0) *(hexp++) = ' ';
    *(ascp++) = isprint(*inp) ? *inp : '.';
    if (c % 16 == 0 || len <= 1) {
      *hexp = '\0';
      *ascp = '\0';
      fprintf(zfd, "     %-40.40s%s\n", hexline, ascline);
      hexp = hexline;
      ascp = ascline;
    }
  }
  fprintf(zfd, "\n");
}


unsigned char *zmalloc(int len, char *file, int line)
{
  unsigned char *ret;
  meminfo *m;

#ifndef NO_MEMORY_PADDING
  ret = (unsigned char *) calloc(1, len + sizeof(beginPad) + sizeof(endPad));
#else
  ret = (unsigned char *) calloc(1, len);
#endif

  if (!ret) {
    fprintf(zfd,"zmalloc: malloc FAILED");
    return NULL;
  }
#ifndef NO_MEMORY_PADDING
  /* insert begin and end padding to detect buffer under/overruns: */
  memcpy(ret, beginPad, sizeof(beginPad));
  ret +=  sizeof(beginPad);  /* make ret skip begin pad */
  memcpy(ret + len, endPad, sizeof(endPad));
#endif

  if (zmalloclogging > 2)
    fprintf(zfd,"zmalloc: 0x%p  %d bytes %s:%d\n", (void *)ret, len, file, line);

  m = (meminfo *) calloc(1, sizeof(meminfo));
  if (!m) {
    fprintf(zfd,"zmalloc: FAILED mem alloc for zmalloc struct... bailing!\n");
    return NULL;
  }
  m->addr = ret;
  m->size = len;
  m->frees = 0;
  m->file = strdup(file);
  if (!m->file) {
    fprintf(zfd,"zmalloc: FAILED mem alloc for zmalloc struct... bailing!\n");
    free(m);
    return NULL;
  }
  m->line = line;
  m->next = memlist[GET_ZBUCKET(ret)];
  memlist[GET_ZBUCKET(ret)] = m;
  return (ret);
}

unsigned char *zrealloc(unsigned char *what, int len, char *file, int line)
{
  unsigned char *ret;
  meminfo *m, *prev_m;

  if (what) {
    for (prev_m = NULL, m = memlist[GET_ZBUCKET(what)]; m; prev_m = m, m = m->next) {
      if (m->addr == what) {
#ifndef NO_MEMORY_PADDING
	ret = (unsigned char *) realloc(what - sizeof(beginPad), len + sizeof(beginPad) + sizeof(endPad));
#else
	ret = (unsigned char *) realloc(what, len);
#endif
	if (!ret) {
	  fprintf(zfd,"zrealloc: FAILED for 0x%p %d bytes mallocd at %s:%d,\n"
		      "          %d bytes reallocd at %s:%d.\n",
		    (void *)m->addr, m->size, m->file, m->line, len, file, line);
	  if (zmalloclogging > 1) zdump(m);
	  return NULL;
	}
#ifndef NO_MEMORY_PADDING
	/* insert begin and end padding to detect buffer under/overruns: */
	memcpy(ret, beginPad, sizeof(beginPad));
	ret +=  sizeof(beginPad);  /* make ret skip begin pad */
	memcpy(ret + len, endPad, sizeof(endPad));
#endif
	if (zmalloclogging > 2)
	  fprintf(zfd,"zrealloc: 0x%p %d bytes mallocd at %s:%d, %d bytes reallocd at %s:%d.\n",
		    (void *)m->addr, m->size, m->file, m->line, len, file, line);

	m->addr = ret;
	m->size = len;
	if (m->file) free(m->file);
	m->file = strdup(file);
	m->line = line;

        /* detach node */
        if (prev_m)
          prev_m->next = m->next;
        else
          memlist[GET_ZBUCKET(what)] = m->next;

        /* readd to proper bucket */
        m->next = memlist[GET_ZBUCKET(ret)];
        memlist[GET_ZBUCKET(ret)] = m;

	/* could continue the loop to check for multiply-allocd memory */
	/* but that's highly improbable so lets just return instead. */
	return (ret);
      }
    }
  }

  /* NULL or invalid pointer given */
  fprintf(zfd,"zrealloc: invalid pointer 0x%p, %d bytes to realloc at %s:%d.\n",
	    (void *)what, len, file, line);

  return (zmalloc(len, file, line));
}

/* doesn't actually free memory */
void zfree(unsigned char *what, char *file, int line)
{
  meminfo *m;
  int gotit = 0;

  if (!what) {
    fprintf(zfd,"zfree: ERR: Null pointer free'd: %s:%d.\n", file, line);
    return;
  }

  /* look up allocated mem in list: */
  for (m = memlist[GET_ZBUCKET(what)]; m; m = m->next) {
    if (m->addr == what) {
      /* got it.  Print it if verbose: */
      if (zmalloclogging > 2) {
	fprintf(zfd,"zfree: Freed 0x%p %d bytes mallocd at %s:%d, freed at %s:%d\n",
		    (void *)m->addr, m->size, m->file, m->line, file, line);
      }
      /* check the padding: */
      pad_check(m);

      /* note that we freed the memory */
      m->frees++;  

      /* check to see if it was freed > once */
      if (m->frees > 1) {
        fprintf(zfd,"zfree: ERR: multiple frees! 0x%p %d bytes\n"
		    "       mallocd at %s:%d, freed at %s:%d.\n",
                    (void *)m->addr, m->size, m->file, m->line, file, line);
	if (zmalloclogging > 1) zdump(m);
      }
      gotit++;
    }
  } /* for.. */

  if (!gotit) {
    fprintf(zfd,"zfree: ERR: attempt to free unallocated memory 0x%p at %s:%d.\n",
		(void *)what, file, line);
  }
  if (gotit > 1) {
    /* this shouldn't happen, eh? */
    fprintf(zfd,"zfree: ERR: Multiply-allocd memory 0x%p.\n", (void *)what);
  }
}


char *zstrdup(const char *src, char *file, int line)
{
  char *result;
#ifndef NO_MEMORY_STRDUP    
  result = (char*)zmalloc(strlen(src) + 1, file, line);
  if (!result)
    return NULL;
  strcpy(result, src);
  return result;
#else
  result = (char*)malloc(strlen(src) + 1);
  if (!result)
    return NULL;
  strcpy(result, src);  /* strcpy ok, size checked above */
  return result;
#endif
}


void zmalloc_check()
{
  meminfo *m, *next_m;
  char *admonishemnt;
  int total_leak = 0, num_leaks = 0, i;

  fprintf(zfd, "\n------------ Checking leaks ------------\n\n");

  for (i = 0; i < NUM_ZBUCKETS; i++) {
    for (m = memlist[i]; m; m = next_m) {
      next_m = m->next;
      if (m->addr != 0 && m->frees <= 0) {
        fprintf(zfd,"zmalloc: UNfreed memory 0x%p %d bytes mallocd at %s:%d\n",
		(void *)m->addr, m->size, m->file, m->line);
        if (zmalloclogging > 1) zdump(m);

        /* check padding on un-freed memory too: */
        pad_check(m);

        total_leak += m->size;
        num_leaks++;
      }
#ifndef NO_MEMORY_PADDING
      if (m->addr) free(m->addr - sizeof(beginPad));
#else
      if (m->addr) free(m->addr);
#endif
      if (m->file) free(m->file);
      free(m);
    }
  }

  if (total_leak) {
    if (total_leak > 10000)
      admonishemnt = "you must work for Microsoft.";
    else if (total_leak > 5000)
      admonishemnt = "you should be ashamed!";
    else if (total_leak > 2000)
      admonishemnt = "you call yourself a programmer?";
    else if (total_leak > 1000)
      admonishemnt = "the X consortium has a job for you...";
    else
      admonishemnt = "close, but not there yet.";
    fprintf(zfd,"zmalloc: %d leaks totalling %d bytes... %s\n",
		num_leaks, total_leak, admonishemnt);
  }
  else {
    fprintf(zfd,"zmalloc: Congratulations: leak-free code!\n");
  }

  if (zfd) {
    fflush(zfd);
    fclose(zfd);
  }
}


void pad_check(meminfo *m)
{
#ifndef NO_MEMORY_PADDING
  if (memcmp(m->addr - sizeof(beginPad), beginPad, sizeof(beginPad)) != 0) {
    fprintf(zfd,"pad_check: ERR: beginPad was modified! (mallocd@ %s:%d)\n", m->file, m->line);
    if (zmalloclogging > 1) zdump(m);
  }
  if (memcmp(m->addr + m->size, endPad, sizeof(endPad)) != 0) {
    fprintf(zfd,"pad_check: ERR: endPad was modified! (mallocd@ %s:%d)\n", m->file, m->line);
    if (zmalloclogging > 1) zdump(m);
  }
#endif
}


#ifdef ZTEST
#undef ZMALLOC_H

#include "zmalloc.h"

int main()
{
  unsigned char * tmp;

  zmalloc_init();

/* You should see no error here. */
  tmp = (unsigned char*)malloc(200);
  free(tmp);

/* Multiple frees test */
  tmp = (unsigned char*)malloc(200);
  strcpy(tmp, "This should show up in the dump but truncated to MAX_ZDUMP_SIZE chars");
  free(tmp);
  free(tmp);

/* Free unallocated mem test */
  tmp += 4;
  free(tmp);

/* Unfreed mem test... You should see "UNfreed mem at line 370" (at end) because of this */
  tmp = (unsigned char*)malloc(200);
  strcpy(tmp, "This is unfreed memory!");

/* Buffer overrun test... You should see an ERR:endPad here */
  tmp = (unsigned char*)malloc(200);
  tmp[202] = 0xbb;
  free(tmp);

/* Buffer underrun test... You should see an ERR:beginPad here */
  tmp = (unsigned char*)malloc(200);
  tmp[-3] = 0x0f;
  free(tmp);

/* Free NULL pointer test... */
  tmp = NULL;
  free(tmp);

  printf("Test completed. See zmalloc.log for the messages.\n");

  zmalloc_check();
  exit(0);
}
#endif /* ZTEST */
