/*
	File:		Aux.c

	Contains:	Process Mgr support for A/UX.
				NOTE: None of the code in this file is referenced unless the build
				defines the HAS_AUX_PROCESSMGR condition.

	Written by:	David Harrison

	Copyright:	© 1980-1992 by Apple Computer, Inc., all rights reserved.

	Change History (most recent first):
	 
		 <5>	 8/26/92	DTY		Roll in latest changes for A/UX.
		 <4>	  4/1/91	DFH		(with John Iarocci) Bring this file up to date.
		 <2>	 12/5/90	DFH		Fixed warning in AUX_Block re: unused param.
		 <0>	 12/4/90	DFH		New Today.

*/ 

#include <Files.h>
#include <QuickDraw.h>
#include <Resources.h>
#include <Retrace.h>
#include <Errors.h>
#include <MFPrivate.h>

#include "Data.h"
#include "OSDispatchPrivate.h"
#include "Glue.h"
#include "SysMisc.h"
#include "Aux.h"
#include "Patches.h"
#include "Lomem.h"

/* Variables local to this module */
static short				auxMaxSelector = -1;
static long					ProcessMgrTaskID = -1;
static Getnextevent_ptr		realGNE;
static Eventavail_ptr		realEA;
static AUXDispatch_ptr		realAD;

/* Some function prototypes that should be in (yet another) header file */
OSErr	NewProcess(InternalLaunchPBPtr pParams);
short	GetNextPseudoEvent(short, EventRecord *, Boolean);
void	OpenAppWDs(PEntryPtr);

/* Handy macro to determine whether given event is best handled by AUX. */
#define IsAUXEvent(pEvent)	((pEvent->what >= FIRST_AUX_EVENT) && (pEvent->what <= LAST_AUX_EVENT))

#pragma segment AUX_SEGMENT

/* AUX_Setup.  Set things up as necessary for A/UX. */
void
AUX_Setup(void)
	{
	/* Determine level of functionality.  This check can probably go away once
	 * we settle on a common version of A/UX
	 */
	auxMaxSelector = AUX_Highest();
	
	/* Set extra patches so we can fool ourselves */
	realGNE = (Getnextevent_ptr) NGetTrapAddress(0xa970, ToolTrap);
	NSetTrapAddress((long) AUX_GetNextEvent, 0xa970, ToolTrap);
	
	realEA = (Eventavail_ptr) NGetTrapAddress(0xa971, ToolTrap);
	NSetTrapAddress((long) AUX_EventAvail, 0xa971, ToolTrap);

	realAD = (AUXDispatch_ptr) NGetTrapAddress(0xabf9, ToolTrap);
	NSetTrapAddress((long) AUX_Dispatch_Patch, 0xabf9, ToolTrap);
	}

/* AUX_EventAvail.  Similar to our replacement for getnextevent, we also replace
 * EventAvail for usage by Process Mgr only.
 */
pascal short
AUX_EventAvail(short evtMask, EventRecord *theEvent)
	{
	Boolean			oldMask;
	EventRecord		anEvent;
	
	oldMask = AUX_SetAUXMask(true);
	for (;;)
		{	
		(void) (*realEA)(evtMask, theEvent);
		
		if (IsAUXEvent(theEvent) == false)
			break;
		
		/* if an A/UX event is available, remove it from the queue and deal with it */
		AUX_GetAnyEventGlue(0, &anEvent, 0, 0, true);
		(void) AUX_HandleEvent(&anEvent);
		};
	
	(void) AUX_SetAUXMask(oldMask);
	
	return theEvent->what == nullEvent ? wFalse : wTrue;
	}

/* AUX_Dispatch_Patch. Patch AUX_Dispatch so we can look for a AUX_BEGINAPPLICATION
 * selector and execute within the ProcessManagers context
 */
 pascal long
 AUX_Dispatch_Patch(short selector, char *p)
 {
	unsigned long	olda5;
	long retval;
	
	olda5 = ProcessMgrA5SimpleSetup();
 	
	if (selector != AUX_BEGINAPPLICATION) 
		retval = (*realAD)(selector, p);
	else 
		/* Fix up working directories for home directory and files in APPPARMHANDLE */
		OpenAppWDs(pCurrentProcess);

	A5SimpleRestore(olda5);

	return(retval);
}

/* AUX_Block.  */
void
AUX_Block(PEntryPtr)
	{
	}

/* AUX_CoffLaunch.  If a coff binary toolbox program is executed, it will first issue a
 * UI_ATTACH ioctl, which will send us an attachEvt.  Upon seeing the attach event, we
 * use AUX_MakeLayer to create a minimal process which can then be switched to using the
 * normal context switching code.  The coff binary will then call (via OSDispatch) this
 * routine to really fill the process out.  All this is necessary because the coff
 * program must execute in a separate A/UX process and it's accesses to the shared data
 * areas must be synchronized.
 */
