/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* |_o_o|\\ Copyright (c) 1989 The Software Distillery.                    *
* |. o.| ||          All Rights Reserved                                  *
* | .  | ||          Written by Doug Walker                               *
* | o  | ||          The Software Distillery                              *
* |  . |//           235 Trillingham Lane                                 *
* ======             Cary, NC 27513                                       *
*                    BBS:(919)-471-6436                                   *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* This file contains the routines that must be linked in to every pgm   */
/* that wants to use MemWatchIII - MWInit, MWTerm, MWCheck, MWSend,      */
/* MWHold, MWPurge, MWSend                                               */

#include "mempriv.h"

struct MWGlobal mwg = {0L};  /* Make sure the 'flags' field is 0's */

/* Initialization routine - should be called once before any memory */
/* is allocated                                                     */

void MWInit(dbfh, flags)
BPTR dbfh;
LONG flags;
{
   if(mwg.flags & MWF_ACTIVE)
   {
      if(!dbfh && mwg.dbfh) dbfh = mwg.dbfh;
      if(dbfh)
      {
         MSG(dbfh, "MemWatch ERROR: MWInit called twice\n");
      }

      return;
   }

   memset((char *)&mwg, 0, sizeof(struct MWGlobal));

   mwg.flags = flags|MWF_ACTIVE;

   if(!(flags & MWF_NOLOG)) mwg.dbfh = (dbfh ? dbfh : Output());

   MWSend(MWM_INIT);

   mwg.lim[MWT_CHIP] = mwg.lim[MWT_FAST] = 0x7fffffff;

   mwg.task = FindTask(0L);
}

/* Termination routine - should be called after all memory usage is done */

void MWTerm()
{
   int msgdone = 0;

   MWCheck();

   MWSend(MWM_TERM);

   /* no need to trash the mem we may be about to free for good */
   mwg.flags |= MWF_NOFTRASH;

   if(!(mwg.flags & MWF_NOFREE))
   {
      while(mwg.first)
      {
         if(mwg.dbfh) 
         {
            if(!msgdone)
            {
               MSG(mwg.dbfh, 
          "MemWatch ERROR: The following allocations were not freed:\n");
               msgdone = 1;
            }
            MWPrint(mwg.first, 0, 0, 0);
         }
         MWFreeMem(mwg.first->memory, mwg.first->size, 0);
      }
   }

   MWPurge();  /* Really free all mem on the 'free' chain */

   memset((char *)&mwg, 0, sizeof(struct MWGlobal));
}

/* Validity check routine - checks all known allocations for overwrites */
/* Called from every alloc and free routine, plus when specifically     */
/* invoked                                                              */

void MWCheck()
{
   struct MWAlc *mwa;
   char *tmpchar;
   int error, header, trailer;

   if(mwg.flags & MWF_ERROR)
   {
      /* Error already found by external process */
      if(mwg.dbfh) 
         MSG(mwg.dbfh, 
            "MemWatch ERROR: External process found memory error\n");
      MWHold();
      return;
   }

   error = 0;
   for(mwa=mwg.first; mwa; mwa=mwa->next)
   {
      if( (mwa->myflags & MWF_REPMASK) == MWF_REPORTED) continue;

      if(header=memcmp((char *)&mwa->header, MWHEADER, 4))
      {
         if(mwg.dbfh) MSG(mwg.dbfh, "MemWatch ERROR: Header trashed\n");
      }

      if(trailer=memcmp(mwa->memory+mwa->size, MWTRAILER, 4))
      {
         if(mwg.dbfh) MSG(mwg.dbfh, "MemWatch ERROR: Trailer trashed\n");
      }

      if(header || trailer)
      {
         error = 1;
         mwa->myflags |= MWF_REPORTED;
         MWPrint(mwa, 0, 0, 0);
      }
   }

   for(mwa=mwg.freed; mwa; mwa=mwa->next)
   {
      if( (mwa->myflags & MWF_REPMASK) == MWF_REPORTED) continue;

      for(header=0, tmpchar=mwa->memory; 
          header<mwa->size; 
          header++, tmpchar++)
      {
         if(*tmpchar != MWFTRASH)
         {
            mwa->myflags |= MWF_REPORTED;
            if(mwg.dbfh)
            {
               error = 1;
               MSG(mwg.dbfh, "MemWatch ERROR: Freed memory modified\n");
               MWPrint(mwa, 0, 0, 0);
               break;
            }
         }
      }
   }

   if(error) MWHold();
}

void MWHold()
{
   struct MWAlc *mwa;

   if(mwg.flags & MWF_EXT)
   {
      if(mwg.dbfh) MSG(mwg.dbfh, "***Process Held By MemWatch***\n");
      MWSend(MWM_KILLME);      /* Should never return...               */
   }
   mwg.flags &= ~MWF_ERROR; /* But if it does, clear the error flag */

   /* If we're attempting to go on, make all the sentinals correct */
   for(mwa=mwg.first; mwa; mwa=mwa->next)
   {
      mwa->myflags = ~MWF_REPMASK;
      memcpy((char *)&mwa->header, MWHEADER, 4);
      memcpy((mwa->memory + mwa->size), MWTRAILER, 4);
   }

   for(mwa=mwg.freed; mwa; mwa=mwa->next)
   {
      mwa->myflags = ~MWF_REPMASK;
      memset(mwa->memory, MWFTRASH, mwa->size);
   }
}

