/******************************************************************************
* All mallocs from irit modules should be piped through this allocator.       *
*									      *
*					 Written by Gershon Elber, April 1993 *
******************************************************************************/

#include <stdio.h>
#include <string.h>
#ifdef sgi
#include <malloc.h>
#endif /* sgi */
#include "irit_sm.h"
#include "imalloc.h"

#define OVERWRITE_STAMP_START 1234567890L    /* A long */
#define OVERWRITE_STAMP_END   0xbd	     /* A byte */
#define OVERWRITE_STAMP_FREED -50964L        /* A long */
#define FREE_TABLE_SIZE 100000

#if defined(AMIGA) && defined(__SASC)
static VoidPtr __far *AllocPtrTable = NULL;
#else
static VoidPtr *AllocPtrTable = NULL;
#endif
static long
    IritDebugSearchPtr = 0;
static int
    AllocNumPtrs = 0,
    IritDebugMalloc = FALSE,
    IritDebugMallocInit = FALSE;

static void AllocError(char *Msg, VoidPtr *p);

/*****************************************************************************
* DESCRIPTION:                                                               *
* Routine to print an allocation error to stderr happend when pointer p      *
* was involved.                         				     *
*                                                                            *
* PARAMETERS:                                                                *
*   Msg:         A description of the error.                                 *
*   p:           Pointer that is involved in the error.                      *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void AllocError(char *Msg, VoidPtr *p)
{
    char s[128];

    sprintf(s, "%s, Ptr = 0x%lx", Msg, (unsigned long) p);

    fprintf(stderr, "Memory allocation error, %s\n", s);
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Tests for error in dynamically allocated memory, without affecting any     M
* such allocation or allocated blocks.       				     M
*   This routine may be used only if "IRIT_MALLOC" environment variable      M
* is set for debugging purposes and it obviously slows down running time.    M
*   The following tests are being made on every block allocated,and messages M
* are printed to stderr as needed:					     M
* 1. Overwriting beyond the end of the allocated block.			     M
* 2. Overwriting below the beginning of the allocated block.		     M
* 3. Freeing an unallocated pointer.					     M
* 4. Freeing the same pointer twice.					     M
* 5. If "IRIT_MALLOC_PTR" environment variable is set to an address, this    M
*    address is being search for during allocation (IritMalloc) and          M
*    announced when detected.						     M
*                                                                            *
* PARAMETERS:                                                                M
*   PrintAlloc:   If TRUE, prints information all all allocated block, not   M
*                 just block with errors.				     M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IritTestAllDynMemory, allocation                                         M
*****************************************************************************/
void IritTestAllDynMemory(int PrintAlloc)
{
    int i;

    if (AllocPtrTable == NULL) {
	fprintf(stderr, "Allocation table was not initialized (call IritMalloc once)\n");
    }

    for (i = 0; i < AllocNumPtrs; i++) {
	if (AllocPtrTable[i] != NULL) {
	    unsigned long
		size = *((unsigned long *) AllocPtrTable[i]);
	    VoidPtr
		p2 = ((char *) AllocPtrTable[i]) + 8 + size;

	    if (*((long *) ((char *) AllocPtrTable[i] + 4)) !=
						OVERWRITE_STAMP_START)
		AllocError("Overwritten start of dynamically allocated memory",
			   AllocPtrTable[i]);
	    else if (*((unsigned char *) p2) != OVERWRITE_STAMP_END)
		AllocError("Overwritten end of dynamically allocated memory",
			   AllocPtrTable[i]);

	    if (PrintAlloc)
		fprintf(stderr, "Allocated 0x%08lx\n",
			(unsigned long) AllocPtrTable[i]);
	}
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Routine to allocate dynamic memory for all IRIT program/tool/libraries.    M
*    All requests for dynamic memory should invoke this function.	     M
*    If the environment variable "IRIT_MALLOC" is set when an IRIT program   M
* is executed, the consistency of the dynamic memory is tested on every      M
* invokation of this routine. See IritTestAllDynMemory function for more.    M
*                                                                            *
* PARAMETERS:                                                                M
*   size:     Size of block to allocate, in bytes.                           M
*                                                                            *
* RETURN VALUE:                                                              M
*   VoidPtr:  A pointer to the allocated block. A function calling this      M
*             may assume return value wil never be NULL, since no more       M
*             memory cases are trapped locally.				     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IritMalloc, allocation                                                   M
*****************************************************************************/
VoidPtr IritMalloc(unsigned size)
{
    VoidPtr p;
    unsigned int OldSize;

    if (!IritDebugMallocInit) {
        IritDebugMalloc = getenv("IRIT_MALLOC") != NULL;
        IritDebugSearchPtr = getenv("IRIT_MALLOC_PTR") != NULL ?
					atoi(getenv("IRIT_MALLOC_PTR")) : 0;
#       ifdef sgi
	    mallopt(M_MXFAST, 256);
	    mallopt(M_BLKSZ, 65536);
#       endif /* sgi */

	if (IritDebugMalloc)
	    AllocPtrTable = malloc(FREE_TABLE_SIZE * sizeof(VoidPtr));

	IritDebugMallocInit = TRUE;
    }

    if (IritDebugMalloc) {
	IritTestAllDynMemory(0);

	OldSize = size;
	size += 16;
    }

    if ((p = malloc(size)) != NULL) {
	if (IritDebugMalloc) {
	    int i;
	    VoidPtr p2;

	    if (p != NULL && ((long) p) == IritDebugSearchPtr) {
		printf("Pointer 0x%08lx just allocated\n",
		       (unsigned long) p);
	    }

	    /* Save allocated pointer so we can search for it when freed. */
	    for (i = 0; i < AllocNumPtrs; i++) {
	        if (AllocPtrTable[i] == NULL) {
		    AllocPtrTable[i] = p;
		    break;
		}
	    }

	    if (i >= AllocNumPtrs) {
		if (i < FREE_TABLE_SIZE - 1)
		    AllocPtrTable[AllocNumPtrs++] = p;
		else {
		    fprintf(stderr, "Allocation table too small.\n");
		    exit(1);
		}
	    }

	    *((long *) p) = OldSize;
	    *((long *) (((char *) p) + 4)) = OVERWRITE_STAMP_START;
	    p = ((char *) p) + 8;
	    p2 = ((char *) p) + OldSize;
	    *((char *) p2) = (char) OVERWRITE_STAMP_END;

	    *((char *) p) = 0; /* In case the freed stamp is still there. */
	}
	    
	return p;
    }

    IritFatalError("Not Enough dynamic memory");

    return NULL;				    /* Make warnings silent. */
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Routine to free dynamic memory for all IRIT program/tool/libraries.        M
*    All requests to free dynamic memory should invoke this function.        M
*    If the environment variable "IRIT_MALLOC" is set when an IRIT program   M
* is executed, the consistency of the dynamic memory is tested on every      M
* invokation of this routine. See IritTestAllDynMemory function for more.    M
*                                                                            *
* PARAMETERS:                                                                M
*   p:          Pointer to a block that needs to be freed.                   M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IritFree, allocation                                                     M
*****************************************************************************/
void IritFree(VoidPtr p)
{
    if (IritDebugMalloc) {
	int i;

	if (*((long *) p) == OVERWRITE_STAMP_FREED)
	    AllocError("Trying to free a free object again", p);

	if (p == NULL)
	    AllocError("Free a NULL pointer", p);

	IritTestAllDynMemory(0);

	*((long *) p) = OVERWRITE_STAMP_FREED;
	p = ((char *) p) - 8;

	/* Compare the freed pointer with the list of allocated ones. */
	for (i = 0; i < AllocNumPtrs; i++) {
	    if (AllocPtrTable[i] == p) {
		AllocPtrTable[i] = NULL;
		break;
	    }
	}

	if (i >= AllocNumPtrs)
	    AllocError("Free unallocated pointer", p);
    }

#ifdef __DEBUG_TC_MALLOC__
    switch (heapcheck()) {
	case _HEAPCORRUPT:
	    AllocError("Heap is corrupted", p);
	    break;
	case _BADNODE:
	    AllocError("Attempt to free a bogus pointer", p);
	    break;
	case _FREEENTRY:
	    AllocError("Attempt to free an already freed pointer", p);
	    break;
	case _HEAPOK:
	case _HEAPEMPTY:
	case _USEDENTRY:
	    break;
	default:
	    AllocError("Allocation error", p);
	    break;

    }
#endif /* __DEBUG_TC_MALLOC__ */

    free(p);
}
