/*
  0202941634 <- last update (yymmddhh'')
     _____________________________________________________________
    /                                                             \ 
    | Module:       explib.cpp - expansion library base code
    \_____________________________________________________________/

    Description:    this is the code for the most fundamental functions
                    of explib

    Refers to:      explib.h,expdef.h
    Used by:        everywhere, especially other explib sources
    Uses:           explib.h

    Contents:
        RootBase :  this class provides fundamental functions for

                    - logging debug messages, error information or
                      other messages to logfile

                    - shell option parsing

    Surveillance:

        Currently, this source isn't float-tracked with the '_' macro
        even if TRACK_PC is set.

    Local Terminology:

        Abbrev. Meaning
        ------- ------------------------
        ...     ...


    Developers: ID: Name:
    =========== --- -----
                JTH Juergen Thumm
  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 930823 0.800   JTH split .h to .h and .cpp, added this header
 940202             errstr()
 940316             FloatControl::logpc
 940408             mem tracking now active by default, unless switched off
                    in the Prolog() call. This is necessary 'cause implicite
                    inits of global instances in some modules alloc memory
                    before main() is called.
 940811             perr now lists the last pc's that were passed before
                    perr was called (only if compiled with TRACK_PC)

 940905             ported to Windows 3.1, see .h
 940911             CLEAN_ALLOC added
 941123             alive() use and code cleanup, several 'root not yet
                    initialized' printf's reduced to AMIGA compile.
                    Default log name now "C:\\bblog" under WIN16.
                    remove(oldlogfile) now done directly before first
                    line of new log is written.
                    no logging of anything before alive().
 941213             RootBase::opt/parm now completely outside memtrack
                    scope, therefore no longer irritating report msg'es.
 940421             NOLOG support.
*/

#   include "expdef.h"
#   include "explib.h"
#   include "list.h"

#   define  xlr explib_root

//  the following defines that any memory block allocated
//  is to be cleared using memset(). Some compilers may do this
//  implicitely, so this define may be commented out.
//  Mind that un-initialized blocks may cause great worrying
//  if you forgot to init some pointers in a structure;
//  without clean-alloc'ed mem, the pointer then may point to
//  some random adress instead of NULL.

#   define  CLEAN_ALLOC

//  if clean allocation is active, memory blocks are filled up
//  with this byte:

#   define  CLEAN_BYTE 0x00

//  ModInit(explib_o);

RootBase::RootBase()
{
    argc    = 0;
    argv    = 0;

    opt     = 0;
    parm    = 0;

#ifdef  AMIGA
    logname     = "t:log";
#endif

#ifdef  WIN16
    logname     = "C:\\bb.log";
#endif

    logfile     = 0;
    logempty    = 1;

    progname[0] = '\0';
    private_vrsn= EXPLIB_VERSION;
    mode        = (enum RootModes)4;    // no log by default
    deblev      = 0;                    // low debug level

    validmatch  = (ulong)&validmatch;   // now, RootBase is initialized
}

RootBase::~RootBase()
{
    // check for forgotten memory:

    if(MemTracker.active)   MemTracker.report();

    MemTracker.active   = 0;

    // free RootBase's allocations, which are never tracked:

    if(opt)     delete [] opt;
    if(parm)    delete [] parm;

    // close logfile, if open:

    if(logfile) fclose(logfile);

    if(!logempty && stdout)
        printf("... there is information in >%s<\n",logname);

    // let the 'valid' flag active for Float- & MemTracker
    // until the very last instruction of the program is done.
}

ushort  RootBase::version() {   return EXPLIB_VERSION;      }
size_t  RootBase::size()    {   return sizeof(RootBase);    }

RootBase explib_root;   // global variable: RootBase instance

/*   _____________________________________________________________
    /                                                             \ 
    | Function: HelpRequest - check if help request is given
    \_____________________________________________________________/

    Descriptn:  if "-?", "/?" etc. is given,
                returns 1,
                otherwise returns 0

    Called by:  main
  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 930425 0.500   JTH created
 930814             adapted to RootBase class

*/
//||HelpRequest

int RootBase::HelpRequest()
{
    if(argc != 2 && argc != 3)  return 0;

    if(argc == 2)
    {
        /* check for -h -help -?    */

        if(     !strcmp(argv[1],"-h")
            ||  !strcmp(argv[1],"-help")
            ||  !strcmp(argv[1],"-?")   )   return 1;
    }

    if(argc == 2)
    {
        /* check for /h /help /?    */

        if(     !strcmp(argv[1],"/h")
            ||  !strcmp(argv[1],"/help")
            ||  !strcmp(argv[1],"/?")   )   return 1;
    }

    if(argc == 3)
    {
        /* check for "opt help"     */

        if(     !strcmp(argv[1],"opt")
            &&  !strcmp(argv[2],"help") )   return 1;
    }

    return 0;
}

