/*
 * $Id: pools.c 1.19 1998/04/16 11:04:43 olsen Exp olsen $
 *
 * :ts=4
 *
 * Wipeout -- Traces and munges memory and detects memory trashing
 *
 * Written by Olaf `Olsen' Barthel <olsen@sourcery.han.de>
 * Public Domain
 */

#ifndef _GLOBAL_H
#include "global.h"
#endif	/* _GLOBAL_H */

/******************************************************************************/

#include "installpatches.h"

/******************************************************************************/

STATIC struct MinList PoolList;

STATIC struct SignalSemaphore DummySemaphore;

/******************************************************************************/

VOID
SetupPoolList(VOID)
{
	/* set up the memory pool registry */
	NewList((struct List *)&PoolList);

	/* initialize the dummy semaphore */
	InitSemaphore(&DummySemaphore);
}

/******************************************************************************/

VOID
HoldPoolSemaphore(struct PoolHeader * ph,ULONG pc)
{
	/* hold the pool semaphore for an instant, breaking
	 * the Forbid() state
	 */
	ObtainSemaphore(&DummySemaphore);
	ReleaseSemaphore(&DummySemaphore);

	/* try to gain control over the pool; if this fails,
	 * somebody else must be mucking with pool at the
	 * moment, which is not such a good idea
	 */
	if(CANNOT AttemptSemaphore(&ph->ph_SignalSemaphore))
	{
		ULONG segment;
		ULONG offset;

		VoiceComplaint(NULL,NULL,"Two tasks are accessing the same pool at the same time\n");

		DPrintf("   Already being used by task 0x%08lx",ph->ph_PoolOwner);

		if(GetTaskName(ph->ph_PoolOwner,GlobalNameBuffer,sizeof(GlobalNameBuffer)))
		{
			DPrintf(", %s \"%s\"",GetTaskTypeName(GetTaskType(ph->ph_PoolOwner)),GlobalNameBuffer);
		}

		DPrintf("\n");

		if(FindAddress(ph->ph_PoolOwnerPC,sizeof(GlobalNameBuffer),GlobalNameBuffer,&segment,&offset))
		{
			DPrintf("                         from \"%s\" Hunk %04lx Offset %08lx\n",GlobalNameBuffer,segment,offset);
		}

		DPrintf("Attempting to be used by task 0x%08lx",FindTask(NULL));

		if(GetTaskName(NULL,GlobalNameBuffer,sizeof(GlobalNameBuffer)))
		{
			DPrintf(", %s \"%s\"",GetTaskTypeName(GetTaskType(NULL)),GlobalNameBuffer);
		}

		DPrintf("\n");

		if(FindAddress(NULL,sizeof(GlobalNameBuffer),GlobalNameBuffer,&segment,&offset))
		{
			DPrintf("                      from \"%s\" Hunk %04lx Offset %08lx\n",GlobalNameBuffer,segment,offset);
		}

		/* wait for the other guy to release the pool */
		ObtainSemaphore(&ph->ph_SignalSemaphore);
	}

	/* register the new owner */
	ph->ph_PoolOwner	= FindTask(NULL);
	ph->ph_PoolOwnerPC	= pc;
}

VOID
ReleasePoolSemaphore(struct PoolHeader * ph)
{
	ReleaseSemaphore(&ph->ph_SignalSemaphore);
}

/******************************************************************************/

BOOL
PuddleIsInPool(struct PoolHeader * ph,APTR mem)
{
	BOOL found = FALSE;

	if(IsPuddleListConsistent(ph))
	{
		struct TrackHeader * th;

		/* check whether the given allocation went into the
		 * given pool
		 */
		for(th = (struct TrackHeader *)ph->ph_Puddles.mlh_Head ;
		    th->th_MinNode.mln_Succ != NULL ;
		    th = (struct TrackHeader *)th->th_MinNode.mln_Succ)
		{
			UBYTE * thatMem;
	
			thatMem = (UBYTE *)(th + 1);
			thatMem += PreWallSize;
	
			if(thatMem == mem)
			{
				found = TRUE;
				break;
			}
		}
	}

	return(found);
}

/******************************************************************************/

VOID
RemovePuddle(struct TrackHeader * th)
{
	/* unregister a puddle from a pool */
	Remove((struct Node *)th);
	th->th_Magic = 0;
}

VOID
AddPuddle(struct PoolHeader * ph,struct TrackHeader * th)
{
	/* register a puddle in a pool */
	AddTail((struct List *)&ph->ph_Puddles,(struct Node *)th);
}

/******************************************************************************/

struct PoolHeader *
FindPoolHeader(APTR poolHeader)
{
	struct PoolHeader * result = NULL;
	struct PoolHeader * node;

	/* determine the pool header being tracked, if there
	 * is one
	 */
	for(node = (struct PoolHeader *)PoolList.mlh_Head ;
	    node->ph_MinNode.mln_Succ != NULL ;
	    node = (struct PoolHeader *)node->ph_MinNode.mln_Succ)
	{
		if(node->ph_PoolHeader == poolHeader)
		{
			result = node;
			break;
		}
	}

	return(result);
}