pascal short
AUX_CoffLaunch(u_long reqSize, u_long taskmode, StringPtr procName, short vrefnum, u_long ssize)
	{
#pragma unused (reqSize, taskmode, procName, vrefnum, ssize)
		
	return pCurrentProcess->p_mypid;
	}

/* AUX_FindProc.  Find the process associated with taskId. */
PEntryPtr
AUX_FindProc(taskId)
	{
	PEntryPtr	pProc;

	pProc = pProcessList;
	while (pProc != nil)
		{
		if (pProc->aux_realpid == taskId)
			break;
		
		pProc = pProc->p_NextProcess;
		}
		
	return pProc;
	}

/* AUX_ForkExec.  Fork and exec a new coff binary. */
short
AUX_ForkExecGlue(short vrefnum, char *name)
	{
	ForkExecRec		params;
	
	assert(AUX_FORKEXEC <= auxMaxSelector);
	
	params.vrefnum = vrefnum;
	params.name = name;
	if (AUX_ForkExec(&params) != 0)
		return permErr;
	else
		return params.pid;
	}

/* AUX_GetAnyEventGlue.  Handy routine for calling AUX_GetAnyEvent, since latter is
 * rather awkward.
 */
short
AUX_GetAnyEventGlue(short evtMask, EventRecord *theEvent, long timeout, RgnHandle bounds, Boolean pullit)
	{
	GetAnyEventRec		params;
	
	assert(AUX_GETANYEVENT <= auxMaxSelector);
	
	params.mask = evtMask;
	params.timeout = timeout;
	params.mouseBounds = bounds;
	params.pullIt = pullit;
	
	(void) AUX_GetAnyEvent(&params);
	
	*theEvent = params.event;
	
	return theEvent->what != nullEvent ? -1 : 0;
	}

/* AUX_GetMinTimeout.  Compute the minimum timeout necessary, we use this so we can
 * sleep in the kernel instead of spinning in user mode.
 */
long
AUX_GetMinTimeout(void)
	{
	extern Boolean napOver;
	
	if ((pFrontProcess->p_waitnexteventlevel > 0)	/* GetNextEvent never blocks */
		&& (coercionState == CS_DONE) 				/* if coercing, don't want physical events */
		&& (napOver == 0)							/* somebody needs attention */
		&& (pSleepingStateList != nil))				/* somebody is sleeping */
		return (pSleepingStateList->p_wakeuptime - TICKS);
	else
		return 0;
	}

/* AUX_GetMouseBounds.  Compute the rectangle for mouse moved events.  Once again, this
 * is so we can sleep in the kernel instead of user mode spinning.
 */
RgnHandle
AUX_GetMouseBounds(void)
	{
	extern RgnHandle mousebox;
	
	return mousebox;
	}

/* AUX_GetNextEvent.  Our replacement for the real GetNextEvent.  This isn't the one
 * seen by the user program, but is the one we will use to actually get events out of
 * the queue.  Ours is different because we can get some funky events back from the
 * kernel and mere mortals shouldn't ever get them.
 *
 * NOTE: ProcessManager overpatches this trap, but calls us in event_common when
 *		the process state is PRRUN.
 */
pascal short
AUX_GetNextEvent(short evtMask, register EventRecord *theEvent)
	{
	Boolean		oldMask;
	short		realRetVal;
	
	oldMask = AUX_SetAUXMask(true);
	
	while ((realRetVal = (*realGNE)(evtMask, theEvent)) && AUX_HandleEvent(theEvent))
		{
		/* Just loop.  All the work is done in the loop control. */
		}

	(void) AUX_SetAUXMask(oldMask);
	
	return realRetVal;
	}

/* AUX_GetTaskGlue.  Handy utility to return the A/UX style task identifier of this
 * process so we can do a real context switch to it later.
 */
long
AUX_GetTaskGlue(void)
	{
	long		taskId;
	
	assert(AUX_GETTASK <= auxMaxSelector);
	AUX_GetTask(&taskId);
	return taskId;
	}

/* AUX_HandleEvent.  Handle an A/UX specific event. */
short
AUX_HandleEvent(EventRecord *theEvent)
	{
	short		retVal;

	retVal = wTrue;
	if (IsAUXEvent(theEvent) == false)
		return wFalse;
	
	switch(theEvent->what)
		{
		/* somebody's UI_SETSELECT broke, make 'em runnable. */
		case	auxSelectEvent:
			AUX_MakeRunnable(theEvent->message);
			break;
		
		/* a new layer was asynchronously created, fit it in to the scheduling */
		case	auxAttachEvent:
			AUX_MakeLayer(theEvent->message, theEvent->modifiers, theEvent->when);
			break;
		
		/* a layer went away by mistake (kill(9...) for instance, clean it up */
		case	auxExitEvent:
			AUX_KillLayer(theEvent->message);
			break;
		}
	theEvent->what = nullEvent;

	return retVal;
	}