/*  _____________________________________________________________
   /                                                             \ 
//|| SplitOptParms
   \_____________________________________________________________/

    Descriptn:  splits shell parameters into pure options (without
                "-", "/" etc.) and parameters.

    Returncode:   possible
        Error       |   Reported to     Remark:
        ----------- V   -----------     -------
        FAILED   .   -               -
        MEMORY   x   perr            can't alloc ptrs
        IO       .   -               -
        INDATA   x   perr            illegal opt/parm sequence
        NOTAVAIL .   -               -

    Called by:  Prolog

    Notes:
        supports multiple option formats, see SupOptFmts

  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 930425 0.500   JTH created

*/

rcode RootBase::SplitOptParms(enum RootBase::OptionFormats supoptfms)
{
 int i,oidx=0,pidx=0;   /* option index, parm index */
 char *cp,optkeypassed=0,slashpassed=0;
 bool trackmode = MemTracker.active;

    // this function allocates RootBase-local memory which
    // is never tracked:

    MemTracker.active   = 0;

    // the case SplitOptParms was already called, free old opts/parms

    if(opt)     {   delete [] opt;  opt  = 0;   }
    if(parm)    {   delete [] parm; parm = 0;   }

    // alloc mem for root ptrs to parms, opts

    if(!(opt = new char*[argc+1]))
    {
        err("out of memory - can't parse options");
        MemTracker.active   = trackmode;
        return MEMORY;
    }

    if(!(parm = new char*[argc+1]))
    {
        err("out of memory - can't parse parameters");
        MemTracker.active   = trackmode;
        return MEMORY;  // opt is deleted later
    }

    MemTracker.active   = trackmode;

    for(i=1; i < argc; i++)
    {
        cp = argv[i];

        if(supoptfms & DASH)
        {
            /* is it an "-" option? */

            if( (strlen(cp) > 1) && *cp == '-' )
            {
                opt[oidx++]  = cp + 1;
                continue;
            }
        }

        if(supoptfms & SLASH)
        {
            /* is it an "/" option? */

            if( (strlen(cp) > 1) && *cp == '/' )
            {
                opt[oidx++]  = cp + 1;
                slashpassed = 1;
                continue;
            }
        }

        if(supoptfms & KEYOPT)
        {
            if(optkeypassed)
            {
                opt[oidx++]  = cp;
                continue;
            }
            else
                if(!strcmp(cp, "opt") || !strcmp(cp, "OPT"))
                {
                    optkeypassed = 1;
                    continue;
                }
        }

        /* anything that's not an opt must be a parm:   */

        if(slashpassed)
        {
            err("parameter %s not allowed after '/option'",cp);
            return INDATA;
        }

        parm[pidx++] = cp;
    }

    opt[oidx]    = NULL;
    parm[pidx]   = NULL;

    return OK;
}

/*   _____________________________________________________________
    /                                                             \ 
    | Function: NextOption - next command line option
    \_____________________________________________________________/

    Descriptn:  gets the next option supplied by the command line,
                WITHOUT the "-", "/" or anything else that marked
                it as an option

    Returns:    char *      ptr to the option
                or NULL     if there are no more options

    Requires:   Prolog or SplitOptParms call

  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 930501 0.500   JTH created

*/

char *RootBase::NextOption()
{
 static char firstcall=1;
 static char **crsr;    /* option cursor    */

    if(firstcall)
    {
        firstcall = 0;

        /* Initialize option cursor:    */

        if(!(crsr = opt))
            return  NULL;   /* no entries at all    */
    }

    if(!*crsr)  return  NULL;   /* last entry reached   */

    return  *crsr++;
}

/*  _____________________________________________________________
   /                                                             \ 
//|| NextParm - next command line parameter
   \_____________________________________________________________/

    Descriptn:  works exaclty like NextCLOpt;
                this returns the next 'real parameter' supplied via
                the command line;
                a 'real parameter' is any of the command line para-
                meters which are NOT an option

  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 930501 0.500   JTH created

*/

char *RootBase::NextParm()
{
 static char firstcall=1;
 static char **crsr;    /* parm cursor  */

    if(firstcall)
    {
        firstcall = 0;

        /* Initialize parm cursor:  */

        if(!(crsr = parm))
            return(NULL);   /* no entries at all    */
    }

    if(!*crsr)  return(NULL);   /* last entry reached   */

    return(*crsr++);
}

/*  _____________________________________________________________
   /                                                             \ 
//|| Prolog - program startup inits
   \_____________________________________________________________/

    Descriptn:  performs fundamental initializations,
                e.g. splitting command line parameters
                into options and 'real parameters'

    Returncode: -

    Called by:  user's main() as one of the first calls

    Calls:      ...

    Dependency: IF you call 'Prolog',
                THEN you must also call 'Epilog' at the end of main() !

  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 930430 0.500   JTH created

*/