/******************************************************************************/

BOOL
DeletePoolHeader(ULONG * stackFrame,struct PoolHeader * ph)
{
	struct TrackHeader * th;
	BOOL deleteIt = TRUE;

	/* In any event, remove the pool from the public list.
	 * It is going to be deleted or let go in a minute.
	 */
	Remove((struct Node *)ph);

	/* gain exclusive access to the pool */
	HoldPoolSemaphore(ph,stackFrame[16]);
	ReleasePoolSemaphore(ph);

	if(IsPuddleListConsistent(ph))
	{
		for(th = (struct TrackHeader *)ph->ph_Puddles.mlh_Head ;
		    th->th_MinNode.mln_Succ != NULL ;
		    th = (struct TrackHeader *)th->th_MinNode.mln_Succ)
		{
			/* Check whether this pool has dead allocations.
			 * If so, do not deallocate it.
			 */
			if(th->th_Magic == 0)
			{
				deleteIt = FALSE;
			}
			else
			{
				/* Check whether this puddle has been
				 * stomped upon. If so, do not free the
				 * pool.
				 */
				if(CheckStomping(stackFrame,th))
				{
					deleteIt = FALSE;
				}

				/* Will be dead food in a minute. */
				th->th_Magic = 0;
			}
		}

		/* if we can proceed to deallocate the
		 * pool, trash all its allocations
		 */
		if(deleteIt)
		{
			struct TrackHeader * node;
			struct TrackHeader * next;
			LONG allocationSize;

			for(node = (struct TrackHeader *)ph->ph_Puddles.mlh_Head ;
			   (next = (struct TrackHeader *)node->th_MinNode.mln_Succ) != NULL ;
			    node = next)
			{
				allocationSize = node->th_NameTagLen + sizeof(*node) + PreWallSize + node->th_Size + node->th_PostSize;
				if(node->th_NameTagLen > 0)
				{
					node = (struct TrackHeader *)(((ULONG)node) - node->th_NameTagLen);
				}

				MungMem((ULONG *)node,allocationSize,DEADBEEF);
			}
		}
	}

	if(NOT ph->ph_Consistent)
	{
		deleteIt = FALSE;
	}

	if(deleteIt)
	{
		APTR poolHeader;
		ULONG * mem;
		LONG allocationSize;

		mem = (ULONG *)(((ULONG)ph) - ph->ph_NameTagLen);
		allocationSize = ph->ph_NameTagLen + sizeof(*ph);

		poolHeader = ph->ph_PoolHeader;

		MungMem(mem,allocationSize,DEADBEEF);

		(*OldDeletePool)(poolHeader,SysBase);
	}
	else
	{
		DumpPoolOwner(ph);
	}

	return(deleteIt);
}

struct PoolHeader *
CreatePoolHeader(ULONG attributes,ULONG puddleSize,ULONG threshSize,ULONG pc)
{
	struct PoolHeader * result = NULL;
	APTR header;

	/* create the pool with the MEMF_CLEAR bit cleared; if there is
	 * any clearing to be done, we want to do it on our own
	 */
	header = (*OldCreatePool)(attributes & (~MEMF_CLEAR),puddleSize,threshSize,SysBase);
	if(header != NULL)
	{
		struct PoolHeader * ph;
		LONG nameTagLen;

		nameTagLen = 0;

		/* get the name of the current task, if this is necessary */
		if(NameTag)
		{
			nameTagLen = GetNameTagLen(pc);
		}
	
		ph = (*OldAllocPooled)(header,nameTagLen + sizeof(*ph),SysBase);
		if(ph != NULL)
		{
			/* the allocation above may have broken a Forbid(), so we
			 * retry reading the creator information
			 */
			if(NameTag)
			{
				LONG newNameTagLen;

				newNameTagLen = GetNameTagLen(pc);

				/* the data should not be different, but if it is
				 * (unlikely), don't bother to tag this header
				 */
				if(newNameTagLen != nameTagLen)
				{
					nameTagLen = 0;
				}
			}

			/* the name tag header precedes the pool header */
			if(nameTagLen > 0)
			{
				FillNameTag(ph,nameTagLen);

				/* skip the name tag header */
				ph = (struct PoolHeader *)(((ULONG)ph) + nameTagLen);
			}

			/* fill in the usual header data */
			ph->ph_PoolHeader	= header;
			ph->ph_PC			= pc;
			ph->ph_Owner		= FindTask(NULL);
			ph->ph_OwnerType	= GetTaskType(NULL);
			ph->ph_NameTagLen	= nameTagLen;
			ph->ph_Consistent	= TRUE;

			GetSysTime(&ph->ph_Time);
	
			ph->ph_Attributes = attributes;
			NewList((struct List *)&ph->ph_Puddles);
	
			InitSemaphore(&ph->ph_SignalSemaphore);
			ph->ph_PoolOwner = NULL;

			AddTail((struct List *)&PoolList,(struct Node *)ph);

			result = ph;
		}
		else
		{
			(*OldDeletePool)(header,SysBase);
		}
	}

	return(result);
}

