#include <exec/types.h>
#include <exec/memory.h>
#include <devices/parallel.h>
#include <proto/exec.h>

#include <stdio.h>
#include <stdlib.h>

/*
 * STRUCT:
 *   MemNode
 *
 * DESCRIPTION:
 *     The MemNode structure is placed at the start of each memory block
 *   managed by the safe_ functions.
 *
 * USAGE:
 *     It is completely internal to the safe_ suite of functions, no other
 *   source need know of it's existance.
 */
struct MemNode
{
  struct MemNode *child;             /* Next block in list        */

  char *file;                        /* File that allocated us    */
  ULONG line;                        /* Line we were allocated on */

  ULONG size;                        /* Size of memory block      */
};


/*
 * The head of our internal memory list
 */
static struct MemNode *far head;

/*
 * When gbl_SafeMem == 0, the SafeMem routines merely call the real versions,
 * without any of the SafeMem checking. This provides an easy way to disable
 * SafeMem without recompiling your entire program.
 */
ULONG far gbl_SafeMem=1;

/*
 * gbl_SafeMemOutput is the function that is called to print the messages.
 * The default is a _writes() to stdout.
 */
void (*gbl_SafeMemOutput)() = (void (*)())_writes;

/*
 * FUNCTION:
 *   void *safe_AllocMem(Size, MemType, File, Line)
 *
 * DESCRIPTION:
 *     Peforms a call to AllocMem() with enhanced error checking. Access
 *   to this function should be through the macros in safemem.h -ONLY-.
 *   Note that all memory allocated with the safe_ functions must be freed
 *   with the safe_ functions.
 *
 * ARGUMENTS:
 *   long Size;      Size of the block to allocate.
 *   long MemType;   Type of memory to request.
 *   char *File;     Calling file     (ie __FILE__)
 *   int  Line;      Calling line     (ie __LINE__)
 *
 * RETURNS:
 *   SUCCESS: A pointer to the new memory block
 *   FAILURE: 0
 *
 * WRITTEN:
 *   Friday 14-Dec-90 13:02:11 spb
 */
void *safe_AllocMem(Size, MemType, File, Line)
ULONG Size;
ULONG MemType;
char *File;
ULONG Line;
{
  if(gbl_SafeMem)
  {
    struct MemNode *t;
    void *result = 0;

    Forbid();

    if(head)
      t = head;
    else
      t = 0;

    head = (struct MemNode *)AllocMem(Size + sizeof(struct MemNode), MemType);

    if(head)
    {
      head->child = t;
      head->file  = File;
      head->line  = Line;
      head->size  = Size;

      result = (void *)((long)head + sizeof(struct MemNode));
    }
    else
      head = t;

    Permit();
    return(result);
  }
  else
    return((void *)AllocMem(Size, MemType));
}

/*
 * FUNCTION:
 *   void *safe_AllocAbs(Size, Location, File, Line)
 *
 * DESCRIPTION:
 *     Peforms a call to AllocAbs() with enhanced error checking. Access
 *   to this function should be through the macros in safemem.h -ONLY-.
 *   Note that all memory allocated with the safe_ functions must be freed
 *   with the safe_ functions.
 *
 * ARGUMENTS:
 *   ULONG Size;      Size of the block to allocate.
 *   void *Location;  Where in memory it is
 *   char *File;      Calling file     (ie __FILE__)
 *   int  Line;       Calling line     (ie __LINE__)
 *
 * RETURNS:
 *   SUCCESS: A pointer to the new memory block
 *   FAILURE: 0
 *
 * WRITTEN:
 *   Sunday 25-Aug-91 08:46:34 spb
 */
void *safe_AllocAbs(Size, Location, File, Line)
ULONG Size;
void *Location;
char *File;
ULONG Line;
{
  if(gbl_SafeMem)
  {
    struct MemNode *t;
    void *result = 0;

    Forbid();

    if(head)
      t = head;
    else
      t = 0;

    head = (struct MemNode *)AllocAbs(Size + sizeof(struct MemNode),
                     (void *)((char *)Location - sizeof(struct MemNode)));

    if(head)
    {
      head->child = t;
      head->file  = File;
      head->line  = Line;
      head->size  = Size;

      result = (void *)((long)head + sizeof(struct MemNode));
    }
    else
      head = t;

    Permit();
    return(result);
  }
  else
    return((void *)AllocAbs(Size, Location));
}

/*
 * FUNCTION:
 *   void safe_FreeMem(Address, Size, File, Line)
 *
 * DESCRIPTION:
 *     Peforms a FreeMem() with the enhanced error checking. Must be accessed
 *   through the macros in safemem.h. Note that all memory allocated with the
 *   safe_ functions must be freed with the safe_ functions.
 *
 * ARGUMENTS:
 *   ULONG Address; Location of block to free
 *   ULONG Size;    Size of block to free
 *   char *File;    Calling file     (__FILE__)
 *   ULONG Line;    Calling line     (__LINE__)
 *
 * WRITTEN:
 *   Friday 14-Dec-90 13:02:20 spb
 */