void RootBase::Prolog(
            int     argc,       /* from main()  */
            char    *argv[],    /* from main()  */
            char    *xlname,    /* log file name (""  = no log) */
            int     xmodes      // debug etc. active or not
           )
{
 RootModes modes = (RootModes)xmodes;
 char *logmode   = "wp";
 int cr1;   /* comparison result    */

    if(modes & NOLOG)
        logmode = "w";  // log disabled, don't open now

    this->argc  = argc;
    this->argv  = argv;

    //  switch modes, including memtracking.

    mode    = modes;

    if(mode & MEMTRACK) MemTracker.active = 1;
    else                MemTracker.active = 0;

    //  Make our program name globally available for error msges:
    //  this is also important for re-identification via MemHook

    if(argc)
        strncpy(progname, argv[0], PROGNAME_MAX);
    else
        strncpy(progname, "<untitled>", PROGNAME_MAX);

    /*  Init logfile stuff: */

    if(strlen(xlname))
    {
        logname = xlname;

        if(!(cr1=strcmp(logmode, "wp")) || !strcmp(logmode, "ap"))
        {
            // open log NOW, keep it open 'til Epilog:

            logfile  = fopen(logname,cr1 ? "a" : "w");
        }
        else
        {
            // open log everytime something must be added:

            logfile = NULL;

            // already existing log might be remove'd
            // as soon as first log entry is written
        }
    }
    else
        logname = NULL;

    //  Split arguments from shell into options and parameters.

    if(argc)

        SplitOptParms(DASH);
}

    /* Now comes...
    =====================================================================

                    "The LOGGING FUNCTION TRIPLET"
                    ------------------------------

    Three VERY useful functions for convenient message logging:

        dmsg    - writes a debug message  to logfile
        pmsg    - writes normal messages  to logfile
        perr    - writes an error message to logfile

    These 3 functions will call another function, "logmsg", which
    should be called ONLY be these functions, and not by a user program.

     _____________________________________/VERSION HISTORY\_____________
    /Date_ Version Who What_____________________________________________\ 
    yymmdd ------- --- --------------------------------------------------
    930504 0.80    JTH created this group of functions. Now, the useful
        logging functions can easily be copied into non-explib sources.
    =====================================================================
    */

/*   _____________________________________________________________
    /                                                             \ 
    | Function: logmsg - log message
    \_____________________________________________________________/

    Restrictn:  LOCAL SUB-FUNCTION - NOT TO BE CALLED BY USER PROGRAM!
                User prg's must use perr, pmsg, dmsg instead.

    Descriptn:  writes a message to the log file

    Called by:  perr, pmsg, dmsg,
                if logging to file was selected in the 'Prolog' call

    Calls:      vfprintf, vprintf, fopen, fclose

    Notes:
      * This will add lines to the logfile in two modes:
        1. if explogfile is an open file: adds line directly
        2. if it's closed: opens file, adds line, then closes it again
        If mode 1. or 2. is used is determined by the 'Prolog' call.
        The 2nd mode guarantees a readable logfile for the case the
        program crashes; in mode 1 the logfile is usually lost.

      * This can also be used WITHOUT explib! To do so, extract
        this, dmsg, pmsg, perr from the source and imbed id into your
        own source. Define then LOG_FILE_NAME.
        In that case, mode (1.) is not available.

    Exceptions:
        If the logfile cannot be opened, the first time this func is
        called an error message and the log line is printf'ed,
        all subsequent calls only the log line is printf'ed.

    NOTE THE SCOPE:

        This function may also be called without(!) a valid 'this' pointer.
        This is so if it's called by 'perr' and this wasn't called via
        the root pointer but directly (adress used as function pointer).
*/

void RootBase::logmsg(char *str, ...)       /* 'str' must contain LF! */
{
    if(explib_root.mode & NOLOG)
        return;     // log forbidden

 FILE *lf;
 static char cnol=0;    /* cannot open log  */

    if(!xlr.alive())    return; // no log filename yet available

 va_list arg_ptr;

  va_start(arg_ptr, str);

    if(xlr.logfile)
    {
        /* log is open: directly append line    */

        vfprintf(xlr.logfile, str, arg_ptr);
        fflush(xlr.logfile);

        xlr.logempty = 0;
    }
    else
    {
      // the case there's an old log file,
      // delete it before writing first line of new one

      if(!cnol && xlr.logempty) remove(xlr.logname);

      // open log, append line, close log

      if(!cnol && (lf = fopen(xlr.logname, "a")))
      {
        vfprintf(lf, str, arg_ptr);
        fclose(lf);

        xlr.logempty = 0;
      }
      else
      {
        if(!cnol && stdout)
        {
            cnol = 1;

#define THIS_SRC_NAME (__FILE__)

            printf("ERROR:%s:CANNOT OPEN LOGFILE \"%s\"\n",
                    THIS_SRC_NAME, xlr.logname);

            printf("      ... writing to standart output\n");
        }

        vprintf(str, arg_ptr);
      }
    }

  va_end(arg_ptr);
}

/*  _____________________________________________________________
   /                                                             \ 
//|| perr - print error
   \_____________________________________________________________/

    Description:    this prints an error message;
                    currently, the message goes to terminal,
                    but in the future output might go to file.

    Usage:          like printf, but '\n' at line-end is not needed.

    Called by:      everywhere

  _________________________________/VERSION HISTORY\____________________
 /Date_ Version What____________________________________________________\ 
 yymmdd ------- ---------------------------------------------------------
 920328 0.50000 created

*/

