/*
  C K C M D B . C  --  malloc debugger.
*/

/*
  Author: Howie Kaye, Columbia University Center for Computing Activities.

  Copyright (C) 1985, 1999,
    Trustees of Columbia University in the City of New York.
    All rights reserved.  See the C-Kermit COPYING.TXT file or the
    copyright text in the ckcmai.c module for disclaimer and permissions.
*/
/* Use the real ones in this module! */
#ifdef malloc
#undef malloc
#endif /* malloc */
#ifdef calloc
#undef calloc
#endif /* calloc */
#ifdef realloc
#undef realloc
#endif /* realloc */
#ifdef free
#undef free
#endif /* free */

#include "ckcsym.h"
#include <stdio.h>
#include "ckcdeb.h"

#ifdef COHERENT
_PROTOTYP ( FILE * fdopen, (int, char *) );
#endif /* COHERENT */

/*
  memdebug:
  variable to control memory debugging.
  if memdebug ==  1, then action is always taken.
  if memdebug ==  0, then no action is taken.
  if memdebug == -1, then the user is asked (works well with gdb).
*/
int memdebug = -1;
int disabled = 0;
int inited = 0;
/*
  To use this package, compile your program with:
  -Dmalloc=dmalloc -Dfree=dfree =Dcalloc=dcalloc ... -DMDEBUG
  and then link it with ckcmdb.c.
*/
#ifdef MDEBUG

#ifndef M_SIZE_T
#ifdef NEXT
#define M_SIZE_T size_t
#else
#ifdef SUNOS41
#define M_SIZE_T unsigned
#else
#define M_SIZE_T int
#endif /* SUNOS41 */
#endif /* NEXT */
#endif /* M_SIZE_T */

#ifdef CK_ANSIC
_PROTOTYP( void free, (void *) );
_PROTOTYP( void * malloc, (size_t) );
_PROTOTYP( void * realloc, (void *, size_t) );
#else
_PROTOTYP( VOID free, (char *) );
_PROTOTYP( char * malloc, (M_SIZE_T) );
_PROTOTYP( char * realloc, (char *, M_SIZE_T) );
#endif /* NEXT */

_PROTOTYP( VOID m_insert, (char *) );
_PROTOTYP( int m_delete, (char *) );

_PROTOTYP( char * dmalloc, (int) );
_PROTOTYP( char * dcalloc, (int, int) );
_PROTOTYP( char * drealloc, (char *, int) );

_PROTOTYP( char *set_range_check, (char *, int) );
_PROTOTYP( char *check_range, (char *) );
_PROTOTYP( static char *maybe_check_range, (char *) );

_PROTOTYP( static VOID maybe_quit, (char *) );
_PROTOTYP( static int ask, (char *) );

#ifndef min
#define min(x,y) ((x) < (y) ? (x) : (y))
#endif /* min */
#define RANGE "ABCDEFGHIJKLMNOP"
#define INTSIZE  sizeof(int)
#define LONGSIZE sizeof(long)
#define RSIZE    sizeof(RANGE)
#define RFRONT   min((RSIZE/2),LONGSIZE)
#define RBACK    min((RSIZE-RFRONT),LONGSIZE)

char *
dmalloc(size) int size; {
    char *cp;

    cp = malloc(size + RSIZE + INTSIZE);
    if (cp) {
	cp = set_range_check(cp, size);
	m_insert(cp);
    }
    return(cp);
}

char *
dcalloc(nelem, elsize) int nelem, elsize; {
    char *cp;

    cp = dmalloc(nelem * elsize);
    if (cp)
	memset(cp, 0, nelem * elsize);
    return(cp);
}

char *
drealloc(bp,size) char *bp; int size; {
    char *cp;

    if (bp == NULL) {
	maybe_quit("Freeing NULL pointer");
    } else {
	m_delete(bp);
	cp = check_range(bp);
    }
    cp = realloc(cp, size + RSIZE + INTSIZE);
    if (cp) {
	cp = set_range_check(cp, size);
	m_insert(cp);
    }
    return(cp);
}

VOID
dfree(cp) char *cp; {
    if (cp == NULL)
	maybe_quit("Freeing NULL pointer");
    else {
	switch(m_delete(cp)) {
	case 0:
	    cp = maybe_check_range(cp);
	    break;
	case 1:
	    cp = check_range(cp);
	    break;
	case 2:
	    break;
	}
    }
#ifndef CK_ANSIC
    return(free(cp));
#endif /* CK_ANSIC */
}

char *
set_range_check(cp,size) char *cp; int size; {
    register int i;
    int tmp = size;

    for(i = 0; i < INTSIZE; i++) {	/* set the size in the string */
	cp[i] = tmp & 0xff;
	tmp >>= 8;
    }
    cp += INTSIZE;			/* skip the size */

    for(i = 0; i < RFRONT; i++)		/* set the front of the range check */
	cp[i] = RANGE[i];		/* string */

    cp += RFRONT;			/* skip the front range check */

    for(i = 0; i < RBACK; i++)		/* set the back odf the range check */
	cp[i+size] = RANGE[i+RFRONT];

    return(cp);
}