void safe_FreeMem(Address, Size, File, Line)
void *Address;
ULONG Size;
char *File;
ULONG Line;
{
  if(gbl_SafeMem)
  {
    struct MemNode *t1, *t2;
    char buffer[256];

    Forbid();

   /*
    * Search the list for the node
    */
    t1 = head;
    t2 = 0;
    while(t1)
    {
      if(Address == (void *)((long)t1+sizeof(struct MemNode)))
      {
       /*
        * Found it, but it's the wrong size
        */
        if(Size != t1->size)
        {
          sprintf(buffer, "%s %d: Incorrect free size. (%d instead of %d)\n",
                     File, Line, Size, t1->size);
                     
          gbl_SafeMemOutput(buffer);
          Permit();
          return;
        }

       /*
        * Right size, so remove block from the list 
        */
        if(t2)
          t2->child = t1->child;
        else
          head = t1->child;

       /*
        * Actually free the memory
        */
        FreeMem(t1, sizeof(struct MemNode)+t1->size);
        Permit();
        return;
      }

      t2 = t1;
      t1 = t1->child;
    }

   /*
    * We didn't find the block on the list, so it can't be freed.
    * Either a free twice, or never allocated at all.
    */
    sprintf(buffer, "%s %d: Free twice (?) FreeMem($%lx, %d)\n",
                     File, Line,                   Address, Size);
    gbl_SafeMemOutput(buffer);
    Permit();
    return;
  }
  else
  {
    FreeMem(Address, Size);
    return;
  }
}

/*
 * FUNCTION:
 *   ULONG safe_ShowMemList(File, Line)
 *
 * DESCRIPTION:
 *     Displays a list of currently unfreed blocks (if there are any) that
 *   were allocated with safe_AllocMem. Along with each memory block
 *   is the file, function and line it was allocated from.
 *     If gbl_Safemem == 0, this function has no effect.
 *
 * ARGUMENTS:
 *   char *File;    Calling File (__FILE__)
 *   ULONG Line;    Calling Line (__LINE__)
 *
 * RETURNS:
 *   ULONG   Number of memoryblocks in the list.
 *
 * WRITTEN:
 *   Friday 14-Dec-90 13:02:26 spb
 */

ULONG safe_ShowMemList(File, Line)
char *File;
ULONG Line;
{
  if(gbl_SafeMem)
  {
    char buffer[256];
    ULONG c=0;
    struct MemNode *t;

    Forbid(); /* Slightly pointless when printing to the screen, eh? */
    if(head)
    {
      t = head;
      sprintf(buffer, "%s %d: Memory List\n", File, Line);
      gbl_SafeMemOutput(buffer);
      sprintf(buffer, "    %-12s %-12s %-16s %-4s\n",
             "Address", "Size", "File", "Line");
      gbl_SafeMemOutput(buffer);
      sprintf(buffer, "    %-12s %-12s %-16s %-4s\n",
             "-------", "----", "----", "----");
      gbl_SafeMemOutput(buffer);

      while(t)
      {
        sprintf(buffer, "    $%-11lx %-12d %-16s %-4d\n",
                t, t->size, t->file, t->line);
        gbl_SafeMemOutput(buffer);

        t = t->child;
        c++;
      }
    }
    Permit();
    return(c);
  }
  else
    return(0);
}

/*
 * FUNCTION:
 *   ULONG safe_FreeMemList(File, Line)
 *
 * DESCRIPTION:
 *     Frees all the memoryblocks present in the safe_ list. This ensures
 *   the environment will get all it's memory back even though the program
 *   is faulty, speeding development time.
 *     If gbl_SafeMem == 0, this function has no effect.
 *
 * ARGUMENTS:
 *   char *File; Calling file (__FILE__)
 *   ULONG Line; Calling line (__LINE__)
 *
 * RETURNS:
 *   long Number of blocks freed.
 *
 * WRITTEN:
 *   Friday 14-Dec-90 13:52:16 spb
 */

ULONG safe_FreeMemList(File, Line)
char *File;
ULONG Line;
{
  if(gbl_SafeMem)
  {
    char buffer[256];
    ULONG c=0;
    struct MemNode *t1, *t2;

    Forbid();
    if(head)
    {
      t1 = head;
      sprintf(buffer, "%s %d: Freeing Memory List\n", File, Line);
      gbl_SafeMemOutput(buffer);

      while(t1)
      {
        t2 = t1->child;
        FreeMem(t1, t1->size + sizeof(struct MemNode));
        t1 = t2;
        c++;
      }
    }

    head = 0;

    Permit();
    return(c);
  }
  else
    return(0);
}