sysint RootBase::err(const char *str, ...)
{
    if(explib_root.mode & NOLOG)
        return  0;  // log forbidden

 static char buf[320];
 static uchar PCsdumped=0;
 va_list arg_ptr;

  va_start(arg_ptr, str);

    vsprintf(buf, str, arg_ptr);

//  _p  (0xEEEEEEEEUL, (ulong)buf, (ulong)str, (ulong)&str);

    xlr.logmsg("ERROR:%s\n", buf);

    if(!PCsdumped)
    {
        xlr.FloatTracker.dumplastpcs(); // what pc's did we pass before error
        PCsdumped   = 1;
    }

    if(stdout)  printf("ERROR:%s\n", buf);  // dump errors also to terminal

  va_end(arg_ptr);

  return 0;
}

/*   _____________________________________________________________
    /                                                             \ 
    | Function:     pmsg - print message                          |
    \_____________________________________________________________/

    Description:    this writes a normal message,
                    to logfile or terminal.

    Usage:          like printf, but WITHOUT '\n' at line-end!

    Called by:      everywhere

    Kwown bugs:     don't know what this func is good for...

  _________________________________/VERSION HISTORY\____________________
 /Date_ Version What____________________________________________________\ 
 yymmdd ------- ---------------------------------------------------------
 920328 0.50000 created

*/

sysint RootBase::msg(const char *str, ...)
{
    if(explib_root.mode & NOLOG)
        return  0;  // log forbidden

 char buf[320];
 va_list arg_ptr;

  va_start(arg_ptr, str);

    vsprintf(buf, str, arg_ptr);

    xlr.logmsg("%s\n", buf);

  va_end(arg_ptr);

  return 0;
}

/*   _____________________________________________________________
    /                                                             \ 
    | Function:     dmsg - debug message output                   |
    \_____________________________________________________________/

    Description:    this will write a debugging message
                    to the log file,
                    but only if the run-time debug switch is active.

    Restrictions:   in case of non-explib compile,
                    there is no run-time debug switch;
                    messages are then always written.

    Usage:          like printf, but '\n' at line-end is not needed.

    Called by:      everywhere

  _________________________________/VERSION HISTORY\____________________
 /Date_ Version What____________________________________________________\ 
 yymmdd ------- ---------------------------------------------------------
 920328 0.50000 created
 930504 0.70    adapted to non-explib compile

*/

sysint RootBase::deb(const char *str, ...)
{
    if(explib_root.mode & NOLOG)
        return  0;  // log forbidden

 char buf[320];
 va_list arg_ptr;

    ifn(xlr.mode & DEBUG) return 0; // no debug - no action

  va_start(arg_ptr, str);

    vsprintf(buf, str, arg_ptr);

    logmsg("DEBUG:%s\n", buf);

  va_end(arg_ptr);

  return 0;
}

    /* End of ...
    =====================================================================

                "The LOGGING FUNCTION TRIPLET"
                ------------------------------

    =====================================================================
    */

/*   _____________________________________________________________
    /                                                             \ 
    | method:   hexdump
    \_____________________________________________________________/

    Description:    this creates a hexdump (including readable characters)
                    using a user-defined output function.

    Example call:

        hexdump(&x, sizeof(x), printf, 1);

    Local Terminology:

        Abbrev. Meaning
        ------- ------------------------
        ...     ...


    Developers: ID: Name:
    =========== --- -----
                JTH Juergen Thumm
  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 ...... 0.1.0   JTH created

*/

void RootBase::hexdump(void *strptr, size_t strsize,
    sysint(*prtfunc)(const char*,...), int lterm)
{
 char tbuf[100];
 int  ind = 3;      /* indent */
 int  outlen, tint, tint2, outl2;
 struct { char HUGE *data; size_t size; } buf;
 unsigned char HUGE *bp,uchr;
 void (*print)(char *,...)  = (void (*)(char*,...))prtfunc;

  buf.data  = (char HUGE *)strptr;
  buf.size  = strsize;

  while(buf.size > 0)
  {
    /* Binary logging: dump upto 16 bytes per line as HEX */

    bp      = buf.data;
    outlen  = min(16, (int)buf.size);

    /*                      1         2         3         4          */
    /*            01234567890123456789012345678901234567890123456789 */
   sprintf(tbuf, "   >                                                "
                 "                   %08lX", bp);
    outl2   = outlen;
    for(tint=1+ind, tint2=51+ind; outl2; outl2--, tint += 3, tint2++)
    {
        uchr    =   *bp++;

        sprintf(tbuf + tint, "%02X ", uchr);
        if(!isprint(uchr))  uchr = 0x1; /* nonprintable char */
        tbuf[tint2] = uchr;
    }
    tbuf[tint-1]    = '<';
    tbuf[tint  ]    = ' ';

    if(lterm)
        print("%s\n", tbuf);
    else
        print("%s", tbuf);

    buf.data    += outlen;
    buf.size    -= outlen;
  }
}

//  ---- Memory allocation surveillance: ----

