/*
 * $Id: main.c 1.13 1998/04/13 09:51:04 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 <stdio.h>

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

#include "Wipeout_rev.h"

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

const STRPTR VersTag = VERSTAG;

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

STATIC const STRPTR Separator = "======================================="
                                "=======================================";

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

STATIC struct WipeoutSemaphore *	WipeoutSemaphore;
STATIC BOOL							WipeoutSemaphoreCreated;

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

STATIC BOOL ShowBannerMessage = TRUE;

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

STATIC BYTE TimerSignal;

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

STATIC LONG
SendWipeoutCmd(LONG command,APTR parameter)
{
	WipeoutSemaphore->ws_Client		= FindTask(NULL);
	WipeoutSemaphore->ws_Command	= command;
	WipeoutSemaphore->ws_Parameter	= parameter;
	WipeoutSemaphore->ws_Error		= OK;

	/* wake up the wipeout semaphore owner and exchange
	 * data or information with it
	 */
	SetSignal(0,SIG_Handshake);
	Signal(WipeoutSemaphore->ws_WipeoutTask,WipeoutSemaphore->ws_WakeupMask);
	Wait(SIG_Handshake);

	return(WipeoutSemaphore->ws_Error);
}

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

VOID
Cleanup(VOID)
{
	/* shut down the timer */
	DeleteTimer();

	if(WipeoutSemaphore != NULL)
	{
		if(WipeoutSemaphoreCreated)
		{
			/* remove the semaphore from the public list */
			RemSemaphore((struct SignalSemaphore *)WipeoutSemaphore);

			/* gain ownership over it */
			ObtainSemaphore((struct SignalSemaphore *)WipeoutSemaphore);
			ReleaseSemaphore((struct SignalSemaphore *)WipeoutSemaphore);

			/* and release it */
			FreeMem(WipeoutSemaphore,sizeof(*WipeoutSemaphore));
		}
		else
		{
			/* drop the ownership */
			ReleaseSemaphore((struct SignalSemaphore *)WipeoutSemaphore);
		}

		WipeoutSemaphore = NULL;
	}

	/* clear the allocation filters */
	ClearFilterList();

	/* get rid of the wakeup signal */
	if(WakeupSignal != -1)
	{
		FreeSignal(WakeupSignal);
		WakeupSignal = -1;
	}

	/* close utility.library, if this is necessary */
	#if !defined(__SASC) || defined(_M68020)
	{
		if(UtilityBase != NULL)
		{
			CloseLibrary(UtilityBase);
			UtilityBase = NULL;
		}
	}
	#endif
}

