/* $RCSfile: mem.c,v $
 * 
 * $Author: halfdan $
 * $Date: 1999/03/18 17:23:20 $
 * $Revision: 1.2 $
 * 
 * $Log: mem.c,v $
 * Revision 1.2  1999/03/18  17:23:20  halfdan
 * Changed all types to internally defined types.
 * Added xmem_strdup and related macros.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include "mem.h"

#ifndef XMEM
#define XMEM
#endif
#ifndef XMEM_GUARD_NO_WORDS
#define XMEM_GUARD_NO_WORDS			(8)
#endif
#ifndef XMEM_GUARD_CHECK_EACH
#define XMEM_GUARD_CHECK_EACH		(1)
#endif
#ifndef XMEM_GUARD_WORD
#define XMEM_GUARD_WORD				(0xDEADBEEF)
#endif

#define NOHASH		(256)
#define HASH(x)		((((unsigned int)x) >> 8) & (NOHASH - 1))
#define ALIGN(x)	(((x) + 3) & ~3)

typedef struct _minfo {
	char	*file;
	int		line;
	void	*addr;
	int		size;
	struct _minfo *next;
	struct _minfo *prev;
} MInfo;

#ifdef XMEM_GUARD_BYTES
static size_t	guardsz = (XMEM_GUARD_NO_WORDS * sizeof(xmem_uint32_t));
static int		calls = 0;
#endif

static int		inited = 0;
static MInfo	*mn[NOHASH];
static int		noBlocks;

#ifdef XMEM_THREAD_SAFE

// XXX: There is no provision for destroying the mutexes (muteces?)

#if defined(unix)

#include <pthread.h>
static pthread_mutex_t	xmem_mutex;
#define xmem_lock_init() pthread_mutex_init(&xmem_mutex, NULL)
#define xmem_lock_free() pthread_mutex_destroy(&xmem_mutex)
#define xmem_lock_area() pthread_mutex_lock(&xmem_mutex);
#define xmem_unlock_area() pthread_mutex_unlock(&xmem_mutex);

#endif
#if defined(WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
static CRITICAL_SECTION		xmem_cs;

#define xmem_lock_init()		InitializeCriticalSection(&xmem_cs)
#define xmem_lock_free()		DeleteCriticalSection(&xmem_cs)
#define xmem_lock_area(void)	EnterCriticalSection(&xmem_cs)
#define xmem_unlock_area(void)	LeaveCriticalSection(&xmem_cs)

#endif

#else
#define xmem_lock_init()
#define xmem_lock_free()
#define xmem_lock_area()
#define xmem_unlock_area()
#endif


static char *my_strdup(const char *str)
{
	char	*ptr;

#ifdef USE_MR_ALLOC
	ptr = mi_mem_allocate(strlen(str) + 1);
#else
	ptr = malloc(strlen(str) + 1);
#endif
	return strcpy(ptr, str);
}

static void xmem_addLink(const char *file, int line, void *p, int size)
{
	MInfo	*mp, *mi;
	
	// Add p to the list
	
#ifdef USE_MR_ALLOC
	mp = mi_mem_allocate(sizeof(MInfo));
#else
	mp = malloc(sizeof(MInfo));
#endif
	
	mi = mn[HASH(p)];
	
	mp->file = my_strdup(file);
	mp->line = line;
	mp->addr = p;
	mp->size = size;
	mp->next = mi;
	mp->prev = NULL;

	if(mi)
		mi->prev = mp;

	mn[HASH(mp->addr)] = mp;
	
	noBlocks++;
}

static void xmem_removeLink(MInfo *mp)
{
#ifdef USE_MR_ALLOC
	mi_mem_release(mp->file);
#else
	free(mp->file);
#endif
	
	if(mp->prev)
		mp->prev->next = mp->next;
	else
		mn[HASH(mp->addr)] = mp->next;
		
	if(mp->next)
		mp->next->prev = mp->prev;
	
	free(mp);

	noBlocks--;
}

static MInfo *xmem_findLink(const void *ptr)
{
	MInfo	*mp = mn[HASH(ptr)];
	
	if(mp == NULL)
		return NULL;
	
	for(; mp; mp = mp->next) {
		if(mp->addr == ptr)
			return mp;
	}
	return NULL;
}

#ifdef XMEM_GUARD_BYTES
static void *xmem_set_guards(xmem_uint8_t *p, size_t size)
{
	xmem_uint32_t	*pre, *aft;
	int				i;
	
	size = ALIGN(size);
	
	pre = (xmem_uint32_t *)p;
	aft = (xmem_uint32_t *)(p + guardsz + size);
	for(i = 0; i < XMEM_GUARD_NO_WORDS; i++) {
		pre[i] = XMEM_GUARD_WORD;
		aft[i] = XMEM_GUARD_WORD;
	}
	return p + guardsz;
}

static int xmem_check_guards(xmem_uint8_t *p, size_t size)
{
	xmem_uint32_t	*pre, *aft;
	int				i, res = 0;
	
	p -= guardsz;
	
	size = ALIGN(size);
	
	pre = (xmem_uint32_t *)p;
	aft = (xmem_uint32_t *)(p + guardsz + size);
	
	for(i = 0; i < XMEM_GUARD_NO_WORDS; i++) {
		if(pre[i] != XMEM_GUARD_WORD)
			res |= 1;
		if(aft[i] != XMEM_GUARD_WORD)
			res |= 2;
	}
	return res;
}
#endif


static void xmem_init(void)
{
	int		i;
	
	if(inited)
		return;
	
	xmem_lock_init();
	
	for(i = 0; i < NOHASH; i++)
		mn[i] = NULL;
	
	noBlocks = 0;
	inited = 1;
}

void xmem_check(void)
{
#ifdef XMEM_GUARD_BYTES

	int		i;
	
//	fprintf(stderr, "XMEM: Checking integrity\n");
	
	if(noBlocks == 0) {
		fprintf(stderr, "XMEM: No blocks to check\n");
		return;
	}
	
	for(i = 0; i < NOHASH; i++) {
		MInfo	*mp = mn[i];
		
		if(mp == NULL)
			continue;
			
		for(; mp; mp = mp->next ) {
			int		res;
			
			res = xmem_check_guards(mp->addr, mp->size);
			
			if(res) {
				fprintf(stderr, "  Guard blocks (%s%s%s) are compromised!\n", 
					(res & 1)?("before"):(""), 
					((res & 3) == 3)?(" and "):(""), 
					(res & 2)?("after"):(""));
				fprintf(stderr, "    (%d bytes [at 0x%p] malloc'ed in %s at line %d)\n", mp->size, mp->addr, mp->file, mp->line);
			}			
		}
	}
	
	
#endif	
}

#ifdef XMEM_GUARD_BYTES
static void xmem_icheck(void)
{
	if(calls++ % XMEM_GUARD_CHECK_EACH == 0)
		xmem_check();
}
#endif

void xmem_dump(void)
{
	int		i;
	
	if(noBlocks == 0) {
		fprintf(stderr, "XMEM: Congratulations!! No blocks left!!\n");
		return;
	}
	
	fprintf(stderr, "XMEM: Available blocks (%d, %d):\n", NOHASH, noBlocks);
	
	for(i = 0; i < NOHASH; i++) {
		MInfo	*mp = mn[i];
		
		if(mp == NULL)
			continue;
			
		for(; mp; mp = mp->next ) {
			fprintf(stderr, "  %d bytes [at 0x%p] malloc'ed in %s at line %d\n", mp->size, mp->addr, mp->file, mp->line);
		}
	}
}


void xmem_free(const char *file, const int line, void * const p, int verbose)
{
	MInfo	*mp;
	int		size;
	
	// Null pointer does nothing
	if(p == NULL)
		return;
	
#ifdef XMEM_GUARD_BYTES
	xmem_icheck();
#endif

	mp = xmem_findLink(p);
	if(mp == NULL) {
		fprintf(stderr, "XMEM: Trying to free unknown block in %s at line %d\n", file, line);
		exit(0);
	}

#ifndef XMEM_VERBOSE	
	if(verbose)
#endif
		size = mp->size;
	
	xmem_removeLink(mp);
	
#ifdef XMEM_GUARD_BYTES
  #ifdef USE_MR_ALLOC
	mi_mem_release((xmem_uint8_t *)p - guardsz);
  #else
	free((xmem_uint8_t *)p - guardsz);
  #endif
#else
 #ifdef USE_MR_ALLOC
	mi_mem_release(p);
  #else
	free(p);
  #endif
#endif
	
#ifndef XMEM_VERBOSE
	if(verbose)
#endif
		fprintf(stderr, "XMEM: Free'd %d bytes (%s at %d) [0x%08x]\n", size, file, line, p);
}


void *xmem_alloc(const char *file, const int line, const int size, int verbose)
{
	void	*p;
	
	if(size == 0)
		return NULL;
	
#ifdef XMEM_GUARD_BYTES
	xmem_icheck();
  #ifdef USE_MR_ALLOC
	p = mi_mem_allocate(ALIGN(size) + 2 * guardsz);
  #else
	p = calloc(1, ALIGN(size) + 2 * guardsz);
  #endif
	p = xmem_set_guards(p, size);
#else
  #ifdef USE_MR_ALLOC
	p = mi_mem_allocate(size);
  #else
	p = calloc(1, size);
  #endif
#endif

	xmem_addLink(file, line, p, size);
		
#ifndef XMEM_VERBOSE
	if(verbose)
#endif
		fprintf(stderr, "XMEM: Malloc'd %d bytes (%s at %d) [0x%08x]\n", size, file, line, p);

	return p;
}

void *xmem_realloc(const char *file, const int line, void * const p, const int size, int verbose)
{
	MInfo	*mp;
	void	*np;
	
	if(size == 0) {
		xmem_free(file, line, p, verbose);
		return NULL;
	}

	if(p == NULL)
		return xmem_alloc(file, line, size, verbose);
	
#ifdef XMEM_GUARD_BYTES
	xmem_icheck();
#endif

	// Check if the block exists
	
	if(mn[HASH(p)] == NULL) {
		xmem_dump();
		fprintf(stderr, "XMEM: Trying to realloc %d bytes with no existing malloc'ed blocks (%s at line %d) [0x%08x]\n", size, file, line, p);
		exit(0);
	}
	
	mp = xmem_findLink(p);
	
	if(mp == NULL) {
		xmem_dump();
		fprintf(stderr, "XMEM: Trying to realloc unknown block in %s at line %d\n", file, line);		
		exit(0);
	}
	
	if(strcmp(mp->file, file) != 0)
		fprintf(stderr, "XMEM: Trying to realloc memory alloced in a different file (%s at line %d) in %s at line %d\n", mp->file, mp->line, file, line);
	
#ifdef XMEM_GUARD_BYTES
  #ifdef USE_MR_ALLOC
	np = mi_mem_reallocate((xmem_uint8_t *)p - guardsz, ALIGN(size) + 2 * guardsz);
  #else
	np = realloc((xmem_uint8_t *)p - guardsz, ALIGN(size) + 2 * guardsz);
  #endif
	np = xmem_set_guards(np, size);
#else
  #ifdef USE_MR_ALLOC
	np = mi_mem_reallocate(p, size);
  #else
	np = realloc(p, size);
  #endif
#endif
	
	xmem_removeLink(mp);
	xmem_addLink(file, line, np, size);
	
#ifndef XMEM_VERBOSE
	if(verbose)
#endif
		fprintf(stderr, "XMEM: Realloc'd %d bytes (%s at %d) [0x%08x -> 0x%08x]\n", size, file, line, p, np);

	return np;
}

char *xmem_strdup(const char *file, const int line, char *str)
{
	char	*nstr;
	
	nstr = xmem_alloc(file, line, strlen(str) + 1, 0);
	memcpy(nstr, str, strlen(str) + 1);
	
	return nstr;
}

// =============================================================
// = PUBLIC FUNCTIONS =========================================
// ===========================================================

// XXX: These are essentially wrapper functions so we can block
//      simultaneous access from other threads to XMEM's data
//		structures.

// XMEM_alloc
// ----------
// Allocates a block of memory, 'size' bytes long.
//
// => file		The name of the source file where the allocation took place
//    line		The line in the source file where the allocation took place
//    size		Number of bytes requested. If this is 0, a NULL pointer is returned.
//    verbose	Set to non-zero if a diagnostic output is required.
// <= void *	A pointer to the newly allocated block (or NULL if size == 0)

void *
XMEM_alloc(const char *file, const int line, const int size, int verbose)
{
	void	*ptr;
	
	xmem_init();
	
	xmem_lock_area();
	ptr = xmem_alloc(file, line, size, verbose);
	xmem_unlock_area();
	
	return ptr;
}

// XMEM_realloc
// ------------
// Re-sizes a previously allocated block of memory to 'size' bytes.
// There are a few exceptions, though:
// If 'p' is NULL, it behaves in the same fashion as XMEM_alloc(...,size,...)
// If 'size' is zero, it behaves in the same fashion as XMEM_free(...,p,...)
// If both are zero, a NULL pointer is returned.
//
// => file		The name of the source file where the re-allocation took place
//    line		The line in the source file where the re-allocation took place
//    p			A pointer to a previously allocated block.
//    size		Number of bytes requested. If this is 0, a NULL pointer is returned.
//    verbose	Set to non-zero if a diagnostic output is required.
// <= void *	A pointer to the newly re-allocated block (or NULL if size == 0)
//              This pointer is *not* guaranteed to be the same as 'p'.

void *
XMEM_realloc(const char *file, const int line, void * const p, const int size, int verbose)
{
	void	*ptr;
	
	xmem_init();
	
	xmem_lock_area();
	ptr = xmem_realloc(file, line, p, size, verbose);
	xmem_unlock_area();
	
	// If all the blocks have been freed, release the mutex
	if(noBlocks == 0) {
		xmem_lock_free();
		inited = 0;
	}

	return ptr;
}

// XMEM_free
// ---------
// Frees a memory block pointed to by 'p'. If 'p' is invalid, a 
// diagnostic message will be output and the program terminated.
// => file		The name of the source file where the re-allocation took place
//    line		The line in the source file where the re-allocation took place
//    p			A pointer to a previously allocated block.

void
XMEM_free(const char *file, const int line, void * const p, int verbose)
{
	xmem_init();
	
	xmem_lock_area();
	xmem_free(file, line, p, verbose);
	xmem_unlock_area();
	
	// If all the blocks have been freed, release the mutex
	if(noBlocks == 0) {
		xmem_lock_free();
		inited = 0;
	}
}

// XMEM_strdup
// -----------
// Allocates memory for a copy of the string given (along with the terminating
// null character) and copies the string to the new location.
// => file		The name of the source file where the re-allocation took place
//    line		The line in the source file where the re-allocation took place
//    str		A pointer to a string
// <= char *	A pointer to a block of memory holding a new copy of
//				the string given.

char *
XMEM_strdup(const char *file, const int line, char *str)
{
	char	*ptr;
	
	xmem_init();
	
	xmem_lock_area();
	ptr = xmem_strdup(file, line, str);
	xmem_unlock_area();
	
	return ptr;
}

// XMEM_dump
// ---------
// Writes out a list of memory blocks currently allocated and also where
// they were allocated.

void
XMEM_dump(void)
{
	xmem_init();
	xmem_lock_area();
	xmem_dump();
	xmem_unlock_area();
}

// XMEM_check
// ----------
// Goes through each allocated block and checks if the guard blocks
// have been compromised. This indicates buffer overruns which can
// often be difficult to track down otherwise.
// Prints out a diagnostic message for each block that's been
// compromised.

void
XMEM_check(void)
{
	xmem_init();
	xmem_lock_area();
	xmem_check();
	xmem_unlock_area();
}