ulong MemBlock::calcsum()
{
 uchar* x = (uchar*)this;
 size_t s = sizeof(*this) - sizeof(ulong);  // size w/o csum entry
 ulong  v = 0;

    for(size_t i=0; i<s; i++)
    {
        //  rotate left one bit

        if(v & (1UL<<31))
            v   = (v << 1) | 1UL;
        else
            v   =  v << 1;

        //  xor with next byte

        v   ^= (ulong)*x++;

        //  do some plain math

        v   =  (ulong)(0 - (long)v);
    }

    return  v;
}

void MemBlock::setsum()
{
    csum    = calcsum();
}

bool MemBlock::testsum()
{
 ulong x1   = csum;
 ulong x2   = calcsum();

    if(x1 != x2)
    {
/*
        pmsg("1) csum=%lx calcsum()=%lx res %d",x1,x2,x1!=x2);  // ***

        ulong x1    = csum;
        ulong x2    = calcsum();

        pmsg("2) csum=%lx calcsum()=%lx res %d",x1,x2,x1!=x2);  // ***
*/
        return 1;
    }

    return 0;
}

//|| MemControl 

#define mtrack explib_root.MemTracker

MemBlock* MemControl::AddMemBlock(void *adr, size_t size, char* src, int line)
{
 MemBlock *mb;
 ListNode *nd;

    ifn(mb = (MemBlock*)malloc(sizeof(MemBlock)))
        return 0;
    else
    {
        // Complete MemBlock structure:

        mb->adr     = adr;
        mb->size    = size;
        mb->seqno   = ++seqno;

        mb->src     = src;
        mb->line    = line;

        MemBlock*   first = (MemBlock*)ls.rawfirst();

        ls.rawaddfront((ListNode*)mb);

        // if there was already a block, it's checksum is no
        // invalid 'cause it's .pred pointer changed, so:

        if(first)   first->setsum();    // update checksum

        mb->setsum();   // create own checksum

//      dmsg("AddMB: %lx umem %lx src %s line %ld",
//          mb, mb->adr, mb->src ? mb->src : "", mb->line); // ***

        ovalloc += size;
    }

    return mb;
}

MemBlock* MemControl::FindMemBlock(void *adr)
{
 ListNode *nd;

    for(nd=ls.rawfirst(); nd; nd=nd->rawnext())
        if(((MemBlock*)nd)->adr == adr)
            break;

    return (MemBlock*)nd;
}

void MemControl::RemMemBlock(MemBlock *mb)
{
    ovalloc -= mb->size;

//  dmsg("RemMB: %lx",mb);  // ***

    //  take care of potential pre/successor:

    MemBlock*   pre = (MemBlock*)mb->rawprev();
    MemBlock*   suc = (MemBlock*)mb->rawnext(); 

    ls.rawremove(mb);

    //  update pre/succ's checksums; they changed
    //  due to pointer manipulations.

    if(pre) pre->setsum();
    if(suc) suc->setsum();

    free(mb);   // free control structure, not the memory itself
}

// NEW /1503941129: pre/post areas for illegal write hit check

void MemControl::SetPrePost(MemBlock* mb, size_t pre, size_t post)
{
 uchar  *m_pre, *m_post;

    m_pre   = (uchar*)mb->adr - pre;        // pre_mem  adr, size is 'pre'
    m_post  = (uchar*)mb->adr + mb->size;   // post_mem adr, size is 'post'

    // write test pattern:

    memset(m_pre,  0xFF, pre);
    memset(m_post, 0xFF, post);
}

rcode MemControl::TestPrePost(MemBlock* mb, size_t pre, size_t post)
{
 rcode  rc=OK;
 uchar  *m_pre, *m_post, *ucp;
 size_t i;
 const  int MAXDUMP=1024;

    //  this code is also called from ListNode and ListHead methods!

    m_pre   = (uchar*)mb->adr - pre;        // pre_mem  adr, size is 'pre'
    m_post  = (uchar*)mb->adr + mb->size;   // post_mem adr, size is 'post'

    // check if pattern is damaged:

    for(i=0, ucp=m_pre; i<pre; i++, ucp++)
    {
        if(*ucp != 0xFF)
        {
            LogBlockErr("ILLEGAL PRE-MEMORY WRITE HIT!", mb);

            pmsg(" first hit at %lx (byte no. %d before block)",
                ucp, (uchar*)mb->adr - ucp);

    pmsg(" now follows a dump of the area before the block (FF == OK)");

            explib_root.hexdump(m_pre,min(pre, MAXDUMP),pmsg,0);

            if(pre > MAXDUMP)   dmsg("... dump aborted");

            rc  = FAILED;

            break;
        }
    }

    for(i=0, ucp=m_post; i<post; i++, ucp++)
    {
        if(i==0 && ucp != m_post)   // ***
            {SysErr(1309941404); rc=FAILED; break;}

        if(*ucp != 0xFF)
        {
            LogBlockErr("ILLEGAL POST-MEMORY WRITE HIT!", mb);

            pmsg(" first hit at %lx (byte no. %d after block)",
                ucp, (ucp - m_post) + 1);

    pmsg(" now follows a dump of the area after the block (FF == OK)");

            explib_root.hexdump(m_post, min(post, MAXDUMP), pmsg, 0);

            if(post > MAXDUMP)  dmsg("... dump aborted");

            rc  = FAILED;

            break;
        }
    }

    return  rc;
}

