/*
 * Stupid malloc debugger. I think I wrote something like
 * this a couple of times already. Where does all the old
 * source code go?
 */

#ifdef MDEBUG

#include <stdlib.h>
#include <string.h>
#include <libisns/util.h>

static void *		isns_malloc_default(size_t, const char *, unsigned int);
static void *		isns_calloc_default(unsigned int, size_t,
				const char *, unsigned int);
static void *		isns_realloc_default(void *, size_t,
				const char *, unsigned int);
static char *		isns_strdup_default(const char *, const char *, unsigned int);
static void		isns_free_default(void *, const char *, unsigned int);

/*
 * These are the function pointers used to redirect malloc and such.
 */
void *			(*isns_malloc_fn)(size_t, const char *, unsigned int) = isns_malloc_default;
void *			(*isns_calloc_fn)(unsigned int, size_t,
				const char *, unsigned int) = isns_calloc_default;
void *			(*isns_realloc_fn)(void *, size_t,
				const char *, unsigned int) = isns_realloc_default;
char *			(*isns_strdup_fn)(const char *, const char *, unsigned int) = isns_strdup_default;
void			(*isns_free_fn)(void *, const char *, unsigned int) = isns_free_default;

#define H_MAGIC		0xfeedbeef
#define T_MAGIC		0xbadf00d
#define CHUNK_OVERHEAD	(sizeof(struct m_header) + sizeof(struct m_trailer))

struct m_header {
	struct isns_list	h_list;
	uint32_t		h_magic;
	size_t			h_size;

	const char *		h_file;
	unsigned int		h_line;
};

struct m_trailer {
	uint32_t		t_magic[8];
	size_t			t_size;
};

static ISNS_LIST_DECLARE(m_list);
static void *			m_low_addr;
static void *			m_high_addr;
static int			m_init = 0;

static void
__isns_check_chunk(const struct m_header *head)
{
	const struct m_trailer *tail;
	int		i;

	if ((void *) head < m_low_addr
	 || (void *) head > m_high_addr) {
		isns_error("%s: m_list corrupted!\n", __FUNCTION__);
		abort();
	}

	if (head->h_magic != H_MAGIC) {
		isns_error("%s: m_list item %p with bad header magic %08x\n",
				__FUNCTION__, head, head->h_magic);
		isns_error("    Allocated from %s:%u\n",
				head->h_file, head->h_line);
		abort();
	}

	tail = ((void *) head) + sizeof(*head) + head->h_size;
	for (i = 0; i < 8; ++i) {
		if (tail->t_magic[i] == T_MAGIC)
			continue;

		isns_error("%s: m_list item %p with bad trailer magic[%d] %08x\n",
				__FUNCTION__, head, i, tail->t_magic[i]);
		isns_error("    Allocated from %s:%u\n",
				head->h_file, head->h_line);
		abort();
	}

	if (tail->t_size != head->h_size) {
		isns_error("%s: m_list item %p size mismatch; head=%u tail=%u\n",
				__FUNCTION__, head,
				head->h_size, tail->t_size);
		isns_error("    Allocated from %s:%u\n",
				head->h_file, head->h_line);
		abort();
	}
}

static void
__isns_verify_all(void)
{
	struct isns_list	*pos, *next;

	isns_list_foreach(&m_list, pos, next) {
		__isns_check_chunk(isns_list_item(struct m_header, h_list, pos));
	}
}

void *
__isns_malloc(size_t size, const char *file, unsigned int line)
{
	struct m_header	*head;
	struct m_trailer *tail;
	size_t		true_size;
	void		*ptr;
	int		i;

	__isns_verify_all();

	true_size = size + sizeof(*head) + sizeof(*tail);
	isns_assert(size < true_size);

	ptr = malloc(true_size);
	if (!ptr)
		return NULL;

	if (!m_low_addr) {
		m_low_addr = m_high_addr = ptr;
	} else if (ptr < m_low_addr) {
		m_low_addr = ptr;
	} else if (ptr > m_high_addr) {
		m_high_addr = ptr;
	}

	head = ptr;
	head->h_magic = H_MAGIC;
	head->h_size = size;
	head->h_file = file;
	head->h_line = line;
	isns_list_append(&m_list, &head->h_list);

	ptr += sizeof(*head);

	tail = ptr + size;
	for (i = 0; i < 8; ++i)
		tail->t_magic[i] = T_MAGIC;
	tail->t_size = size;

	return ptr;
}

