/* Resource tracking routines, by Karl Lehenbauer
   Lattice version by David Gay.
   This code is in the public domain
*/

/* tracking memory allocator */

#include <exec/types.h>
#include <exec/memory.h>
#include <stdio.h>
#include <string.h>

#include <proto/exec.h>
#include <proto/dos.h>

/* comment out the following line if you want locks freed twice reported
 * at the cost of getting spurious resource error messages when
 * reusing the lock */
/* #define FORGET_LOCKS_WHEN_UNLOCKED */

/* comment out the following line if you want memory freed twice reported
 * at the cost of getting spurious resource error messages when
 * freeing and reallocating memory */
#define FORGET_MEMORY_WHEN_FREED

/* make sure our invocations of the real routines on behalf of the user
   don't cause us to recurse */

#ifdef AllocMem
#undef AllocMem
#undef FreeMem
#undef AllocSignal
#undef FreeSignal
#undef Lock
#undef UnLock
#undef DupLock
#undef ParentDir
#endif

/* my flags */
#define FREED_IT 1

struct TrackingAllocMemData
{
    UBYTE *where;             /* address returned by allocator */
    long amount;             /* number of bytes allocated */
    long alloc_flags;         /* flags passed to allocator */
    char *file;         /* filename of caller from the macro */
    int line;                 /* line number of caller from macro */
    long my_flags;             /* flags internal to tracker */
    struct TrackingAllocMemData *next;    /* pointer to next entry */
};

struct TrackingAllocMemData *TrackingAllocMemList = NULL;
long MemAllocCount = 0, MemFreeCount = 0;