void MemControl::LogBlockErr(char* errstr, MemBlock* mb)
{
    perr("%s", errstr);

    perr(" block %lx, size %lu, alloc'ed in %s line %d",
        mb->adr, mb->size, mb->src ? mb->src : "<unknown>", mb->line);
}

rcode MemControl::checkup()
{
 ListNode*  nd;

 MemBlock*  mb;
 uchar  *imem;      // internal memory start
 size_t spre,spost; // pre/post area sizes

 rcode  rc = OK;
 const  int MAXDUMP=1024;

    for(nd=ls.rawfirst(); nd; nd=nd->rawnext())
    {
        mb  = (MemBlock*)nd;

#ifdef  WIN16
        //  1. is higher adress word 0 but lower word set?
        //     if so, mb points to illegal 'NULL' segment

        if( (((ulong)mb) >> 16) == 0UL && (uword)mb != 0 )
        {
            perr("illegal memory control block adress");
            perr("node=%lx block=%lx", nd, mb);

            exit(1);    // ... leave as early as possible!
        }
#endif
        //  2. is checksum invalid?

        if(mb->testsum())
        {
            perr("illegal checksum in memory control block");
            perr("node=%lx block=%lx oldsum=%lx newsum=%lx",
                nd, mb, mb->getsum(), mb->calcsum() );

            pmsg("+ control block contents: +");
            explib_root.hexdump(mb, sizeof(MemBlock), pmsg);

            pmsg("+ related user memory block: +");
            perr(" block %lx, size %lu (0x%lx), alloc'ed in %s line %d",
                mb->adr, mb->size, mb->size,
                mb->src ? mb->src : "<unknown>", mb->line);

            perr("+ related user memory contents: +");
            explib_root.hexdump(mb->adr, min(mb->size, MAXDUMP), pmsg);

            exit(1);
        }

        //  3. user memory area corrupted?

        spre    = max(mb->size >> 4, 4);    // 1/16 or 4 bytes before usermem
        spost   = max(mb->size >> 3, 4);    // 1/8  or 4 bytes after  usermem

        if(TestPrePost(mb, spre, spost))

            exit(1);
    }

    return  rc;
}

//|| new,delete

#ifdef  new
#define tmssm_2508931306 new
#undef  new
#endif

#ifdef  delete
#define tmssm_2508931307 delete
#undef  delete
#endif

void *operator new(size_t size, char* source, int line)
{_
 uchar      *imem,*umem;    // internal/user memory start
 MemBlock   *mb;
 size_t     spre,spost,salloc;  // pre/post/overall size

#ifdef  AMIGA
    if(!explib_root.alive() && stdout)
        printf("WARNING 2003941225: Root not yet initialized\n");
#endif

    if(!explib_root.alive() || !mtrack.active)
    {
        umem    = (uchar*)malloc(size);     // surveillance inactive

#ifdef  CLEAN_ALLOC
        if(umem)    memset(umem, CLEAN_BYTE, size); // clear (upto 1st 64k of) block
#endif
        return  umem;
    }

    if(mtrack.checkup()) return 0; // *** is memlist still usable?

    // calc size of extra allocated memory BEFORE and AFTER the user mem.
    // this extra mem will be checked on de-allocation for damages.

    spre    = max(size >> 4, 4);    // 1/16 or 4 bytes before usermem
    spost   = max(size >> 3, 4);    // 1/8  or 4 bytes after  usermem

    salloc  = spre + size + spost;

    umem    = 0;

    if(imem = (uchar*)malloc(salloc))   // get internal memory start
    {
        umem    = imem + spre;          // calc user's memory start

        ifn(mb  = mtrack.AddMemBlock(umem, size, source, line))
        {
            free(imem);
            umem = 0;
        }
        else
        {
            // write test data into pre/post areas.
            // on 'delete' call, it is checked if these datas
            // were damaged.

            mtrack.SetPrePost(mb, spre, spost);

#ifdef  CLEAN_ALLOC
            memset(umem, CLEAN_BYTE, size); // clear (upto 1st 64k of) block
#endif
            mtrack.newcnt++;
        }
    }

    return  umem;
}