BOOL
Setup(VOID)
{
	struct WipeoutSemaphore * ws = NULL;
	LONG error = OK;
	int i;

	WakeupSignal = -1;
	InitFilterList();

	/* determine the program name */
	StrcpyN(sizeof(ProgramName),ProgramName,VERS);

	for(i = strlen(ProgramName) - 1 ; i >= 0 ; i--)
	{
		if(ProgramName[i] == ' ')
		{
			ProgramName[i] = '\0';
			break;
		}
	}

	/* fill in the name and the version number */
	SPrintfN(sizeof(ProgramNameAndVersion),ProgramNameAndVersion,"%s (%s)",VERS,DATE);

	/* Kickstart 2.04 or higher required */
	if(SysBase->LibNode.lib_Version < 37)
	{
		const STRPTR message = "This program requires Kickstart 2.04 or better.\n";

		Write(Output(),message,strlen(message));
		return(FAILURE);
	}

	/* open utility.library, if this is necessary */
	#if !defined(__SASC) || defined(_M68020)
	{
		UtilityBase = OpenLibrary("utility.library",37);
		if(UtilityBase == NULL)
		{
			Printf("%s: Could not open utility.library V37.\n",ProgramName);
			return(FAILURE);
		}
	}
	#endif

	/* allocate the timer data */
	TimerSignal = CreateTimer();
	if(TimerSignal == -1)
	{
		Printf("%s: Could not create timer.\n",ProgramName);
		return(FAILURE);
	}

	/* allocate the wakeup signal */
	WakeupSignal = AllocSignal(-1);
	if(WakeupSignal == -1)
	{
		Printf("%s: Could not allocate wakeup signal.\n",ProgramName);
		return(FAILURE);
	}

	Forbid();

	/* try to find the global wipeout semaphore */
	WipeoutSemaphore = (struct WipeoutSemaphore *)FindSemaphore(WIPEOUTSEMAPHORENAME);
	if(WipeoutSemaphore == NULL)
	{
		/* it does not exist yet; create it */
		WipeoutSemaphore = AllocMem(sizeof(*WipeoutSemaphore),MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR);
		if(WipeoutSemaphore != NULL)
		{
			WipeoutSemaphoreCreated = TRUE;

			ws = WipeoutSemaphore;

			/* fill in name and priority; AddSemaphore() will take
			 * care of the rest
			  */
			ws->ws_SignalSemaphore.ss_Link.ln_Name	= ws->ws_SemaphoreName;
			ws->ws_SignalSemaphore.ss_Link.ln_Pri	= 1;

			strcpy(ws->ws_SemaphoreName,WIPEOUTSEMAPHORENAME);

			/* for compatibility checking, if the semaphore
			 * needs to grow
			 */
			ws->ws_Version = WIPEOUTSEMAPHOREVERSION;

			/* fill in references to changeable parameters */
			ws->ws_IsActive		= &IsActive;
			ws->ws_ShowFail		= &ShowFail;
			ws->ws_WaitAfterHit	= &WaitAfterHit;
			ws->ws_NameTag		= &NameTag;
			ws->ws_NoReuse		= &NoReuse;
			ws->ws_ARegCheck	= &ARegCheck;
			ws->ws_DRegCheck	= &DRegCheck;
			ws->ws_StackCheck	= &StackCheck;
			ws->ws_StackLines	= &StackLines;
			ws->ws_CheckDelay	= &CheckDelay;
			ws->ws_WipeoutTask	= FindTask(NULL);
			ws->ws_WakeupMask	= 1UL << WakeupSignal;

			/* and finally make the semaphore public */
			AddSemaphore((struct SignalSemaphore *)ws);
		}
		else
		{
			error = ERROR_NO_FREE_STORE;
		}
	}
	else
	{
		ws = WipeoutSemaphore;

		/* obtain ownership of the semaphore */
		ObtainSemaphore((struct SignalSemaphore *)ws);
	}

	Permit();

	if(error == OK)
	{
		struct RDArgs * rda;

		/* these are the command line parameters, later
		 * filled in by ReadArgs() below
		 */
		struct
		{
			/* the following options control the startup defaults
			 * and cannot be changed by subsequent invocations
			 * of Wipeout
			 */
			NUMBER	PreSize;
			NUMBER	PostSize;
			KEY		FillChar;
			SWITCH	Parallel;
			SWITCH	NoBanner;

			/* the following options can be changed later */
			SWITCH	Remunge;
			SWITCH	Check;
			SWITCH	Mark;
			SWITCH	Unmark;
			SWITCH	ShowUnmarked;
			KEY		Name;
			SWITCH	NameTag;
			SWITCH	NoNameTag;
			SWITCH	Active;
			SWITCH	Inactive;
			SWITCH	Wait;
			SWITCH	NoWait;
			SWITCH	Reuse;
			SWITCH	NoReuse;
			SWITCH	ShowFail;
			SWITCH	NoShowFail;
			SWITCH	ARegCheck;
			SWITCH	NoARegCheck;
			SWITCH	DRegCheck;
			SWITCH	NoDRegCheck;
			SWITCH	StackCheck;
			SWITCH	NoStackCheck;
			NUMBER	StackLines;
			NUMBER	CheckDelay;
		} params;
	
		/* this is the command template, as required by ReadArgs() below;
		 * its contents must match the "params" data structure above
		 */
		const STRPTR cmdTemplate =
			"PRESIZE/K/N,"
			"POSTSIZE/K/N,"
			"FILLCHAR/K,"
			"PARALLEL/S,"
			"NOBANNER/S,"
			"REMUNGE/S,"
			"CHECK/S,"
			"MARK/S,"
			"UNMARK/S,"
			"SHOWUNMARKED/S,"
			"NAME=TASK/K,"
			"NAMETAG/S,"
			"NONAMETAG/S,"
			"ACTIVE=ENABLE/S,"
			"INACTIVE=DISABLE/S,"
			"WAIT/S,"
			"NOWAIT/S,"
			"REUSE/S,"
			"NOREUSE/S,"
			"SHOWFAIL/S,"
			"NOSHOWFAIL/S,"
			"AREGCHECK/S,"
			"NOAREGCHECK/S,"
			"DREGCHECK/S,"
			"NODREGCHECK/S,"
			"STACKCHECK/S,"
			"NOSTACKCHECK/S,"
			"STACKLINES/K/N,"
			"CHECKDELAY/K/N";

		memset(&params,0,sizeof(params));

		/* read the command line parameters */
		rda = ReadArgs((STRPTR)cmdTemplate,(LONG *)&params,NULL);
		if(rda != NULL)
		{
			/* establish defaults */
			PreWallSize		= 32;
			PostWallSize	= 32;
			IsActive		= TRUE;
			StackLines		= 2;

			/* trigger a memory check? */
			if(params.Check)
			{
				Signal(ws->ws_WipeoutTask,SIG_Check);
			}

			/* disable wipeout? */
			if(params.Inactive)
			{
				Signal(ws->ws_WipeoutTask,SIG_Disable);
			}

			/* enable wipeout? */
			if(params.Active)
			{
				Signal(ws->ws_WipeoutTask,SIG_Enable);
			}

			/* remunge memory? */
			if(params.Remunge)
			{
				/* tell the semaphore owner to munge the memory */
				if(NOT WipeoutSemaphoreCreated)
				{
					SendWipeoutCmd(WIPEOUTCMD_Remunge,NULL);
				}
			}

			/* mark all allocations? */
			if(params.Mark)
			{
				/* tell the semaphore owner to mark the allocations */
				if(NOT WipeoutSemaphoreCreated)
				{
					SendWipeoutCmd(WIPEOUTCMD_Mark,NULL);
				}
			}

			/* clear all allocation marks? */
			if(params.Unmark)
			{
				/* tell the semaphore owner to clear the marks */
				if(NOT WipeoutSemaphoreCreated)
				{
					SendWipeoutCmd(WIPEOUTCMD_Unmark,NULL);
				}
			}

			/* show all unmarked allocations? */
			if(params.ShowUnmarked)
			{
				/* tell the semaphore owner to clear the marks */
				if(NOT WipeoutSemaphoreCreated)
				{
					SendWipeoutCmd(WIPEOUTCMD_ShowUnmarked,NULL);
				}
			}

			/* put together a list of tasks to filter out
			 * when making memory allocations?
			 */
			if(params.Name != NULL)
			{
				if(WipeoutSemaphoreCreated)
				{
					/* we created the semaphore, so we can
					 * fill in the filter lists all by
					 * ourselves.
					 */
					if(CANNOT UpdateFilter(params.Name))
						error = ERROR_NO_FREE_STORE;
				}
				else
				{
					/* we did not create the semaphore,
					 * so we will have to tell the semaphore
					 * owner to update the filter lists.
					 */
					error = SendWipeoutCmd(WIPEOUTCMD_UpdateFilterList,params.Name);
				}
			}

			/* set the pre-wall allocation size */
			if(params.PreSize != NULL)
			{
				LONG value;

				value = ((*params.PreSize) + 3) & ~3;
				if(value < 4)
					value = 4;
				else if (value > 65535)
					value = 65535;

				PreWallSize = value;
			}

			/* set the post-wall allocation size */
			if(params.PostSize != NULL)
			{
				LONG value;

				value = ((*params.PostSize) + 3) & ~3;
				if(value < 4)
					value = 4;
				else if (value > 65535)
					value = 65535;

				PostWallSize = value;
			}

			if(params.ARegCheck)
			{
				(*ws->ws_ARegCheck) = TRUE;
			}

			if(params.NoARegCheck)
			{
				(*ws->ws_ARegCheck) = FALSE;
			}

			if(params.DRegCheck)
			{
				(*ws->ws_DRegCheck) = TRUE;
			}

			if(params.NoDRegCheck)
			{
				(*ws->ws_DRegCheck) = FALSE;
			}

			if(params.Wait)
			{
				(*ws->ws_WaitAfterHit) = TRUE;
			}

			if(params.NoWait)
			{
				(*ws->ws_WaitAfterHit) = FALSE;
			}

			if(params.NameTag)
			{
				(*ws->ws_NameTag) = TRUE;
			}

			if(params.NoNameTag)
			{
				(*ws->ws_NameTag) = FALSE;
			}

			if(params.Reuse)
			{
				(*ws->ws_NoReuse) = FALSE;
			}

			if(params.NoReuse)
			{
				(*ws->ws_NoReuse) = TRUE;
			}

			if(params.StackCheck)
			{
				(*ws->ws_StackCheck) = TRUE;
			}

			if(params.NoStackCheck)
			{
				(*ws->ws_StackCheck) = FALSE;
			}

			if(params.StackLines != NULL)
			{
				LONG value;

				value = (*params.StackLines);
				if(value < 1)
					value = 1;

				(*ws->ws_StackLines) = value;
			}

			if(params.ShowFail)
			{
				(*ws->ws_ShowFail) = TRUE;
			}

			if(params.NoShowFail)
			{
				(*ws->ws_ShowFail) = FALSE;
			}

			/* do not show the banner message? */
			if(params.NoBanner)
			{
				ShowBannerMessage = FALSE;
			}

			/* enable parallel port output? */
			if(params.Parallel)
			{
				ChooseParallelOutput();
			}

			/* use a special fill character? */
			if(params.FillChar != NULL)
			{
				LONG number;

				/* this can either be a decimal or a
				 * hexadecimal number; the latter is
				 * indicated by a preceding "$" or "0x"
				 */
				if(DecodeNumber(params.FillChar,&number))
				{
					if(0 <= number && number <= 255)
					{
						SetFillChar(number);
					}
					else
					{
						error = ERROR_BAD_NUMBER;
					}
				}
				else
				{
					error = ERROR_BAD_NUMBER;
				}
			}

			/* set or update the automatic check delay? */
			if(params.CheckDelay != NULL)
			{
				LONG value;

				value = (*params.CheckDelay);
				if(value < 0)
					value = 0;

				CheckDelay = value;

				/* notify the semaphore owner of the
				 * new check delay; this will automatically
				 * trigger a new memory check
				 */
				if(NOT WipeoutSemaphoreCreated)
				{
					error = SendWipeoutCmd(WIPEOUTCMD_NewCheckDelay,(APTR)CheckDelay);
				}
			}

			FreeArgs(rda);
		}
		else
		{
			error = IoErr();
		}
	}

	if(error == OK)
	{
		/* set up the tracking lists */
		SetupAllocationList();
		SetupPoolList();

		return(SUCCESS);
	}
	else
	{
		PrintFault(error,ProgramName);

		return(FAILURE);
	}
}

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

