language-Modula2-0.1: examples/Modula-2_Libraries/PMOS/sources/general/terminat.mod
IMPLEMENTATION MODULE TerminationControl;
(********************************************************)
(* *)
(* Support for program termination procedures. *)
(* *)
(* Programmer: P. Moylan *)
(* Last edited: 21 March 1995 *)
(* Status: Working *)
(* *)
(********************************************************)
FROM Storage IMPORT
(* proc *) ALLOCATE, DEALLOCATE;
(*<TopSpeed*)
FROM Lib IMPORT
(* proc *) Terminate;
(*>*)
(*<FST
FROM System IMPORT
(* proc *) TermProcedure, Terminate;
>*)
FROM InnerKernel IMPORT
(* proc *) PrepareForShutdown;
FROM MiscPMOS IMPORT
(* proc *) CopyString;
(************************************************************************)
TYPE
(* We have one ListElement for each termination procedure. *)
ListPointer = POINTER TO ListElement;
ListElement = RECORD
Procedure: PROC;
next: ListPointer;
END (*RECORD*);
VAR
(* ListHead and ListTail point to the first and last elements, *)
(* respectively, of the list of termination procedures. *)
ListHead, ListTail: ListPointer;
(* The termination handler which was present before we installed *)
(* our own. *)
OriginalHandler: PROC;
(* Text message supplied by caller of procedure Crash. *)
CrashMessage: ARRAY [0..79] OF CHAR;
(* MessagePresent = TRUE iff procedure Crash has been called. *)
MessagePresent: BOOLEAN;
(* Flag to say whether termination processing has commenced. *)
TerminationInProgress: BOOLEAN;
(************************************************************************)
(* UPDATING THE LIST OF TERMINATION PROCEDURES *)
(************************************************************************)
PROCEDURE SetTerminationProcedure (TP: PROC);
(* Adds TP to the list of procedures which will be called just *)
(* before program termination. The list is ordered such that the *)
(* last procedure added will be the first one called. Exception: *)
(* if termination is already in progress when this procedure is *)
(* called, then TP will not be called until all of the existing *)
(* termination procedures have been called. This rule permits *)
(* multi-pass termination processing, where necessary, by letting *)
(* termination procedures themselves install more termination *)
(* procedures. *)
VAR OldHead, NewTail: ListPointer;
BEGIN
IF TerminationInProgress THEN
(* Add the new list element to the tail of the list. *)
NEW (NewTail);
WITH NewTail^ DO
Procedure := TP;
next := NIL;
END (*WITH*);
IF ListTail = NIL THEN
ListHead := NewTail;
ELSE
ListTail^.next := NewTail;
END (*IF*);
ListTail := NewTail;
ELSE
(* Termination not already in progress. Add the new item *)
(* to the head of the list, to give the desired LIFO order. *)
OldHead := ListHead;
NEW (ListHead);
WITH ListHead^ DO
Procedure := TP;
next := OldHead;
END (*WITH*);
IF OldHead = NIL THEN
ListTail := ListHead;
END (*IF*);
END (*IF*);
END SetTerminationProcedure;
(************************************************************************)
(* THE ACTUAL TERMINATION HANDLER *)
(************************************************************************)
PROCEDURE TerminationHandler;
(* This is the procedure which is called on program termination. *)
(* It then calls all of the procedures which the user wants called. *)
VAR OldHead: ListPointer; UserProc: PROC;
BEGIN
TerminationInProgress := TRUE;
(* Work through the list of termination procedures. Note that *)
(* it's important to remove the termination handler from the *)
(* list before calling it, to avoid recursive calls in case the *)
(* handler itself triggers another termination. *)
WHILE ListHead <> NIL DO
UserProc := ListHead^.Procedure;
OldHead := ListHead; ListHead := ListHead^.next;
DISPOSE (OldHead);
UserProc;
END (*WHILE*);
(* Finally, call the system-level termination handler. *)
PrepareForShutdown;
(*<TopSpeed*) OriginalHandler(); (*>*)
(*<FST Terminate(0); >*)
END TerminationHandler;
(************************************************************************)
(* RAISING AN ERROR CONDITION *)
(************************************************************************)
PROCEDURE Crash (message: ARRAY OF CHAR);
(* Terminates the program with an error report. *)
BEGIN
CopyString (message, CrashMessage);
MessagePresent := TRUE;
TerminationHandler;
END Crash;
(************************************************************************)
(* USER-CALLABLE ERROR REPORTING *)
(************************************************************************)
PROCEDURE TerminationMessage (VAR (*OUT*) message: ARRAY OF CHAR): BOOLEAN;
(* Returns the message supplied by the caller of the Crash *)
(* procedure. The function result is TRUE if such a message *)
(* exists, and FALSE if Crash was never called. *)
BEGIN
IF MessagePresent THEN
CopyString (CrashMessage, message);
RETURN TRUE;
ELSE
RETURN FALSE;
END (*IF*);
END TerminationMessage;
(************************************************************************)
(* INITIALISATION *)
(************************************************************************)
BEGIN
ListHead := NIL; ListTail := NIL;
TerminationInProgress := FALSE; MessagePresent := FALSE;
(*<TopSpeed*) Terminate (TerminationHandler, OriginalHandler); (*>*)
(*<FST TermProcedure (TerminationHandler); >*)
END TerminationControl.