/******************************************************************************/

VOID
CheckPools(VOID)
{
	struct PoolHeader * ph;
	struct TrackHeader * th;
	ULONG totalBytes;
	ULONG totalAllocations;
	ULONG totalPools;
	BOOL poolOwnerDumped;

	totalBytes = 0;
	totalAllocations = 0;
	totalPools = 0;

	Forbid();

	/* check all registered pools */
	for(ph = (struct PoolHeader *)PoolList.mlh_Head ;
	    ph->ph_MinNode.mln_Succ != NULL ;
	    ph = (struct PoolHeader *)ph->ph_MinNode.mln_Succ)
	{
		poolOwnerDumped = FALSE;

		if(IsPuddleListConsistent(ph))
		{
			/* check all allocations in this pool */
			for(th = (struct TrackHeader *)ph->ph_Puddles.mlh_Head ;
			    th->th_MinNode.mln_Succ != NULL ;
			    th = (struct TrackHeader *)th->th_MinNode.mln_Succ)
			{
				/* Check whether the header data is consistent. */
				if(IsValidTrackHeader(th) && IsTrackHeaderChecksumCorrect(th))
				{
					/* Don't count dead allocations. */
					if(th->th_Magic != 0)
					{
						/* check if the allocation was trashed */
						if(CheckStomping(NULL,th))
						{
							/* if the allocation was trashed,
							 * show the pool it belongs to
							 */
							if(NOT poolOwnerDumped)
							{
								DumpPoolOwner(ph);
		
								poolOwnerDumped = TRUE;
							}
						}
		
						totalBytes += th->th_Size;
						totalAllocations++;
					}
				}
			}
		}

		/* check whether the creator of this pool
		 * is still with us
		 */
		if(NOT IsTaskStillAround(ph->ph_Owner))
		{
			VoiceComplaint(NULL,NULL,"Orphaned pool?\n");
			DumpPoolOwner(ph);
		}

		totalPools++;
	}

	Permit();

	DPrintf("%ld byte(s) in %ld allocation(s) in %ld pool(s).\n",totalBytes,totalAllocations,totalPools);
}

/******************************************************************************/

VOID
ShowUnmarkedPools(VOID)
{
	struct PoolHeader * ph;
	struct TrackHeader * th;
	ULONG totalBytes;
	ULONG totalAllocations;
	ULONG totalPools;

	/* Show and count all pooled memory allocations. */

	totalBytes = 0;
	totalAllocations = 0;
	totalPools = 0;

	Forbid();

	/* check all registered pools */
	for(ph = (struct PoolHeader *)PoolList.mlh_Head ;
	    ph->ph_MinNode.mln_Succ != NULL ;
	    ph = (struct PoolHeader *)ph->ph_MinNode.mln_Succ)
	{
		if(IsPuddleListConsistent(ph))
		{
			/* check all allocations in this pool */
			for(th = (struct TrackHeader *)ph->ph_Puddles.mlh_Head ;
			    th->th_MinNode.mln_Succ != NULL ;
			    th = (struct TrackHeader *)th->th_MinNode.mln_Succ)
			{
				/* Don't count dead allocations. */
				if(th->th_Magic != 0)
				{
					if(NOT th->th_Marked)
					{
						VoiceComplaint(NULL,th,NULL);
					}
	
					totalBytes += th->th_Size;
					totalAllocations++;
				}
			}
		}

		totalPools++;
	}

	Permit();

	DPrintf("%ld byte(s) in %ld allocation(s) in %ld pool(s).\n",totalBytes,totalAllocations,totalPools);
}

/******************************************************************************/

VOID
ChangePuddleMarks(BOOL markSet)
{
	struct PoolHeader * ph;
	struct TrackHeader * th;

	/* Mark or unmark all memory puddles. */

	Forbid();

	for(ph = (struct PoolHeader *)PoolList.mlh_Head ;
	    ph->ph_MinNode.mln_Succ != NULL ;
	    ph = (struct PoolHeader *)ph->ph_MinNode.mln_Succ)
	{
		if(IsPuddleListConsistent(ph))
		{
			for(th = (struct TrackHeader *)ph->ph_Puddles.mlh_Head ;
			    th->th_MinNode.mln_Succ != NULL ;
			    th = (struct TrackHeader *)th->th_MinNode.mln_Succ)
			{
				/* Ignore dead allocations. */
				if(th->th_Magic != 0)
				{
					th->th_Marked = markSet;
	
					/* Repair the checksum value. */
					FixTrackHeaderChecksum(th);
				}
			}
		}
	}

	Permit();
}

/******************************************************************************/

BOOL
IsPuddleListConsistent(struct PoolHeader * ph)
{
	BOOL isConsistent = TRUE;

	Forbid();

	if(NOT IsMemoryListConsistent(&ph->ph_Puddles))
	{
		ph->ph_Consistent = FALSE;

		isConsistent = FALSE;

		NewList((struct List *)&ph->ph_Puddles);
	}

	Permit();

	return(isConsistent);
}