#define LAUNCH_DELAY		(60*60)						/* one minute */

/* AUX_IdleProc.  */
void
AUX_IdleProc(void)
	{
	register PEntryPtr	myProc;
	unsigned long		olda5;
	EventRecord			anEvent;

	olda5 = ProcessMgrA5Setup();
	myProc = pCurrentProcess;
	for (;;)
		{
		assert(myProc->aux_waitproc);
		
		(void) AUX_GetAnyEventGlue(0, &anEvent, LAUNCH_DELAY, 0, true);
		
		/* Handle the attach.  aux_restore_ctx should get us out of here, never to return. */
		if ((anEvent.what == auxAttachEvent) && (anEvent.message == myProc->aux_waitproc))
			{
			myProc->aux_realpid = myProc->aux_waitproc;
			myProc->aux_waitproc = 0;
			AUX_SwitchGlue(myProc->aux_realpid);
			aux_restore_ctx(pCurrentProcess->p_sp);
			dbmsg("How the Å did I get here?");
			}
		
		/* AUX event was not the usual attach, or an attach came from an incorrect
		 * AUX process.  Feebly try to handle the event.  Then die.
		 */
		(void) AUX_HandleEvent(&anEvent);
		AUX_Kill(myProc->aux_waitproc);
		ExitToShell();
		}
	}

/* AUX_KernelEvents.  */
short
AUX_KernelEvents(EventRecord *theEvent)
	{
	short		foundSome;
	
	foundSome = 0;
	while (AUX_GetAnyEventGlue(0, theEvent, 0, 0, 1) && IsAUXEvent(theEvent) != 0)
		{
		(void) AUX_HandleEvent(theEvent);
		foundSome++;
		}
	
	return foundSome;
	}

/* AUX_KillLayer.  Get rid of an abnormally terminated process */
void
AUX_KillLayer(long taskId)
	{
	register PEntryPtr		pProc;
	
	if ((pProc = AUX_FindProc(taskId)) != nil)
		(void) KillProcess(&pProc->p_serialNumber);
	}

/* AUX_Launch.  */
OSErr
AUX_Launch(InternalLaunchPBPtr pParams)
	{
	short		realPid;
	PEntryPtr	pProc;
	OSErr		err;
	WDPBRec     wd;

	/* setup a WDPBRec to pass to AUX_ForkExec through the AUXDispatch */
	wd.ioCompletion = nil;
	wd.ioNamePtr = nil;
	wd.ioWDProcID = 0;
	wd.ioVRefNum = pParams->ilAppSpecPtr->vRefNum;
	wd.ioWDDirID = pParams->ilAppSpecPtr->parID;
	PBOpenWD(&wd, true);
    
	realPid = AUX_ForkExecGlue(wd.ioVRefNum, pParams->ilAppSpecPtr->name);

	/* Close the WD, if we opened it */
	if (wd.ioWDCreated != 0)
		PBCloseWD(&wd, SyncHFS);

	/* Negative result is an OSErr.  Result should never be 0.  Positive result is AUX pid. */
	if (realPid < 0)
		return((OSErr) realPid);

	pParams->ilTaskMode |= COFF_BINARY;
	if ((err = NewProcess(pParams)) != noErr)
		{
		dbmsg("AUX_Launch: NewProcess failed");
		AUX_Kill(realPid);
		return(err);
		}
	
	pProc = pParams->ilResultProc;
	pProc->aux_waitproc = realPid;
	pProc->aux_realpid = AUX_ProcessMgrTask();

	return(noErr);
	}

/* AUX_MakeLayer.  Create an empty process.  After the A/UX process switch it will be
 * filled out by whomever did the UI_ATTACH.
 */
void
AUX_MakeLayer(long taskId, unsigned short flags, long size)
	{
	register PEntryPtr	pProc;
	InternalLaunchPB	launchPB;
	CoffName			coffname;

	coffname.taskid = taskId;
	if ((pProc = AUX_Unblock(taskId)) != nil)
		return;

	if (AUX_COFF_FSSpec(&coffname) != noErr)
		return;
	
	launchPB.ilAppSpecPtr = &coffname.fsspec;
	launchPB.ilTaskMode = COFF_BINARY | ASYNC_LAUNCH | (unsigned long) flags;
	launchPB.ilDAInfoPtr = nil;
	launchPB.ilPartitionSize = size + DEFLTSTACK - MACPLUS_DEFLTSTACK;
	launchPB.ilStackSize = DEFLTSTACK;
	launchPB.ilAppParameters = nil;
	launchPB.ilAppParamHdl = nil;
	if (NewProcess(&launchPB) != noErr)
		{
		dbmsg("AUX_MakeLayer: NewProcess failed");
		AUX_Kill(taskId);
		return;
		}
	
	pProc = launchPB.ilResultProc;
	pProc->aux_realpid = taskId;
	pProc->aux_waitproc = 0;
	
	(void) WakeUpProcess(&pFrontProcess->p_serialNumber);
	}

