/*
 * Copyright (C) 2001 Sistina Software (UK) Limited.
 *
 * This file is released under the LGPL.
 */

#include <assert.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>

#include "dbg_malloc.h"
#include "log.h"

struct memblock {
	struct memblock *prev, *next; /* All allocated blocks are linked */
	size_t length;                /* Size of the requested block */
	int id;                       /* Index of the block */
	const char *file;             /* File that allocated */
	int line;                     /* Line that allocated */
	void *magic;                  /* Address of this block */
};

static struct {
	unsigned int blocks, mblocks;
	unsigned int bytes, mbytes;

} _mem_stats = {0, 0, 0, 0};

static struct memblock *_head = 0;
static struct memblock *_tail = 0;

void *malloc_aux(size_t s, const char *file, int line)
{
	struct memblock *nb;
	size_t tsize = s + sizeof(*nb) + sizeof(unsigned long);

	if (!(nb = malloc(tsize))) {
		log_error("couldn't allocate any memory, size = %" PRIuPTR, s);
		return 0;
	}

	/* set up the file and line info */
	nb->file = file;
	nb->line = line;

#ifdef BOUNDS_CHECK
	bounds_check();
#endif

	/* setup fields */
	nb->magic = nb + 1;
	nb->length = s;
	nb->id = ++_mem_stats.blocks;
	nb->next = 0;
	nb->prev = _tail;

	/* link to tail of the list */
	if (!_head)
		_head = _tail = nb;
	else {
		_tail->next = nb;
		_tail = nb;
	}

	/* stomp a pretty pattern across the new memory
	   and fill in the boundary bytes */
	{
		char *ptr = (char *) (nb + 1);
		int i;
		for (i = 0; i < s; i++)
			*ptr++ = i & 0x1 ? (char) 0xba : (char) 0xbe;

		for (i = 0; i < sizeof(unsigned long); i++)
			*ptr++ = (char) nb->id;
	}

	if (_mem_stats.blocks > _mem_stats.mblocks)
		_mem_stats.mblocks = _mem_stats.blocks;

	_mem_stats.bytes += s;
	if (_mem_stats.bytes > _mem_stats.mbytes)
		_mem_stats.mbytes = _mem_stats.bytes;

	return nb + 1;
}

void free_aux(void *p)
{
	char *ptr;
	int i;
	struct memblock *mb = ((struct memblock *) p) - 1;
	if (!p)
		return;

#ifdef BOUNDS_CHECK
	bounds_check();
#endif

	/* sanity check */
	assert(mb->magic == p);

	/* check data at the far boundary */
	ptr = ((char *) mb) + sizeof(struct memblock) + mb->length;
	for (i = 0; i < sizeof(unsigned long); i++)
		if(*ptr++ != (char) mb->id)
			assert(!"Damage at far end of block");

	/* have we freed this before ? */
	assert(mb->id != 0);
	mb->id = 0;

	/* stomp a different pattern across the memory */
	ptr = ((char *) mb) + sizeof(struct memblock);
	for (i = 0; i < mb->length; i++)
		*ptr++ = i & 1 ? (char) 0xde : (char) 0xad;

	/* unlink */
	if (mb->prev)
		mb->prev->next = mb->next;
	else
		_head = mb->next;

	if (mb->next)
		mb->next->prev = mb->prev;
	else
		_tail = mb->prev;

	assert(_mem_stats.blocks);
	_mem_stats.blocks--;
	_mem_stats.bytes -= mb->length;

	/* free the memory */
	free(mb);
}

void *realloc_aux(void *p, unsigned int s, const char *file, int line)
{
	void *r;
	struct memblock *mb = ((struct memblock *) p) - 1;

	r = malloc_aux(s, file, line);

	if (p) {
		memcpy(r, p, mb->length);
		free_aux(p);
	}

	return r;
}

#ifdef DEBUG_MEM
int dump_memory(void)
{
	unsigned long tot = 0;
	struct memblock *mb;
	if (_head)
		log_very_verbose("You have a memory leak:");

	for (mb = _head; mb; mb = mb->next) {
		print_log(_LOG_INFO, mb->file, mb->line, 
			  "block %d at %p, size %" PRIdPTR,
			  mb->id, mb->magic, mb->length);
		tot += mb->length;
	}

	if (_head)
		log_very_verbose("%ld bytes leaked in total", tot);

	return 1;
}

void bounds_check(void)
{
	struct memblock *mb = _head;
	while (mb) {
		int i;
		char *ptr = ((char *) (mb + 1)) + mb->length;
		for (i = 0; i < sizeof(unsigned long); i++)
			if (*ptr++ != (char) mb->id)
				assert(!"Memory smash");

		mb = mb->next;
	}
}
#endif

/*
 * Local variables:
 * c-file-style: "linux"
 * End:
 */