/*
 * FUNCTION:
 *   void *safe_malloc(size, file, line)
 *
 * DESCRIPTION:
 *      malloc() with  with enhanced error checking. Access
 *   to this function should be through the macros in safemem.h -ONLY-.
 *   Note that all memory allocated with the safe_ functions must be freed
 *   with the safe_ functions.
 *
 *      malloc(x) is equivalent to AllocMem(x ,MEMF_PUBLIC|MEMF_CLEAR).
 *
 * ARGUMENTS:
 *   ULONG size;
 *   char *file;
 *   ULONG line;
 *
 * RETURNS:
 *   SUCCESS: A pointer to the block allocated
 *   FAILURE: 0
 *
 * WRITTEN:
 *   Saturday 05-Dec-92 16:34:
 */
void *safe_malloc(size, file, line)
ULONG size;
char *file;
ULONG line;
{
  struct MemNode *newnode;
  void *result;

  if(gbl_SafeMem)
  {
    Forbid();

    newnode = (struct MemNode *)AllocMem(size + sizeof(struct MemNode), 
                                         MEMF_PUBLIC | MEMF_CLEAR);

    if(newnode)
    {
      newnode->child = head;
      newnode->file  = file;
      newnode->line  = line;
      newnode->size  = size;
      head           = newnode;

      result = (void *)((long)newnode + sizeof(struct MemNode));
    }
    else
      result = 0;

    Permit();
    return(result);
  }
  else
    return(malloc(size));
}

/*
 * FUNCTION:
 *   void safe_free(address, file, line)
 *
 * DESCRIPTION:
 *     Safe version of free(). Access should be through the macros.
 *
 * ARGUMENTS:
 *   void *address;
 *   char *file;
 *   ULONG line;
 *
 * WRITTEN:
 *   Saturday 05-Dec-92 16:41:
 */
void safe_free(address, file, line)
void *address;
char *file;
ULONG line;
{
  struct MemNode *node, *prev;

  if(gbl_SafeMem)
  {
    if(address)
    {
      char buffer[256];
    
      Forbid();

      prev = 0;
      node = head;

      while(node)
      {
        if(address == (void *)((long)node+sizeof(struct MemNode)))
        {
         /*
          * Remove block from the list
          */
          if(prev)
            prev->child = node->child;
          else
            head = node->child;

         /*
          * Free the memory
          */
          FreeMem(node, node->size + sizeof(struct MemNode));
          return;
        }

        prev = node;
        node = node->child;
      }

      sprintf(buffer, "%s %d: Free twice (?) free($%lx)\n",
                       file, line,                address);
      gbl_SafeMemOutput(buffer);

      Permit();
    }
  }
  else
    free(address);
}

/*
 * FUNCTION:
 *   void *safe_realloc(block, newsize, file, line)
 *
 * DESCRIPTION:
 *   Safe version of realloc().
 *
 * ARGUMENTS:
 *   void *block;
 *   ULONG newsize;
 *   char *file;
 *   ULONG line;
 *
 * RETURNS:
 *   SUCCESS: Pointer to new block (pointer may be different to the old block,
 *            but the contents will be preserved).
 *   FAILURE: 0
 *
 * WRITTEN:
 *   Saturday 05-Dec-92 16:42:
 */
void *safe_realloc(block, newsize, file, line)
void *block;
ULONG newsize;
char *file;
ULONG line;
{
  void *newblock;
  struct MemNode *node;
  long oldsize;

  if(gbl_SafeMem)
  {
   /*
    * if the oldblock was 0, then this operation is equivalent to a
    * malloc().
    */
    if(!block)
      return(safe_malloc(newsize, file, line));
  
    Forbid();
  
   /*
    * Find the size of the current block
    */
    oldsize = -1;
    node = head;
    while(node)
    {
      if(block == (void *)((long)node+sizeof(struct MemNode)))
      {
        oldsize = node->size;
        break;
      }
  
      node = node->child;
    }
  
    if(oldsize == -1)
    {
      char buffer[256];
      sprintf(buffer, "%s %d: Illegal realloc(%lx, %ld)\n",
                       file, line,            block, newsize);
      gbl_SafeMemOutput(buffer);                 
      Permit();
      return(0);
    }
  
    newblock = safe_malloc(newsize, file, line);
    if(newblock)
    {
      memcpy(newblock, block, min(newsize, oldsize));
      safe_free(block, file, line);
    }
  
    Permit();
    return(newblock);
  }
  else
    return(realloc(block, newsize));
}

/*
 * FUNCTION:
 *   void *safe_calloc(elements, elementsize, file, line)
 *
 * DESCRIPTION:
 *     Safe version of calloc().
 *     Equivalent to malloc(elements*elementsize).
 *
 * ARGUMENTS:
 *   ULONG elements, elementsize;
 *   char *file;
 *   ULONG line;
 *
 * RETURNS:
 *   SUCCESS: Pointer to allocated block
 *   FAILURE: 0
 *
 * WRITTEN:
 *   Saturday 05-Dec-92 16:44:
 */
void *safe_calloc(elements, elementsize, file, line)
ULONG elements, elementsize;
char *file;
ULONG line;
{
  return(safe_malloc(elements * elementsize, file, line));
}