void *operator new(size_t size)
{_
 uchar      *imem,*umem;    // internal/user memory start
 MemBlock   *mb;
 size_t     spre,spost,salloc;  // pre/post/overall size

#ifdef  AMIGA
    if(!explib_root.alive() && stdout)
        printf("WARNING 2003941226: Root not yet initialized\n");
#endif

    if(!explib_root.alive() || !mtrack.active)
    {
        umem    = (uchar*)malloc(size);     // surveillance inactive

#ifdef  CLEAN_ALLOC
        if(umem)    memset(umem, CLEAN_BYTE, size); // clear (upto 1st 64k of) block
#endif
        return  umem;
    }

    if(mtrack.checkup()) return 0; // *** is memlist still usable?

    // calc size of extra allocated memory BEFORE and AFTER the user mem.
    // this extra mem will be checked on de-allocation for damages.

    spre    = max(size >> 4, 4);    // 1/16 or 4 bytes before usermem
    spost   = max(size >> 3, 4);    // 1/8  or 4 bytes after  usermem

    salloc  = spre + size + spost;

    umem    = 0;

    if(imem = (uchar*)malloc(salloc))   // get internal memory start
    {
        umem    = imem + spre;          // calc user's memory start

        ifn(mb  = mtrack.AddMemBlock(umem, size, 0, -1))
        {
            free(imem);
            umem = 0;
        }
        else
        {
            // write test data into pre/post areas.
            // on 'delete' call, it is checked if these datas
            // were damaged.

            mtrack.SetPrePost(mb, spre, spost);

#ifdef  CLEAN_ALLOC
            memset(umem, CLEAN_BYTE, size); // clear (upto 1st 64k of) block
#endif
            mtrack.newcnt++;
        }
    }

    return  umem;
}

void operator delete(
    void *umem
#ifdef  AMIGA
    , size_t dummy
#endif
    )
{_
 MemBlock   *mb;        // control structure
 uchar      *imem;      // internal memory start
 size_t     spre,spost; // pre/post area sizes

    ifn(mtrack.active)
    {   free(umem); return; }

    if(!explib_root.alive())
    {
#ifdef  AMIGA
        if(stdout)
            printf("WARNING 2003941227: Root not yet initialized\n");
#endif
        free(umem);
        return;
    }

    mtrack.delcnt++;

    if(mb   = mtrack.FindMemBlock(umem))
    {
        spre    = max(mb->size >> 4, 4);    // 1/16 or 4 bytes before usermem
        spost   = max(mb->size >> 3, 4);    // 1/8  or 4 bytes after  usermem

        imem    = (uchar*)umem - spre;      // calc internal memory start

        // check if pre- or post-area was damaged.
        // if so, messages in log will be created.

        mtrack.TestPrePost(mb, spre, spost);

        // remove MemBlock control structure

        mtrack.RemMemBlock(mb);

        // free total memory block, including user memory

        free(imem);
    }
    else
    {
        perr("MEMORY MANAGEMENT ERROR:");
        perr("  delete(%lx) called but %lx doesn't exist",
            umem, umem);
    }
}

bool MemControl::TestAdr(void *x, char* src, int line)
{
 ListNode   *ln;
 MemBlock   *mb;        // control structure
 size_t     spre,spost; // pre/post area sizes
 uchar*     uadr;

    ifn(active) return 0;

    if(!explib_root.alive())
    {
#ifdef  AMIGA
        if(stdout)  printf("WARNING 0204941113: Root not yet initialized\n");
#endif
        return 0;
    }

    //  NOTE THIS: the ListNode and ListHead methods use TestAdr()
    //             themselves, so DON'T call them in here!

    for(ln=ls.rawfirst(); ln; ln=ln->rawnext())
    {
        mb  = (MemBlock*)ln;

        uadr = (uchar*)mb->adr;

        if(uadr <= x && uadr + mb->size > x)
        {
            spre    = max(mb->size >> 4, 4);
            spost   = max(mb->size >> 3, 4);

            if(mtrack.TestPrePost(mb, spre, spost))
            {
                perr(" detected in %s line %d", src, line);

                exit(1);

                return  1;
            }

            return  0;
        }
    }

    perr("ILLEGAL POINTER ADRESS DETECTED");
    perr(" %s line %d : unknown adress %lx",
        src, line, x);

    exit(1);

    return  1;  // invalid adress
}

#ifdef  tmssm_2508931306
#define new     tmssm_2508931306
#endif

#ifdef  tmssm_2508931307
#define delete  tmssm_2508931307
#endif

/*   _____________________________________________________________
    /                                                             \ 
    | method:   MemControl::report
    \_____________________________________________________________/

    Description:    this checks
                    - if there were more or less 'delete' operator calls
                      then 'new' calls
                    - if the overall amount of memory freed by 'delete'
                      is less then the allocated amount

        If there are forgotten memory blocks, hexdumps of some of the first
        will be created in the log as messages.

    Uses:           perr, pmsg

    Local Terminology:

        Abbrev. Meaning
        ------- ------------------------
        ...     ...


    Developers: ID: Name:
    =========== --- -----
                JTH Juergen Thumm
  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 930824 0.1.0   JTH created

*/

