/*
 * $Id: pools.c 1.14 1998/04/13 08:33:27 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");

		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)
{
	struct TrackHeader * th;
	BOOL found = FALSE;

	/* 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 PoolHeader * ph,struct TrackHeader * th)
{
	/* unregister a puddle from a pool */
	Remove((struct Node *)th);
}

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);

	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(deleteIt)
	{
		(*OldDeletePool)(ph->ph_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 nameLen;

		nameLen = 0;

		/* get the name of the current task, if this is necessary */
		if(NameTag)
		{
			if(GetTaskName(NULL,GlobalNameBuffer,sizeof(GlobalNameBuffer)))
			{
				nameLen = ((strlen(GlobalNameBuffer)+1) + 3) & ~3;
			}
		}
	
		ph = (*OldAllocPooled)(header,sizeof(*ph) + nameLen,SysBase);
		if(ph != NULL)
		{
			/* 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_NameLen		= nameLen;

			/* the name follows the header */
			if(nameLen > 0)
			{
				strcpy((char *)(ph + 1),GlobalNameBuffer);
			}

			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;

		/* 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)
			{
				/* 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?");
			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)
	{
		/* 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)
	{
		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;
			}
		}
	}

	Permit();
}
