/* Memory management routines.
 * Leak checking based on code by Kelmar (2001/1/13)
 *
 * IRC Services is copyright (c) 1996-2009 Andrew Church.
 *     E-mail: <achurch@achurch.org>
 * Parts written by Andrew Kempe and others.
 * This program is free but copyrighted software; see the file COPYING for
 * details.
 */

#define NO_MEMREDEF
#include "services.h"

/*************************************************************************/
/*************************************************************************/

/* smalloc, scalloc, srealloc, sstrdup:
 *	Versions of the memory allocation functions which will cause the
 *	program to terminate with an "Out of memory" error if the memory
 *	cannot be allocated.  (Hence, the return value from these functions
 *	is never NULL, except for smalloc()/scalloc() called with a size of
 *      zero.)
 */

#ifdef MEMCHECKS
# define FILELINE , char *file, int line
# define xmalloc(a)    MCmalloc(a,file,line)
# define xsmalloc(a)   smalloc(a,file,line)
# define xcalloc(a,b)  MCcalloc(a,b,file,line)
# define xrealloc(a,b) MCrealloc(a,b,file,line)
# define xfree(a)      MCfree(a,file,line)
#else
# define FILELINE /*nothing*/
# define xmalloc(a)    malloc(a)
# define xsmalloc(a)   smalloc(a)
# define xcalloc(a,b)  calloc(a,b)
# define xrealloc(a,b) realloc(a,b)
# define xfree(a)      free(a)
#endif

/*************************************************************************/

void *smalloc(long size FILELINE)
{
    void *buf;

    if (size == 0)
	return NULL;
    buf = xmalloc(size);
    if (buf == NULL)
	raise(SIGUSR1);
    return buf;
}

/*************************************************************************/

void *scalloc(long elsize, long els FILELINE)
{
    void *buf;

    if (elsize == 0 || els == 0)
	return NULL;
    buf = xcalloc(elsize, els);
    if (buf == NULL)
	raise(SIGUSR1);
    return buf;
}

/*************************************************************************/

void *srealloc(void *oldptr, long newsize FILELINE)
{
    void *buf;

    if (newsize == 0) {
	xfree(oldptr);
	return NULL;
    }
    buf = xrealloc(oldptr, newsize);
    if (buf == NULL)
	raise(SIGUSR1);
    return buf;
}

/*************************************************************************/

char *sstrdup(const char *s FILELINE)
{
    char *t = xsmalloc(strlen(s) + 1);
    strcpy(t, s);  /* safe, obviously */
    return t;
}

/*************************************************************************/
/************ Everything from here down is MEMCHECKS-related. ************/
/*************************************************************************/

#ifdef MEMCHECKS

/*************************************************************************/

static long allocated = 0;	/* Amount of memory currently allocated */
static long runtime = 0;	/* `allocated' value at init_memory() time */

#ifdef SHOWALLOCS
int showallocs = 1;		/* Actually log allocations? */
#endif

typedef struct _smemblock {
    long size;			/* Size of this block */
    int32 sig;			/* Signature word: 0x5AFEC0DE */
    void *data;			/* Start of the user's data */
} MemBlock;
# define SIGNATURE	0x5AFEC0DE
# define FREE_SIGNATURE	0xDEADBEEF	/* Used for freed memory */

/*************************************************************************/
/*************************************************************************/

/* Leak-checking initialization and exit code. */

static void show_leaks(void)
{
    if (runtime >= 0 && (allocated - runtime) > 0) {
	log("SAFEMEM: There were %ld bytes leaked on exit!",
	    (allocated - runtime));
    } else {
	log("SAFEMEM: No memory leaks detected.");
    }
}

void init_memory(void)
{
    runtime = allocated;
    log("init_memory(): runtime = %ld", runtime);
    atexit(show_leaks);
}

/* Used to avoid memory-leak message from the parent process */
void uninit_memory(void)
{
    runtime = -1;
}

/*************************************************************************/

/* Substitutes for malloc() and friends.  memory.h redefines malloc(), etc.
 * to these functions if MEMCHECKS is defined. */

/*************************************************************************/