/*
  Put calls to this routine in your code any place where you want to
  check whether you've copied too many characters into a malloc'd space.
*/
char *
check_range(cp) char *cp; {
    register char *bp = cp - RFRONT - INTSIZE;
    char *xp = bp;
    register int i;
    int size = 0;

    for(i = 0 ; i < INTSIZE; i++) {	/* get the size out of the string */
	size <<= 8;
	size |= bp[INTSIZE-i-1] & 0xff;
    }
    bp += INTSIZE;

    for(i = 0; i < RFRONT; i++)		/* check front range check */
	if (bp[i] != RANGE[i]) {
	    maybe_quit("leftside malloc buffer overrun");
	    break;
	}
    bp += RFRONT;			/* skip front range check */

    for(i = 0; i < RBACK; i++)		/* check back range check */
	if (bp[i+size] != RANGE[i+RFRONT]) {
	    maybe_quit("rightside malloc buffer overrun");
	    break;
	}
    return(xp);
}

static char *
maybe_check_range(cp) char *cp; {
    register char *bp = cp - RFRONT - INTSIZE;
    char *xp = bp;
    register int i;
    int size = 0;

    for(i = 0 ; i < INTSIZE; i++) {	/* get the size out of the string */
	size <<= 8;
	size |= bp[INTSIZE-i-1] & 0xff;
    }
    bp += INTSIZE;

    for(i = 0; i < RFRONT; i++)		/* check front range check */
	if (bp[i] != RANGE[i]) {
	    return(cp);
	}
    bp += RFRONT;			/* skip front range check */

    for(i = 0; i < RBACK; i++)		/* check back range check */
	if (bp[i+size] != RANGE[i+RFRONT]) {
	    fprintf(stderr,"rightside malloc buffer overrun\n");
	    abort();
	    break;
	}
    return(xp);
}

#define BUCKETS 10000
char *m_used[BUCKETS];
char *m_used2[BUCKETS];

VOID
m_insert(cp) register char *cp; {
    register int i;

    if (disabled)
	return;

    for(i = 0; i < BUCKETS; i++)
	if (m_used[i] == 0) {
	    m_used[i] = cp;
	    return;
	}
    disabled ++;
}

static VOID
m_insert2(cp) register char *cp; {
    register int i;

    if (disabled)
	return;
    for(i = 0; i < BUCKETS; i++)
	if (m_used2[i] == 0) {
	    m_used2[i] = cp;
	    return;
	}
    disabled ++;
}

int
m_delete(cp) register char *cp; {
    register int i;

    for(i = 0; i < BUCKETS; i++)
	if (m_used[i] == cp) {
	    m_used[i] = 0;
	    return(1);
	}
    for(i = 0; i < BUCKETS; i++)
	if (m_used2[i] == cp) {
	    m_used2[i] = 0;
	    return(2);
	}
    if (disabled)
	return(0);

    maybe_quit("Freeing unmalloc'ed pointer");
    return(0);
}

VOID
m_init() {
    register int i;

    inited = 1;
    disabled = 0;
#ifdef NEXT
    malloc_debug(2+4+8+16);
#endif /* NEXT */

    for(i = 0; i < BUCKETS; i++)
      m_used[i] = 0;
}

VOID
m_done() {
    register int i,j=0;

    if (disabled)
	return;
    for(i = 0; i < BUCKETS; i++)
	if (m_used[i] != 0) {
	    if (memdebug) {
		if (j == 0)
		    fprintf(stderr,"unfree'ed buffers, indices: ");
		fprintf(stderr,"%d, ", i);
		j++;
	    }
	}
    if (j)
	fprintf(stderr,"\n");
    for(i = 0; i < BUCKETS; i++)
	if (m_used2[i] != 0) {
	    if (memdebug) {
		if (j == 0)
		    fprintf(stderr,"unfree'ed registered buffers, indices: ");
		fprintf(stderr,"%d, ", i);
		j++;
	    }
	}
    if (j)
	fprintf(stderr,"\n");
    if (j)
	maybe_quit("Unfree'ed malloc buffers");
}

VOID
m_checkranges() {
    int i;

    for ( i = 0; i < BUCKETS; i++)
	if (m_used[i])
	    check_range(m_used[i]);
}

static VOID
maybe_quit(str) char *str; {
    debug(F100,"mdebug maybe_quit","",0);
    if (memdebug == 0)
	return;
    fprintf(stderr,"%s\n",str);
    if (memdebug == 1)
	abort();
    if (memdebug == -1)
	if (ask("Quit? "))
	    abort();
}

static int
ask(str) char *str; {
    char buf[100];
    FILE *in;
    int fd;

    fd = dup(fileno(stdin));
    in = fdopen(fd, "r");
    while(1) {
	fprintf(stderr,str);
	fflush(stderr);
	if (fgets(buf, 99, in) == NULL)	/* EOF? */
	    return(0);
	if (buf[0] == 'n' || buf[0] == 'N') {
	    fclose(in);
	    return(0);
	}
	if (buf[0] == 'y' || buf[0] == 'Y') {
	    fclose(in);
	    return(1);
	}
	fprintf(stderr,"please answer y/n.\n");
    }
}
#endif /* MDEBUG */
