#include <stdio.h>
#include <stdlib.h>
#include "buffer.h"
#include "lhash.h"
#include "cryptlib.h"

static int mh_mode=CRYPTO_MEM_CHECK_OFF;

static LHASH *mh=NULL;

typedef struct mem_st
	{
	char *addr;
	int num;
	char *file;
	int line;
	} MEM;

int CRYPTO_mem_ctrl(mode)
int mode;
	{
	int ret=mh_mode;

	CRYPTO_w_lock(CRYPTO_LOCK_MALLOC);
	switch (mode)
		{
	case CRYPTO_MEM_CHECK_ON:
		mh_mode|=CRYPTO_MEM_CHECK_ON;
		break;
	case CRYPTO_MEM_CHECK_OFF:
		mh_mode&= ~CRYPTO_MEM_CHECK_ON;
		break;
	default:
		break;
		}
	CRYPTO_w_unlock(CRYPTO_LOCK_MALLOC);
	return(ret);
	}

static int mem_cmp(a,b)
MEM *a,*b;
	{
	return(a->addr - b->addr);
	}

static unsigned long mem_hash(a)
MEM *a;
	{
	unsigned long ret;

	ret=(unsigned long)a->addr;

	ret=ret*17851+(ret>>14)*7+(ret>>4)*251;
	return(ret);
	}

char *CRYPTO_malloc(num,file,line)
int num;
char *file;
int line;
	{
	char *ret;
	MEM *m;

	if ((ret=malloc(num)) == NULL)
		return(NULL);

	if (mh_mode & CRYPTO_MEM_CHECK_ON)
		{
		if ((m=(MEM *)malloc(sizeof(MEM))) == NULL)
			{
			free(ret);
			return(NULL);
			}
		CRYPTO_w_lock(CRYPTO_LOCK_MALLOC);
		if (mh == NULL)
			{
			if ((mh=lh_new(mem_hash,mem_cmp)) == NULL)
				{
				free(ret);
				free(m);
				return(NULL);
				}
			}

		m->addr=ret;
		m->file=file;
		m->line=line;
		m->num=num;
		if (lh_insert(mh,(char *)m) != NULL)
			{
			free(m);
			free(ret);
			abort();
			ret=NULL;
			}
		CRYPTO_w_unlock(CRYPTO_LOCK_MALLOC);
		}
	return(ret);
	}

void CRYPTO_free(addr)
char *addr;
	{
	MEM m,*mp;

	if (mh_mode & CRYPTO_MEM_CHECK_ON)
		{
		CRYPTO_w_lock(CRYPTO_LOCK_MALLOC);
		m.addr=addr;
		mp=(MEM *)lh_delete(mh,(char *)&m);
		if (mp != NULL)
			free(mp);
		CRYPTO_w_unlock(CRYPTO_LOCK_MALLOC);
		}
	free(addr);
	}

char *CRYPTO_realloc(addr,num,file,line)
char *addr;
int num;
char *file;
int line;
	{
	char *ret;
	MEM m,*mp;

	ret=realloc(addr,num);
	if (ret == addr) return(ret);

	if (mh_mode & CRYPTO_MEM_CHECK_ON)
		{
		if (ret == NULL) return(NULL);
		m.addr=addr;
		CRYPTO_w_lock(CRYPTO_LOCK_MALLOC);
		mp=(MEM *)lh_delete(mh,(char *)&m);
		if (mp != NULL)
			{
			mp->addr=ret;
			lh_insert(mh,(char *)mp);

			if (mp != NULL)
				mp->addr=ret;
			else
				abort();
			}
		CRYPTO_w_unlock(CRYPTO_LOCK_MALLOC);
		}
	return(ret);
	}

typedef struct mem_leak_st
	{
	BIO *bio;
	int chunks;
	long bytes;
	} MEM_LEAK;

static void print_leak(m,l)
MEM *m;
MEM_LEAK *l;
	{
	char buf[128];

	sprintf(buf,"file=%s, line=%d, number=%d, address=%08lX\n",
		m->file,m->line,m->num,(long)m->addr);
	BIO_puts(l->bio,buf);
	l->chunks++;
	l->bytes+=m->num;
	}

void CRYPTO_mem_leaks(b)
BIO *b;
	{
	MEM_LEAK ml;
	char buf[80];

	if (mh == NULL) return;
	ml.bio=b;
	ml.bytes=0;
	ml.chunks=0;
	CRYPTO_w_lock(CRYPTO_LOCK_MALLOC);
	lh_doall_arg(mh,(void (*)())print_leak,(char *)&ml);
	CRYPTO_w_unlock(CRYPTO_LOCK_MALLOC);
	if (ml.chunks != 0)
		{
		sprintf(buf,"%ld bytes leaked in %d chunks\n",
			ml.bytes,ml.chunks);
		BIO_puts(b,buf);
		}
	/*
	lh_stats_bio(mh,b);
        lh_node_stats_bio(mh,b);
        lh_node_usage_stats_bio(mh,b);
	*/
	}

#ifndef WIN16
void CRYPTO_mem_leaks_fp(fp)
FILE *fp;
	{
	BIO *b;

	if (mh == NULL) return;
	if ((b=BIO_new(BIO_s_file())) == NULL)
		return;
	BIO_set_fp(b,fp,BIO_NOCLOSE);
	CRYPTO_mem_leaks(b);
	BIO_free(b);
	}
#endif

