sl@0: /* 
sl@0:  * tclMacExit.c --
sl@0:  *
sl@0:  *	This file contains routines that deal with cleaning up various state
sl@0:  *	when Tcl/Tk applications quit.  Unfortunantly, not all state is cleaned
sl@0:  *	up by the process when an application quites or crashes.  Also you
sl@0:  *	need to do different things depending on wether you are running as
sl@0:  *	68k code, PowerPC, or a code resource.  The Exit handler code was 
sl@0:  *	adapted from code posted on alt.sources.mac by Dave Nebinger.
sl@0:  *
sl@0:  * Copyright (c) 1995 Dave Nebinger.
sl@0:  * Copyright (c) 1995-1996 Sun Microsystems, Inc.
sl@0:  *
sl@0:  * See the file "license.terms" for information on usage and redistribution
sl@0:  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
sl@0:  *
sl@0:  * RCS: @(#) $Id: tclMacExit.c,v 1.4 1999/04/16 00:47:19 stanton Exp $
sl@0:  */
sl@0: 
sl@0: #include "tclInt.h"
sl@0: #include "tclMacInt.h"
sl@0: #include <SegLoad.h>
sl@0: #include <Traps.h>
sl@0: #include <Processes.h>
sl@0: 
sl@0: /*
sl@0:  * Various typedefs and defines needed to patch ExitToShell.
sl@0:  */
sl@0:  
sl@0: enum {
sl@0:         uppExitToShellProcInfo = kPascalStackBased
sl@0: };
sl@0: 
sl@0: #if GENERATINGCFM
sl@0: typedef UniversalProcPtr ExitToShellUPP;
sl@0: 
sl@0: #define CallExitToShellProc(userRoutine)        \
sl@0:         CallUniversalProc((UniversalProcPtr)(userRoutine),uppExitToShellProcInfo)
sl@0: #define NewExitToShellProc(userRoutine) \
sl@0:         (ExitToShellUPP)NewRoutineDescriptor((ProcPtr)(userRoutine), \
sl@0: 		uppExitToShellProcInfo, GetCurrentArchitecture())
sl@0: 
sl@0: #else
sl@0: typedef ExitToShellProcPtr ExitToShellUPP;
sl@0: 
sl@0: #define CallExitToShellProc(userRoutine)        \
sl@0:         (*(userRoutine))()
sl@0: #define NewExitToShellProc(userRoutine) \
sl@0:         (ExitToShellUPP)(userRoutine)
sl@0: #endif
sl@0: 
sl@0: #define DisposeExitToShellProc(userRoutine) \
sl@0:         DisposeRoutineDescriptor(userRoutine)
sl@0: 
sl@0: #if defined(powerc)||defined(__powerc)
sl@0: #pragma options align=mac68k
sl@0: #endif
sl@0: struct ExitToShellUPPList{
sl@0:         struct ExitToShellUPPList* nextProc;
sl@0:         ExitToShellUPP userProc;
sl@0: };
sl@0: #if defined(powerc)||defined(__powerc)
sl@0: #pragma options align=reset
sl@0: #endif
sl@0: 
sl@0: typedef struct ExitToShellDataStruct ExitToShellDataRec,* ExitToShellDataPtr,** ExitToShellDataHdl;
sl@0: 
sl@0: typedef struct ExitToShellUPPList ExitToShellUPPList,* ExitToShellUPPListPtr,** ExitToShellUPPHdl;
sl@0: 
sl@0: #if defined(powerc)||defined(__powerc)
sl@0: #pragma options align=mac68k
sl@0: #endif
sl@0: struct ExitToShellDataStruct{
sl@0:     unsigned long a5;
sl@0:     ExitToShellUPPList* userProcs;
sl@0:     ExitToShellUPP oldProc;
sl@0: };
sl@0: #if defined(powerc)||defined(__powerc)
sl@0: #pragma options align=reset
sl@0: #endif
sl@0: 
sl@0: /*
sl@0:  * Static globals used within this file.
sl@0:  */
sl@0: static ExitToShellDataPtr gExitToShellData = (ExitToShellDataPtr) NULL;
sl@0: 
sl@0: 
sl@0: /*
sl@0:  *----------------------------------------------------------------------
sl@0:  *
sl@0:  * TclPlatformExit --
sl@0:  *
sl@0:  *	This procedure implements the Macintosh specific exit routine.
sl@0:  *	We explicitly callthe ExitHandler function to do various clean
sl@0:  *	up.  
sl@0:  *
sl@0:  * Results:
sl@0:  *	None.
sl@0:  *
sl@0:  * Side effects:
sl@0:  *	We exit the process.
sl@0:  *
sl@0:  *----------------------------------------------------------------------
sl@0:  */
sl@0: 
sl@0: void
sl@0: TclpExit(
sl@0:     int status)		/* Ignored. */
sl@0: {
sl@0:     TclMacExitHandler();
sl@0: 
sl@0: /* 
sl@0:  * If we are using the Metrowerks Standard Library, then we will call its exit so that it
sl@0:  * will get a chance to clean up temp files, and so forth.  It always calls the standard 
sl@0:  * ExitToShell, so the Tcl handlers will also get called.
sl@0:  *   
sl@0:  * If you have another exit, make sure that it does not patch ExitToShell, and does
sl@0:  * call it.  If so, it will probably work as well.
sl@0:  *
sl@0:  */
sl@0:  
sl@0: #ifdef __MSL__    
sl@0:     exit(status);
sl@0: #else
sl@0:     ExitToShell();
sl@0: #endif
sl@0: 
sl@0: }
sl@0: 
sl@0: /*
sl@0:  *----------------------------------------------------------------------
sl@0:  *
sl@0:  * TclMacExitHandler --
sl@0:  *
sl@0:  *	This procedure is invoked after Tcl at the last possible moment
sl@0:  *	to clean up any state Tcl has left around that may cause other
sl@0:  *	applications to crash.  For example, this function can be used
sl@0:  *	as the termination routine for CFM applications.
sl@0:  *
sl@0:  * Results:
sl@0:  *	None.
sl@0:  *
sl@0:  * Side effects:
sl@0:  *	Various cleanup occurs.
sl@0:  *
sl@0:  *----------------------------------------------------------------------
sl@0:  */
sl@0: 
sl@0: void
sl@0: TclMacExitHandler()
sl@0: {
sl@0:     ExitToShellUPPListPtr curProc;
sl@0: 
sl@0:     /*
sl@0:      * Loop through all installed Exit handlers
sl@0:      * and call them.  Always make sure we are in
sl@0:      * a clean state in case we are recursivly called.
sl@0:      */
sl@0:     if ((gExitToShellData) != NULL && (gExitToShellData->userProcs != NULL)){
sl@0:     
sl@0: 	/*
sl@0: 	 * Call the installed exit to shell routines.
sl@0: 	 */
sl@0: 	curProc = gExitToShellData->userProcs;
sl@0: 	do {
sl@0: 	    gExitToShellData->userProcs = curProc->nextProc;
sl@0: 	    CallExitToShellProc(curProc->userProc);
sl@0: 	    DisposeExitToShellProc(curProc->userProc);
sl@0: 	    DisposePtr((Ptr) curProc);
sl@0: 	    curProc = gExitToShellData->userProcs;
sl@0: 	} while (curProc != (ExitToShellUPPListPtr) NULL);
sl@0:     }
sl@0: 
sl@0:     return;
sl@0: }
sl@0: 
sl@0: /*
sl@0:  *----------------------------------------------------------------------
sl@0:  *
sl@0:  * TclMacInstallExitToShellPatch --
sl@0:  *
sl@0:  *	This procedure installs a way to clean up state at the latest
sl@0:  *	possible moment before we exit.  These are things that must
sl@0:  *	be cleaned up or the system will crash.  The exact way in which
sl@0:  *	this is implemented depends on the architecture in which we are
sl@0:  *	running.  For 68k applications we patch the ExitToShell call.
sl@0:  *	For PowerPC applications we just create a list of procs to call.
sl@0:  *	The function ExitHandler should be installed in the Code 
sl@0:  *	Fragments terminiation routine.
sl@0:  *
sl@0:  * Results:
sl@0:  *	None.
sl@0:  *
sl@0:  * Side effects:
sl@0:  *	Installs the new routine.
sl@0:  *
sl@0:  *----------------------------------------------------------------------
sl@0:  */
sl@0: 
sl@0: OSErr 
sl@0: TclMacInstallExitToShellPatch(
sl@0:     ExitToShellProcPtr newProc)		/* Function pointer. */
sl@0: {
sl@0:     ExitToShellUPP exitHandler;
sl@0:     ExitToShellUPPListPtr listPtr;
sl@0: 
sl@0:     if (gExitToShellData == (ExitToShellDataPtr) NULL){
sl@0: 	TclMacInitExitToShell(true);
sl@0:     }
sl@0: 
sl@0:     /*
sl@0:      * Add the passed in function pointer to the list of functions
sl@0:      * to be called when ExitToShell is called.
sl@0:      */
sl@0:     exitHandler = NewExitToShellProc(newProc);
sl@0:     listPtr = (ExitToShellUPPListPtr) NewPtrClear(sizeof(ExitToShellUPPList));
sl@0:     listPtr->userProc = exitHandler;
sl@0:     listPtr->nextProc = gExitToShellData->userProcs;
sl@0:     gExitToShellData->userProcs = listPtr;
sl@0: 
sl@0:     return noErr;
sl@0: }
sl@0: 
sl@0: /*
sl@0:  *----------------------------------------------------------------------
sl@0:  *
sl@0:  * ExitToShellPatchRoutine --
sl@0:  *
sl@0:  *	This procedure is invoked when someone calls ExitToShell for
sl@0:  *	this application.  This function performs some last miniute
sl@0:  *	clean up and then calls the real ExitToShell routine.
sl@0:  *
sl@0:  * Results:
sl@0:  *	None.
sl@0:  *
sl@0:  * Side effects:
sl@0:  *	Various cleanup occurs.
sl@0:  *
sl@0:  *----------------------------------------------------------------------
sl@0:  */
sl@0: 
sl@0: static pascal void
sl@0: ExitToShellPatchRoutine()
sl@0: {
sl@0:     ExitToShellUPP oldETS;
sl@0:     long oldA5;
sl@0: 
sl@0:     /*
sl@0:      * Set up our A5 world.  This allows us to have
sl@0:      * access to our global variables in the 68k world.
sl@0:      */
sl@0:     oldA5 = SetCurrentA5();
sl@0:     SetA5(gExitToShellData->a5);
sl@0: 
sl@0:     /*
sl@0:      * Call the function that invokes all
sl@0:      * of the handlers.
sl@0:      */
sl@0:     TclMacExitHandler();
sl@0: 
sl@0:     /*
sl@0:      * Call the origional ExitToShell routine.
sl@0:      */
sl@0:     oldETS = gExitToShellData->oldProc;
sl@0:     DisposePtr((Ptr) gExitToShellData);
sl@0:     SetA5(oldA5);
sl@0:     CallExitToShellProc(oldETS);
sl@0:     return;
sl@0: }
sl@0: 
sl@0: /*
sl@0:  *----------------------------------------------------------------------
sl@0:  *
sl@0:  * TclMacInitExitToShell --
sl@0:  *
sl@0:  *	This procedure initializes the ExitToShell clean up machanism.
sl@0:  *	Generally, this is handled automatically when users make a call
sl@0:  *	to InstallExitToShellPatch.  However, it can be called 
sl@0:  *	explicitly at startup time to turn off the patching mechanism.
sl@0:  *	This can be used by code resources which could be removed from
sl@0:  *	the application before ExitToShell is called.
sl@0:  *
sl@0:  *	Note, if we are running from CFM code we never install the
sl@0:  *	patch.  Instead, the function ExitHandler should be installed
sl@0:  *	as the terminiation routine for the code fragment.
sl@0:  *
sl@0:  * Results:
sl@0:  *	None.
sl@0:  *
sl@0:  * Side effects:
sl@0:  *	Creates global state.
sl@0:  *
sl@0:  *----------------------------------------------------------------------
sl@0:  */
sl@0: 
sl@0: void 
sl@0: TclMacInitExitToShell(
sl@0:     int usePatch)	/* True if on 68k. */
sl@0: {
sl@0:     if (gExitToShellData == (ExitToShellDataPtr) NULL){
sl@0: #if GENERATINGCFM
sl@0: 	gExitToShellData = (ExitToShellDataPtr)
sl@0: 	  NewPtr(sizeof(ExitToShellDataRec));
sl@0: 	gExitToShellData->a5 = SetCurrentA5();
sl@0: 	gExitToShellData->userProcs = (ExitToShellUPPList*) NULL;
sl@0: #else
sl@0: 	ExitToShellUPP oldExitToShell, newExitToShellPatch;
sl@0: 	short exitToShellTrap;
sl@0: 	
sl@0: 	/*
sl@0: 	 * Initialize patch mechanism.
sl@0: 	 */
sl@0: 	 
sl@0: 	gExitToShellData = (ExitToShellDataPtr) NewPtr(sizeof(ExitToShellDataRec));
sl@0: 	gExitToShellData->a5 = SetCurrentA5();
sl@0: 	gExitToShellData->userProcs = (ExitToShellUPPList*) NULL;
sl@0: 
sl@0: 	/*
sl@0: 	 * Save state needed to call origional ExitToShell routine.  Install
sl@0: 	 * the new ExitToShell code in it's place.
sl@0: 	 */
sl@0: 	if (usePatch) {
sl@0: 	    exitToShellTrap = _ExitToShell & 0x3ff;
sl@0: 	    newExitToShellPatch = NewExitToShellProc(ExitToShellPatchRoutine);
sl@0: 	    oldExitToShell = (ExitToShellUPP)
sl@0: 	      NGetTrapAddress(exitToShellTrap, ToolTrap);
sl@0: 	    NSetTrapAddress((UniversalProcPtr) newExitToShellPatch,
sl@0: 		    exitToShellTrap, ToolTrap);
sl@0: 	    gExitToShellData->oldProc = oldExitToShell;
sl@0: 	}
sl@0: #endif
sl@0:     }
sl@0: }