int
main(
	int		argc,
	char **	argv)
{
	int result = RETURN_FAIL;

	/* set up all the data we need */
	if(Setup())
	{
		result = RETURN_OK;

		/* are we the owner of the semaphore? if
		 * so, do something useful
		 */
		if(WipeoutSemaphoreCreated)
		{
			ULONG signalsReceived;
			ULONG sigWakeup = (1UL<<WakeupSignal);
			ULONG sigTimer = (1UL<<TimerSignal);
			struct timeval tv;
    
			/* munge all free memory */
			if(ShowBannerMessage)
			{
				DPrintf("%s -- Traces and munges memory and detects memory trashing\n",ProgramName);
				DPrintf("Written by Olaf `Olsen' Barthel <olsen@sourcery.han.de>\n");
				DPrintf("Public Domain\n\n");

				DPrintf("Munging memory... ");
			}

			BeginMemMung();

			if(ShowBannerMessage)
			{
				Forbid();
				GetSysTime(&tv);
				ConvertTimeAndDate(&tv,GlobalDateBuffer,GlobalTimeBuffer);
				DPrintf("done (%s%s).\n",GlobalDateBuffer,GlobalTimeBuffer);
				Permit();
			}
	
			/* plant the monitoring patches */
			InstallPatches();

			/* if we are to check the memory list periodically,
			 * start the timer now
			 */
			if(CheckDelay > 0)
			{
				StartTimer(CheckDelay / 10,(CheckDelay % 10) * (MILLION / 10));
			}
    
			while(TRUE)
			{
				/* wait for something to happen */
				signalsReceived = Wait(SIG_Check | SIG_Disable | SIG_Enable | sigWakeup | sigTimer);

				/* received a command? */
				if(FLAG_IS_SET(signalsReceived,sigWakeup))
				{
					APTR parameter = WipeoutSemaphore->ws_Parameter;
					LONG error = OK;

					/* what are we to do now? */
					switch(WipeoutSemaphore->ws_Command)
					{
						/* update the filter list? */
						case WIPEOUTCMD_UpdateFilterList:

							if(CANNOT UpdateFilter((STRPTR)parameter))
								error = ERROR_NO_FREE_STORE;

							break;

						/* change the check timer delay? */
						case WIPEOUTCMD_NewCheckDelay:

							CheckDelay = (LONG)parameter;

							/* re-check the check timer delay */
							signalsReceived |= sigTimer;
							break;

						/* remunge unallocated memory? */
						case WIPEOUTCMD_Remunge:

							DPrintf("Remunging memory... ");
							BeginMemMung();
							DPrintf("done.\n");

							break;

						/* mark all "current" memory allocations? */
						case WIPEOUTCMD_Mark:

							DPrintf("Marking memory... ");
							ChangeMemoryMarks(TRUE);
							ChangePuddleMarks(TRUE);
							DPrintf("done.\n");
							break;

						/* clear all memory marks? */
						case WIPEOUTCMD_Unmark:

							DPrintf("Clearing memory marks... ");
							ChangeMemoryMarks(FALSE);
							ChangePuddleMarks(FALSE);
							DPrintf("done.\n");
							break;

						/* show all memory marks? */
						case WIPEOUTCMD_ShowUnmarked:

							DPrintf("%s\nShowing all unmarked memory allocations\n",Separator);
							ShowUnmarkedMemory();
							ShowUnmarkedPools();
							DPrintf("%s\n\n",Separator);
							break;
					}
    
					/* let the client task go */
					WipeoutSemaphore->ws_Error = error;
					Signal(WipeoutSemaphore->ws_Client,SIG_Handshake);
				}
    
				/* start or stop the timer, depending on the
				 * check delay length
				 */
				if(FLAG_IS_SET(signalsReceived,sigTimer))
				{
					if(CheckDelay > 0)
					{
						StartTimer(CheckDelay / 10,(CheckDelay % 10) * (MILLION / 10));

						signalsReceived |= SIG_Check;
					}
					else
					{
						StopTimer();
					}
				}

				/* check all memory allocations */
				if(FLAG_IS_SET(signalsReceived,SIG_Check))
				{
					struct timeval tv;

					Forbid();

					GetSysTime(&tv);
					ConvertTimeAndDate(&tv,GlobalDateBuffer,GlobalTimeBuffer);

					DPrintf("%s\nChecking all memory allocations (%s%s)\n%s\n",
						Separator,GlobalDateBuffer,GlobalTimeBuffer,Separator);

					CheckAllocatedMemory();
					CheckPools();
					CheckFilter();

					DPrintf("%s\n\n",Separator);

					Permit();
				}
    
				/* stop monitoring memory allocations */
				if(FLAG_IS_SET(signalsReceived,SIG_Disable))
				{
					if(IsActive)
					{
						struct timeval tv;
	
						Forbid();
	
						GetSysTime(&tv);
						ConvertTimeAndDate(&tv,GlobalDateBuffer,GlobalTimeBuffer);
	
						DPrintf("%s\n%s deactivated (%s%s)\n%s\n",
							Separator,ProgramName,GlobalDateBuffer,GlobalTimeBuffer,Separator);
	
						IsActive = FALSE;
	
						Permit();
					}
				}
    
				/* restart monitoring memory allocations */
				if(FLAG_IS_SET(signalsReceived,SIG_Enable))
				{
					if(NOT IsActive)
					{
						struct timeval tv;

						Forbid();

						GetSysTime(&tv);
						ConvertTimeAndDate(&tv,GlobalDateBuffer,GlobalTimeBuffer);

						DPrintf("%s\n%s activated (%s%s)\n%s\n",
							Separator,ProgramName,GlobalDateBuffer,GlobalTimeBuffer,Separator);

						IsActive = TRUE;

						Permit();
					}
				}
    		}
		}
	}

	/* note that we never arrive here if we are the owner
	 * of the wipeout semaphore
	 */
	Cleanup();

	return(result);
}