void *TrackingAllocMem(amount,flags,file,line)
long amount;
long flags;
char *file;
int line;
{
    struct TrackingAllocMemData *rp;
    UBYTE *users_memory;

    /* perform the actual alloc */
    users_memory = AllocMem(amount,flags);

    /* if it succeeded, record tracking info */
    if (users_memory)
    {
         MemAllocCount++;

         if ((rp = AllocMem((long)sizeof(struct TrackingAllocMemData),0L)) == N
ULL)
             fprintf(stderr, "tracker: can't alloc memory to record AllocMem da
ta");

         /* add new alloc data entry to linked list */
         rp->next = TrackingAllocMemList;
         TrackingAllocMemList = rp;

         /* shove in save values */
         rp->amount = amount;
         rp->alloc_flags = flags;
         rp->where = users_memory;
         rp->file = file;
         rp->line = line;
         rp->my_flags = 0;
    }
    /* return pointer to the space allocated */
    return(users_memory);
}

void TrackingFreeMem(where,amount,file,line)
UBYTE *where;
long amount;
char *file;
int line;
{
    struct TrackingAllocMemData *rp, *op, *freep;

    MemFreeCount++;
    /* scan the memory tracking list for a match */
    for (rp = TrackingAllocMemList, op = NULL; rp != NULL; op = rp, rp = rp->ne
xt)
    {
         /* if we matched the address */
         if (rp->where == where)
         {
             /* if they got the amount wrong, tell them */
             if (rp->amount != amount)
             {
                 fprintf(stderr,"freed addr %lx OK but length differs, talloc'e
d %ld, freed %ld,\n\tallocated at file %s line %d, freed at file %s line %d\n",
                 where,rp->amount,amount,rp->file,rp->line,file,line);
             }
#ifndef FORGET_MEMORY_WHEN_FREED
             /* if it's already free, tell them they freed twice */
             if (rp->my_flags & FREED_IT)
             {
                 fprintf(stderr,"freed memory twice at %lx, amount %ld,\n\tallo
cated in file %s at line %d, freed in file %s at line %d\n",where,amount,rp->fil
e,rp->line,file,line);
                 return;
             }
             else
             {
                 /* mark this entry as free */
                 rp->my_flags |= FREED_IT;
             }
#else
             /* remove entry from linked list and free it */
             if (op != NULL) op->next = rp->next;
             else
                 TrackingAllocMemList = rp->next;
             freep = rp;
             rp = rp->next;
             FreeMem(freep,(long)sizeof(struct TrackingAllocMemData));
#endif
             /* Munge memory block */
             memset((char *)where, amount, 3); /* Set to an odd number ... */
             FreeMem(where,(long)amount);

             return;
         }
    }
    fprintf(stderr,"Freed memory at %lx of amount %ld that wasn't allocated,\n\
tfreed at file %s line %d\n",where,amount,file,line);
    FreeMem(where,amount);
}

void ReportUnfreedMemory()
{
    struct TrackingAllocMemData *rp = TrackingAllocMemList, *freep;

    while (rp != NULL)
    {
         if (!(rp->my_flags & FREED_IT))
         {
             fprintf(stderr,"FreeMem was never called for memory at %lx, amount
 %ld,\n\tthe alloc was performed at file %s line %d\n",rp->where,rp->amount,rp->
file,rp->line);
         }
         freep = rp;
         rp = rp->next;
         FreeMem(freep,(long)sizeof(struct TrackingAllocMemData));
    }
    printf("Total tracked AllocMem calls %ld, FreeMem calls %ld\n",MemAllocCoun
t,MemFreeCount);
}


/* track signals */
/* tracking AllocSignal doesn't currently track where it was called from */

long TrackingSignalMask = 0;
long SignalAllocCount = 0, SignalFreeCount = 0;

long TrackingAllocSignal(signal_num,file,line)
long signal_num;
char *file;
int line;
{
    SignalAllocCount++;

    signal_num = AllocSignal(signal_num);

    if (signal_num != -1)
         TrackingSignalMask |= (1 << signal_num);

    return(signal_num);
}

void TrackingFreeSignal(signal_num,file,line)
long signal_num;
char *file;
int line;
{
    SignalFreeCount++;

    if (!(TrackingSignalMask & (1 << signal_num)))
    {
         fprintf(stderr, "freed a signal (%ld) that was never allocated, at fil
e %s line %d\n",
             signal_num,file,line);
    }
    TrackingSignalMask &= ~(1 << signal_num);
}

void ReportUnfreedSignals()
{
    if (TrackingSignalMask)
         fprintf(stderr, "failed to free signals indicated by this mask: %8lx\n
",
             TrackingSignalMask);
    printf("Total tracked AllocSignal calls %ld, FreeSignal calls %ld\n",Signal
AllocCount,SignalFreeCount);
}

/* tracking lock and unlock */

struct TrackingLockData
{
    BPTR lock;    /* lock returned by Lock */
    char *name;         /* name of file that was locked */
    long accessMode;         /* access mode of the file that was locked */
    char *file;         /* ptr to file name of line of caller */
    int line;                 /* ptr to line number text of locker */
    long my_flags;             /* flags internal to tracker */
    struct TrackingLockData *next;    /* pointer to next entry */
};

/* flags */
#define CREATED_BY_DUPLOCK 1

struct TrackingLockData *TrackingLockList = NULL;
long TrackerLockCount = 0, TrackerUnLockCount = 0;

BPTR TheTrackingLock(name, accessMode, file, line)
char *name;
long accessMode;
char *file;
int line;
{
    struct TrackingLockData *lp;
    BPTR users_lock;

    users_lock = Lock(name, (long)accessMode);

    if (users_lock)
    {
         TrackerLockCount++;

         if ((lp = AllocMem((long)sizeof(struct TrackingLockData),0L)) == NULL)
     
             fprintf(stderr, "tracker: can't alloc memory to record lock data")
;

         /* add new alloc data entry to linked list */
         lp->next = TrackingLockList;
         TrackingLockList = lp;

         /* shove in save values */
         lp->accessMode = accessMode;
         lp->file = file;
         lp->line = line;
         lp->my_flags = 0;
         lp->lock = users_lock;

         /* alloc space for filename and save */
         if ((lp->name = AllocMem((long)(strlen(name)+1),0L)) == NULL)
             fprintf(stderr, "tracker: can't alloc memory to record lock filena
me");
         strcpy(lp->name,name);
    }
    return(users_lock);
}

BPTR TheTrackingDupLock(lock, file, line)
BPTR lock;
char *file;
int line;
{
    struct TrackingLockData *lp;
    BPTR users_lock;

    users_lock = DupLock(lock);

    if (users_lock)
    {
         TrackerLockCount++;

         if ((lp = AllocMem((long)sizeof(struct TrackingLockData),0L)) == NULL)
     
             fprintf(stderr, "tracker: can't alloc memory to record lock data")
;

         /* add new alloc data entry to linked list */
         lp->next = TrackingLockList;
         TrackingLockList = lp;

         lp->file = file;
         lp->line = line;
         lp->name = NULL;
         lp->lock = users_lock;
         lp->my_flags = CREATED_BY_DUPLOCK;
    }
    return(users_lock);
}

void TheTrackingUnLock(lock,file,line)
BPTR lock;
char *file;
int line;
{
    struct TrackingLockData *lp, *op, *freep;

    TrackerUnLockCount++;

    /* scan the lock tracking list for a match */
    for (lp = TrackingLockList, op = NULL; lp != NULL; op = lp, lp = lp->next)
    {
         /* if we matched the lock */
         if (lp->lock == lock)
         {
#ifndef FORGET_LOCKS_WHEN_UNLOCKED
             /* if it's already free, tell them they freed twice */
             if (lp->my_flags & FREED_IT)
             {
                 fprintf(stderr,"freed lock twice, lock %lx, filename %s\n\tloc
ked at file %s line %d, freed at file %s line %d\n",lock,lp->name,lp->file,lp->l
ine,file,line);
                 return;
             }
             else
             {
                 /* mark this entry as free */
                 lp->my_flags |= FREED_IT;
             }
#else
             if (op != NULL) op->next = lp->next;
             else TrackingLockList = lp->next;
             freep = lp;
             lp = lp->next;
             if (lp->name != NULL)
                 FreeMem(lp->name,(long)(strlen(lp->name)+1));
             FreeMem(freep,(long)(sizeof(struct TrackingLockData)));
#endif
             UnLock(lock);
             return;
         }
    }
    fprintf(stderr,"Freed lock %lx that hadn't been allocated at file %s line %
d\n",lock,file,line);
}

BPTR TheTrackingParentDir(BPTR lock, char *file, int line)
{
    struct TrackingLockData *lp;
    BPTR users_lock;

    users_lock = ParentDir(lock);

    if (users_lock)
    {
         TrackerLockCount++;

         if ((lp = AllocMem((long)sizeof(struct TrackingLockData),0L)) == NULL)
     
             fprintf(stderr, "tracker: can't alloc memory to record lock data")
;

         /* add new alloc data entry to linked list */
         lp->next = TrackingLockList;
         TrackingLockList = lp;

         /* shove in save values */
         lp->accessMode = 0;
         lp->file = file;
         lp->line = line;
         lp->my_flags = 0;
         lp->lock = users_lock;
         lp->name = NULL;
    }
    return(users_lock);
}

void ReportUnfreedLocks()
{
    struct TrackingLockData *lp = TrackingLockList, *freep;

    while (lp != NULL)
    {
         if (!(lp->my_flags & FREED_IT))
         {
             if (lp->my_flags & CREATED_BY_DUPLOCK)
             {
                 fprintf(stderr,"UnLock was never called for lock %lx,\n\It was
 created by DupLock at file %s line %d\n",lp->lock,lp->file,lp->line);
             }
             else
             {
                 fprintf(stderr,"UnLock was never called for lock %lx,\n\It was
 created by a Lock of %s\nat file %s line %d\n",lp->lock,lp->name,lp->file,lp->l
ine);
             }
         }
         if (lp->name != NULL)
             FreeMem(lp->name,(long)(strlen(lp->name)+1));
         freep = lp;
         lp = lp->next;
         FreeMem(freep,(long)sizeof(struct TrackingLockData));
    }
    printf("Total tracked Lock and DupLock calls %ld, UnLock calls %ld\n",Track
erLockCount,TrackerUnLockCount);
}

void TrackerExitReport()
{
    ReportUnfreedMemory();
    ReportUnfreedLocks();
    ReportUnfreedSignals();
}