void MemControl::report(char *sourcename, int sourceline)
{
 int maxout   = 30;     // terminal: upto 30 entries        
 int dumpsize = 48;     // terminal: 3 lines per hexdump    
 int remain   = 0;
 MemBlock *mb,*mb2;

    if(newcnt != delcnt)
    {
        perr("Memory management inconsistency occurred.");

        perr("operator 'new' was called %d times,", newcnt);
        perr("but   'delete' was called %d times!", delcnt);
    }

     // FULL MEMORY TRACKING ACTIVE: create extended report 

     if(ovalloc)
     {
        maxout = 30; dumpsize = 80;

        if(sourcename)
            perr("%lu bytes remained allocated at end of >%s<.",
                   ovalloc, sourcename);
        else
            perr("%lu bytes remained allocated.", ovalloc);

        pmsg("");

        if(sourcename)
            pmsg("report() called from >%s<, line %d",
                 sourcename, sourceline);

        pmsg("Now follows a list of the remaining memory blocks.");
        pmsg("Blocks greater than %d bytes are truncated.",dumpsize);

        pmsg("");

        pmsg(" ADRESS    SIZE   SOURCENAME / LINE , NEW_CALL_NO.");
        //    12345678 12345678

        for(mb=(MemBlock*)ls.rawfirst(); mb; mb=(MemBlock*)mb->rawnext())
        {
            pmsg("");

            pmsg("%08lX %08lu %s / %d , %d",
                 mb->adr, mb->size,
                 mb->src ? mb->src : "<unknown>", mb->line, mb->seqno);

            // Display hexdump of the first bytes of the block: 

            explib_root.hexdump(mb->adr, min(mb->size, dumpsize), pmsg, 0);

            // Now, did we list enough entries? 

            if(!--maxout && mb->rawnext())
            {
                // How many entries were not listed? 

                for(mb2 = (MemBlock*)mb->rawnext(); mb2;
                    mb2 = (MemBlock*)mb2->rawnext())
                        remain++;

                pmsg("... report stopped. %d entries omitted.",
                              remain);

                break;

            }   // endif listed_maximum and not_at_end 

        }   // endfor memory blocks 

     }   // if remaining overall size 
}

/*   _____________________________________________________________
    /                                                             \ 
    | Program Float tracking
    \_____________________________________________________________/

  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 940316 1.00    JTH created
*/

//|| FloatCtrl 

void FloatControl::logpc(char* source, int line)
{
    logidx++;

    PCEntry*    pce = &pclog[logidx &= INDEXMASK];

    if(!explib_root.alive())
    {
#ifdef  AMIGA
        if(stdout)
            printf("WARNING 2003941223 [%s, %d]: Root not yet initialized\n",
                source, line);
#endif
        return;
    }

//  if(strcmp(source, "mcpp:explib/sound.cpp"))
//  {   logidx--;   return; }   // ÷

    pce->source = source;
    pce->line   = (ulong)line;  // bit 1 << 31 == 0 : has no user parms
    pce->seqno  = seqcnt;

    // float control logging:
/*
    if( ((seqcnt & 127) == 0x0 && explib_root.deblev >= 10)

        || explib_root.deblev >= 100

        || seqcnt > 140000UL
      )
        dmsg("[%lu %s %ld]",seqcnt,source?source:"",(ulong)line);   // ***
*/
    seqcnt++;
}

void FloatControl::dumplastpcs()
{
    if(explib_root.mode & RootBase::NOLOG)
        return;     // log forbidden

 ushort dumpidx = logidx;
 ushort i;
 PCEntry*   pce;
 const  ushort nlist = min(100, ENTRIES);

    xlr.logmsg("\t/--- these checkpoints were passed before: ---\n");

    for(i=0; i<nlist; i++)
    {
        dumpidx = (dumpidx - 1) & INDEXMASK;

        pce = &pclog[dumpidx];

        if(pce->seqno == -1)    break;  // no more valid entries: stop

        xlr.logmsg("\t| %s line %d\n",
            pce->source ? pce->source : "<unknown>",
            pce->line & ~(1UL<<31) );
    }

    if(!i)  xlr.logmsg("\t| [no entries available]\n");

    xlr.logmsg("\t\\---------------------------------------------\n");
}

void FloatControl::logpcbig(

        char*   source,
        int     line,

        ulong   parm0,
        ulong   parm1,
        ulong   parm2,
        ulong   parm3

        )
{
    logidx++;

    PCEntry*    pce = &pclog[logidx &= INDEXMASK];

    if(!explib_root.alive())
    {
#ifdef  AMIGA
        if(stdout)
            printf("WARNING 2003941224 [%s, %d]: Root not yet initialized\n",
                source, line);
#endif

        return;
    }

    pce->source     = source;
    pce->line       = (ulong)line | (1UL << 31);    // flag: has user parms
    pce->seqno      = seqcnt++;

    pce->parm[0]    = parm0;
    pce->parm[1]    = parm1;
    pce->parm[2]    = parm2;
    pce->parm[3]    = parm3;
}

/*   _____________________________________________________________
    /                                                             \ 
    | delivering an error string for a 'rcode'
    \_____________________________________________________________/

  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 940202 1.00    JTH created
*/

static const char *ExpLib_ErrStr[] = {  // 'static' == local scope

    "<NO ERROR>",

    "FUNCTION FAILED",
    "OUT OF MEMORY",
    "CANNOT OPEN FILE",
    "I/O ERROR",
    "INVALID INPUT DATA",
    "DATA NOT AVAILABLE",
    "END OF DATA",
    "INTERNAL PROGRAM ERROR",
    "SPECIAL ERROR",
    "OVERFLOW ERROR"

    };

const char* errstr(rcode r)
{
    return  ExpLib_ErrStr[(int)r];
}