void *MCmalloc(long size, const char *file, int line)
{
    MemBlock *mb;

    if (size == 0)
	return NULL;
    mb = malloc(size + sizeof(MemBlock));
    if (mb) {
	mb->size = size;
	mb->sig = SIGNATURE;
	mb->data = (void *)((char *)(mb) + sizeof(MemBlock));
	allocated += size;
# ifdef SHOWALLOCS
	if (showallocs) {
	    log("smalloc(): Allocated %ld bytes at %p (%s:%d)",
		size, mb->data, file, line);
	}
# endif
	return mb->data;
    } else {
# ifdef SHOWALLOCS
	if (showallocs) {
	    log("mcalloc(): Unable to allocate %ld bytes (%s:%d)",
		size, file, line);
	}
# endif
	return NULL;
    }
}

/*************************************************************************/

void *MCcalloc(long elsize, long els, const char *file, int line)
{
    MemBlock *mb;

    if (elsize == 0 || els == 0)
	return NULL;
    mb = malloc(elsize * els + sizeof(MemBlock));
    if (mb) {
	mb->size = elsize * els;
	mb->sig = SIGNATURE;
	mb->data = (void *)((char *)(mb) + sizeof(MemBlock));
	memset(mb->data, 0, elsize * els);
	allocated += mb->size;
# ifdef SHOWALLOCS
	if (showallocs) {
	    log("scalloc(): Allocated %ld bytes at %p (%s:%d)",
		els*elsize, mb->data, file, line);
	}
# endif
	return mb->data;
    } else {
# ifdef SHOWALLOCS
	if (showallocs) {
	    log("scalloc(): Unable to allocate %ld bytes (%s:%d)",
		els*elsize, file, line);
	}
# endif
	return NULL;
    }
}

/*************************************************************************/

void *MCrealloc(void *oldptr, long newsize, const char *file, int line)
{
    MemBlock *newb, *oldb;
    long oldsize;
# ifdef SHOWALLOCS
    void *olddata;
# endif

    if (newsize == 0) {
	MCfree(oldptr, file, line);
	return NULL;
    }
    if (oldptr == NULL)
	return MCmalloc(newsize, file, line);
    oldb = (MemBlock *)((char *)(oldptr) - sizeof(MemBlock));
    if (oldb->sig != SIGNATURE) {
	fatal("Attempt to realloc() an invalid pointer (%p) (%s:%d)",
	      oldptr, file, line);
    }
# ifdef SHOWALLOCS
    olddata = oldb->data;
# endif
    oldsize = oldb->size;
    newb = realloc(oldb, newsize + sizeof(MemBlock));
    if (newb) {
	newb->size = newsize;
	newb->sig = SIGNATURE;
	newb->data = (void *)((char *)(newb) + sizeof(MemBlock));
	/* Adjust our tracker acordingly */
	allocated += (newsize - oldsize);
# ifdef SHOWALLOCS
	if (showallocs) {
	    log("srealloc(): Adjusted %ld bytes (%p) to %ld bytes (%p)"
		" (%s:%d)", oldsize, olddata, newsize, newb->data, file, line);
	}
# endif
	return newb->data;
    } else {
# ifdef SHOWALLOCS
	if (showallocs) {
	    log("srealloc(): Unable to adjust %ld bytes (%p) to %ld bytes"
		" (%s:%d)", oldsize, olddata, newsize, file, line);
	}
# endif
	return NULL;
    }
}

/*************************************************************************/

void MCfree(void *ptr, const char *file, int line)
{
    MemBlock *mb;

    if (ptr == NULL)
	return;
    mb = (MemBlock *)((char *)(ptr) - sizeof(MemBlock));
    if (mb->sig != SIGNATURE) {
	fatal("Attempt to free() an invalid pointer (%p) (%s:%d)",
	      ptr, file, line);
    }
    allocated -= mb->size;
# ifdef SHOWALLOCS
    if (showallocs) {
	log("sfree(): Released %ld bytes at %p (%s:%d)",
	    mb->size, mb->data, file, line);
    }
# endif
    mb->sig = FREE_SIGNATURE;
    free(mb);
}

/*************************************************************************/

char *MCstrdup(const char *s, const char *file, int line)
{
    char *t = MCmalloc(strlen(s) + 1, file, line);
    strcpy(t, s);  /* safe, obviously */
    return t;
}

/*************************************************************************/

#endif  /* MEMCHECKS */