void *
__isns_calloc(unsigned int nele, size_t size,
		const char *file, unsigned int line)
{
	void	*ptr;

	ptr = __isns_malloc(nele * size, file, line);
	if (ptr)
		memset(ptr, 0, nele * size);
	return ptr;
}

void *
__isns_realloc(void *old, size_t new_size,
		const char *file, unsigned int line)
{
	struct m_header *old_head = NULL;
	void	*new;

	if (old) {
		old_head = (old - sizeof(struct m_header));
		__isns_check_chunk(old_head);
	}

	new = __isns_malloc(new_size, file, line);
	if (new && old) {
		memcpy(new, old, old_head->h_size);
		isns_free_fn(old, file, line);
	}

	return new;
}


char *
__isns_strdup(const char *s, const char *file, unsigned int line)
{
	size_t	len;
	char	*ptr;

	len = s? strlen(s) : 0;
	ptr = __isns_malloc(len + 1, file, line);
	if (ptr) {
		memcpy(ptr, s, len);
		ptr[len] = '\0';
	}
	return ptr;
}

void
__isns_free(void *ptr,
	    __attribute__((unused))const char *file,
	    __attribute__((unused))unsigned int line)
{
	struct m_header	*head;
	size_t	true_size;

	if (ptr == NULL)
		return;

	head = ptr - sizeof(struct m_header);
	__isns_check_chunk(head);

#if 0
	printf("__isns_free(%u from %s:%u): freed by %s:%u\n",
			head->h_size, head->h_file, head->h_line,
			file, line);
#endif
	true_size = head->h_size + CHUNK_OVERHEAD;
	isns_list_del(&head->h_list);

	memset(head, 0xa5, true_size);
	free(head);

	__isns_verify_all();
}

/*
 * Enable memory debugging
 */
static void
__isns_mdebug_init(void)
{
	const char	*tracefile;

	tracefile = getenv("ISNS_MTRACE");
	if (tracefile)
		isns_error("MTRACE not yet supported\n");

	if (getenv("ISNS_MDEBUG")) {
		isns_malloc_fn = __isns_malloc;
		isns_calloc_fn = __isns_calloc;
		isns_realloc_fn = __isns_realloc;
		isns_strdup_fn = __isns_strdup;
		isns_free_fn = __isns_free;
		isns_notice("Enabled memory debugging\n");
	}

	m_init = 1;
}

static inline void
isns_mdebug_init(void)
{
	if (!m_init)
		__isns_mdebug_init();
}

/*
 * Default implementations of malloc and friends
 */
static void *
isns_malloc_default(size_t size,
		    __attribute__((unused))const char *file,
		    __attribute__((unused))unsigned int line)
{
	isns_mdebug_init();
	return malloc(size);
}

static void *
isns_calloc_default(unsigned int nele, size_t size,
		    __attribute__((unused))const char *file,
		    __attribute__((unused))unsigned int line)
{
	isns_mdebug_init();
	return calloc(nele, size);
}

static void *
isns_realloc_default(void *old, size_t size,
		     __attribute__((unused))const char *file,
		     __attribute__((unused))unsigned int line)
{
	isns_mdebug_init();
	return realloc(old, size);
}

static char *
isns_strdup_default(const char *s,
		    __attribute__((unused))const char *file,
		    __attribute__((unused))unsigned int line)
{
	isns_mdebug_init();
	return strdup(s);
}

static void
isns_free_default(void *ptr,
		  __attribute__((unused))const char *file,
		  __attribute__((unused))unsigned int line)
{
	isns_mdebug_init();
	return free(ptr);
}
#endif
