/* tracking memory allocator */

#include <exec/types.h>
#include <exec/memory.h>
#include <functions.h>
#include <stdio.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
#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;
{
	register 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)) == NULL)
			panic("tracker: can't alloc memory to record AllocMem data");

		/* 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;
{
	register 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->next)
	{
		/* 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'ed %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\tallocated in file %s at line %d, freed in file %s at line %d\n",where,amount,rp->file,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
			FreeMem(where,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",MemAllocCount,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("freed a signal (%ld) that was never allocated, at file %s line %d\n",
			signal_num,file,line);
		TrackingSignalMask &= ~(1 << signal_num);
	}
}

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

/* tracking lock and unlock */

struct TrackingLockData
{
	struct FileLock *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;

struct FileLock *TrackingLock(name, accessMode, file, line)
char *name;
long accessMode;
char *file;
int line;
{
	register struct TrackingLockData *lp;
	struct FileLock *users_lock;

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

	if (users_lock)
	{
		TrackerLockCount++;

		if ((lp = AllocMem((long)sizeof(struct TrackingLockData),0L)) == NULL)
			panic("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)
			panic("tracker: can't alloc memory to record lock filename");
		strcpy(lp->name,name);
	}
	return(users_lock);
}

struct FileLock *TrackingDupLock(lock, file, line)
struct FileLock *lock;
char *file;
int line;
{
	register struct TrackingLockData *lp;
	struct FileLock *users_lock;

	users_lock = DupLock(lock);

	if (users_lock)
	{
		TrackerLockCount++;

		if ((lp = AllocMem((long)sizeof(struct TrackingLockData),0L)) == NULL)
			panic("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 TrackingUnLock(lock,file,line)
struct FileLock *lock;
char *file;
int line;
{
	register 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\tlocked at file %s line %d, freed at file %s line %d\n",lock,lp->name,lp->file,lp->line,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);
}

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->line);
			}
		}
		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",TrackerLockCount,TrackerUnLockCount);
}

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