/* MWPurge really frees all memory placed on the 'freed' chain by */
/* FreeMem() or free()                                            */

void MWPurge()
{
   struct MWAlc *cur, *next;

   for(cur=mwg.freed; cur; cur=next)
   {
      next = cur->next;
      FreeMem(cur, cur->size + sizeof(struct MWAlc));
   }
   mwg.freed = NULL;
}

/* MWSend communicates with the external MemWatch process */

int MWSend(type)
int type;
{
   struct MsgPort *extport;
   struct MWMsg mwmsg;

   if(!(mwg.flags & MWF_ACTIVE) ||
      !(mwg.flags & MWF_EXT)) return(1);

   if(!(extport = FindPort(MWPORTNAME)) || 
      !(mwmsg.msgpart.mn_ReplyPort = CreatePort(NULL, 0)))
      goto exterror;

   mwmsg.msgpart.mn_Length = sizeof(struct MWMsg);
   mwmsg.type = type|MWM_REPLY;
   mwmsg.mwg = &mwg;
   PutMsg(extport, &mwmsg.msgpart);
   WaitPort(mwmsg.msgpart.mn_ReplyPort);
   GetMsg(mwmsg.msgpart.mn_ReplyPort);
   DeletePort(mwmsg.msgpart.mn_ReplyPort);
   if(mwmsg.type != MWM_OK) 
   {
      goto exterror;
   }

   return(0);

exterror:
   if(mwg.dbfh) 
      MSG(mwg.dbfh, 
          "MemWatch ERROR: Can't communicate with external process\n");
   mwg.flags &= ~MWF_EXT;
   return(1);
}

#define ALCMSG \
  "0x00000000 length 00000000 allocated line 00000 file xxxxxxxxxxxxxxx\n"
 /*0         1         2         3         4         5         6*/
 /*0123456789012345678901234567890123456789012345678901234567890*/
#define ALC_MEM  2
#define ALC_LEN 18
#define ALC_LIN 42
#define ALC_FIL 53

#define FREMSG \
  "Invalid FreeMem call, addr 0x00000000 length 00000000\n"
 /*0         1         2         3         4         5         6*/
 /*0123456789012345678901234567890123456789012345678901234567890*/
#define FRE_MEM 29
#define FRE_LEN 45

#define LENMSG \
  "FreeMem called with length 00000000 on the following allocation:\n"
 /*0         1         2         3         4         5         6*/
 /*0123456789012345678901234567890123456789012345678901234567890*/
#define LEN_LEN 27
  
#define USEMSG \
  "00000000 bytes in 00000000 allocations\n"
 /*0         1         2         3         4         5         6*/
 /*0123456789012345678901234567890123456789012345678901234567890*/
#define USE_SIZ 0
#define USE_ALC 18

#define MAXMSG \
  "Max chip usage = 00000000 bytes; Max fast usage = 00000000 bytes\n\n"
 /*0         1         2         3         4         5         6*/
 /*0123456789012345678901234567890123456789012345678901234567890*/
#define MAX_CHP 17
#define MAX_FST 50

#define DIGITS "0123456789ABCDEF"

static void fmtdec(char *, long, LONG);
static void fmthex(char *, LONG);

static char *msgs[] =
{
   ALCMSG, FREMSG, LENMSG, USEMSG, MAXMSG
};

void MWPrint(mwa, msgnum, val1, val2)
struct MWAlc *mwa;
int msgnum;
LONG val1, val2;
{
   char *buffer;
   int i;

   if(!(mwg.flags & MWF_ACTIVE) || !mwg.dbfh) return;

   buffer = msgs[msgnum];

   switch(msgnum)
   {
      case 0:
         fmthex(msgs[0]+ALC_MEM, (LONG)mwa->memory);
         fmtdec(msgs[0]+ALC_LEN, 8, mwa->size);
         fmtdec(msgs[0]+ALC_LIN, 5, mwa->line);

         if( (i = strlen(mwa->file)) > 15) i=15;
         memcpy(msgs[0]+ALC_FIL, mwa->file, i);
         msgs[0][ALC_FIL+i]   = '\n';
         msgs[0][ALC_FIL+i+1] = '\0';
         break;

      case 1:
         fmthex(msgs[1]+FRE_MEM, val1);
         fmtdec(msgs[1]+FRE_LEN, 8, val2);
         break;

      case 2:
         fmtdec(msgs[2]+LEN_LEN, 8, val1);
         break;

      case 3:
         fmtdec(msgs[3]+USE_SIZ, 8, val1);
         fmtdec(msgs[3]+USE_ALC, 8, val2);
         break;

      case 4:
         fmtdec(msgs[4]+MAX_CHP, 8, val1);
         fmtdec(msgs[4]+MAX_FST, 8, val2);
         break;

      default:
         /* What do we do here?  Give up, I guess*/
         return;
   }

   MSG(mwg.dbfh, buffer);
}

static void fmtdec(buf, len, val)
char *buf;
long len;
LONG val;
{
   int i;
   for(i=1; i<=len; i++, val/=10)
      buf[len-i] = (val ? DIGITS[val%10] : ' ');
}

static void fmthex(buf, val)
char *buf;
LONG val;
{
   int i, j;
   union
   {
      LONG l;
      char c[4];
   } u;

   u.l = val;
   for(i=j=0; i<4; i++)
   {
      buf[j++] = DIGITS[(u.c[i]&0xf0)>>4];
      buf[j++] = DIGITS[u.c[i]&0x0f];
   }
}