/* AUX_MakeRunnable.  Make an application contained in a different task runnable.  taskId
 * refers to the kernel event layer identifier we got back from AUX_GetTask and stowed
 * away in auxInfo.
 */
void
AUX_MakeRunnable(long taskId)
	{
	PEntryPtr		pProc;
	
	if ((pProc = AUX_FindProc(taskId)) != nil)
		(void) WakeUpProcess(&pProc->p_serialNumber);
	}

/* AUX_ProcessMgrTask.  Return the task descriptor for Macintosh Applications */
long
AUX_ProcessMgrTask(void)
	{
	if (ProcessMgrTaskID < 0)
		AUX_GetTask(&ProcessMgrTaskID);
		
	return ProcessMgrTaskID;
	}

/* AUX_SwitchGlue.  Force an A/UX kernel context switch. */
void
AUX_SwitchGlue(long taskId)
	{
	long			rval;
	EventRecord		anEvent;
	
	assert(AUX_SWITCH <= auxMaxSelector);

	/* If the Process Mgr process this task is associated with is gone, kill this task
	 * after the switch, if this is the original Process Mgr process we have to
	 * leave it laying around anyway.
	 * NOTE: Inverting taskID tells AUX_Switch to kill the originating process after
	 * enabling the new one.
	 */
	if (((rval = AUX_GetTaskGlue()) != AUX_ProcessMgrTask()) && AUX_FindProc(rval) == 0)
		taskId = (-(taskId));

	for (;;)
		{
		/* Try switch.  On error we try to destroy the layer (AUX_KillLayer won't
		 * return).  If it does, we aren't in a valid process, so we pick a new one.
		 */
		if (AUX_Switch(taskId) != 0)
			{
			AUX_KillLayer(taskId);
			cpu_resched();
			}
		
		/* Normally, when we get back from the AUX_Switch, we will have already been
		 * made the current process.  If the task id of this a/ux process is not the
		 * same as where we think we ought to be, we got woken up because the current
		 * process died (most likely, perhaps theres something else, so we're pretending
		 * to be paranoid) so we reap and handle any a/ux events.  If it's true that
		 * the current process really did die, we will eventually receive an exitEvent,
		 * which will result in the associated Process Mgr process being killed and the
		 * processor reschedule, in which case we won't ever return to this routine.
		 */
		if (pCurrentProcess->aux_realpid == AUX_GetTaskGlue())
			break;
			
		/* AUX_HandleEvent won't return if the process we're switching to has exited,
		 * thus giving us an exitEvent
		 */
		do
		{
			(void) AUX_GetAnyEventGlue(0, &anEvent, 0l, 0l, true);
		} while (AUX_HandleEvent(&anEvent));
		
		dbmsg("AUX_SwitchGlue: probably shouldn't ever get here");
		
		/* having handled whatever broke us out of our interminable sleep, try again */
		}
	}

/* AUX_Unblock.  Find the nacent Process Mgr process associated with the given taskID,
 * and declare it alive and well.
 */
PEntryPtr
AUX_Unblock(long taskId)
	{
	PEntryPtr	pProc;

	pProc = pProcessList;
	while (pProc != nil)
		{
		if (pProc->aux_waitproc == taskId)
			{
			pProc->aux_waitproc = 0;
			pProc->aux_realpid = taskId;
			break;
			}
		
		pProc = pProc->p_NextProcess;
		}
		
	return pProc;
	}

/* AUX_WaitForEvent.  */
short
AUX_WaitForEvent(short mask, EventRecord *theEvent)
	{
	if (pCurrentProcess)
		if (GetNextPseudoEvent(mask, theEvent, false))
			return wTrue;

	(void) AUX_GetAnyEventGlue(mask, theEvent, AUX_GetMinTimeout(), AUX_GetMouseBounds(), false);
	
	return theEvent->what == nullEvent ? wFalse : wTrue;
	}

/* AUX_WaitNextEvent.  */
short
AUX_WaitNextEvent(short mask, EventRecord *theEvent, long timeout, RgnHandle mbox)
	{
	short		rval;
	
	AUX_SetTimeOut(timeout);
	AUX_SetBounds(mbox);
	
	rval = GetNextEvent(mask, theEvent);
	
	AUX_SetTimeOut(0);
	AUX_SetBounds(nil);
	
	return rval;
	}
