Google
 

Trailing-Edge - PDP-10 Archives - AP-D471B-SB_1978 - devser.bli
There are no other files named devser.bli in the archive.
!***COPYRIGHT (C) 1974, 1975, 1976, 1977 DIGITAL EQUIPMENT CORP., MAYNARD, MASS.***
MODULE DEVSER(RESERVE(#11,#12,#13,#14),SREG=#17,FREG=#16,DREGS=4,
	      VREG=#15,MLIST,TIMER=EXTERNAL(SIX12),FSAVE)=
BEGIN

REQUIRE  DATA.BLI;
EXTERNAL	MOVE,
		UNLINK;

COMMENT;

! THIS FILE CONTAINS THE DISK SERVICE ROUTINES FOR ROLLING AND/OR
! FAILSOFTING INCOMING MESSAGES, AND "FAILSOFTING" OUTGOING
! DEFERRED MESSAGES.

! THE FAILSOFT/ROLLOUT FILE FOR INCOMING MESSAGES IS TREE STRUCTURED
! IN THE FOLLOWING MANNER:
!					    ( GROUP ON DISK )
!	-----------	---------     -----------     -----------
!	! LEAF	  !====>! DSKGH !====>! DSK	!====>! DSK	!...
!	! POINTER !	! TABLE	!     ! MESSAGE !==V  ! MESSAGE !
!	! TABLE	  !	---------     ! HEADER	!  :  ! HEADER	!
!	-----------	    .	      -----------  :  -----------
!			    .			   :
!			    .			   V
!						----------
!						! CHUNKS !
!						----------
!
!					    ( GROUP IN CORE )
!	-----------	---------     ----------      -----------
!	! LEAF	  !====>! GROUP !====>! MESSAGE !====>! MESSAGE ! ...
!	! OF THE  !	! HEADER!     ! HEADER	!==V  ! HEADER  !
!	! TREE	  !	---------     -----------  :  -----------
!	-----------	    .			   :
!			    .			   :
!			    .			   V
!						----------
!						! CHUNKS !
!						----------

! LEAF POINTER TABLE
!
!	-----------
!	! T1 ! T2 !
!	-----------
!	!    .    !
!	---- . ----
!	!    .    !
!	---- . ----
!	!    .	  !
!	-----------
!	!    ! TN !
!	-----------
!
!	THE GROUP POINTER TABLE CONSISTS OF ONE HALF-WORD PER LEAF AND
!	THE CONTENTS OF THESE HALFWORDS ARE POINTERS TO THE BEGINNING
!	OF THE DSKGHTAB ASSOCIATED WITH EACH LEAF.  THE GROUP POINTER
!	TABLE RESIDES AT A FIXED LOCATION IN THE ROLLOUT FILE. AS OF
!	THIS WRITING, THAT LOCATION IS PTL .IBASE AND UP AS REQUIRED.
!	CHECK DATBAS FOR THE CURRENT ADDRESS.
! DISK GROUP HEADER TABLE (DSKGHTAB):
!
!		-------------------------------------------------
! WORD 0:	! POINTER TO PREV DSKGH ! POINTER TO NEXT DSKGH ! ====V
!		-------------------------------------------------     :
!		!		      UNUSED			!  (2):
!		-------------------------------------------------     :
!		!		      UNUSED			!  (3):
!		-------------------------------------------------     :
!		!		      UNUSED			!  (4):
!		-------------------------------------------------     :
! ENTRY 1:	!	UNUSED		! POINTER TO FIRST DSKMH!  (5):
!		-------------------------------------------------     :
!		!	    TRANSACTION SEQUENCE NUMBER		!  (6):
!		-------------------------------------------------     :
!		!		    SENDER ID			!     :
!		-------------------------------------------------     :
!		!		      UNUSED			!     :
!		-------------------------------------------------     :
! ENTRY 2:	!			.			!     :
!		-------------------------------------------------     :
!		!	   		.			!     :
!		-------------------------------------------------     :
!		!			.			!     :
!		-------------------------------------------------     :
!								      :
!	V=============================================================<
!	:
!	:	-------------------------------------------------
!	>====>	! POINTER TO PREV DSKGH ! POINTER TO NEXT DSKGH !
!		-------------------------------------------------
!		!			0			!
!		-------------------------------------------------
!		!			0			!
!		-------------------------------------------------
!		!			0			!
!		-------------------------------------------------
! ENTRY N:	!						!
!		-------------------------------------------------
!		!			.			!
!		-------------------------------------------------
!		!			.			!
!		-------------------------------------------------
!		!			.			!
!		-------------------------------------------------
!		!			.			!
!		-------------------------------------------------
! DISK MESSAGE HEADER (DSKMH):
!
!		-------------------------------------------------
! WORD 0:	! POINTER TO PREV DSKMH ! POINTER TO NEXT DSKMH !  (1)
!		-------------------------------------------------
!		!		      DATE			!  (2)
!		-------------------------------------------------
!		!		      TIME			!  (3)
!		-------------------------------------------------
!		!		POINTER TO DSKGHTAB		!  (4)
!		-------------------------------------------------
!		!	TRANSACTION SEQUENCE NUMBER		!  (?)
!		-------------------------------------------------
!		!	SOT		!	ENDI		!  (?)
!		-------------------------------------------------
! FIRST RI:	!		RETRIEVAL INFORMATION		!  (5)
!		-------------------------------------------------
!		!			.			!
!		-------------------------------------------------
!		!			.			!
!		-------------------------------------------------
!		!			.			!
!		-------------------------------------------------
! LAST RI:	!	   -1	(6)	! NEXT BLOCK OF RI'S	! ====V
!		-------------------------------------------------     :
!								      :
!	V=============================================================<
!	:
!	:	-------------------------------------------------
!	>====>	!		COPY FROM FIRST MH		!  (7)
!		-------------------------------------------------
!		!			0			!
!		-------------------------------------------------
!		!			0			!
!		-------------------------------------------------
!		!			0			!
!		-------------------------------------------------
! FIRST RI:	!		RETRIEVAL INFORMATION		!
!		-------------------------------------------------
!		!			.			!
!		-------------------------------------------------
!		!			.			!
!		-------------------------------------------------
!		!			.			!
!		-------------------------------------------------
! LAST RI:	!	   -1		! NEXT BLOCK OF RI'S	! ====:
!		-------------------------------------------------     :
!								    ETC.
!
!	(1)	POINTER TO NEXT MH IS THE PARTICLE NUMBER OF THE NEXT
!		DSKMH PARTICLE ( OR ZERO IF NONE OR IF EXTENDED MH)
!		IF EXTENDED MH, ONLY THE LAST EXTENSION HAS A POINTER
!		TO THE NEXT
!	(2)	IF FAILSOFTING THEN THE DATE OF THE MESSAGE IS STORED,
!		OTHERWISE 0.
!	(3)	TIME IS TREATED THE SAME AS DATE
!	(4)	XWD OFFSET_IN_THE_PARTICLE, PARTICLE_NUMBER
!	(5)	RI ( RETREIVAL INFORMATION ) CONSISTS OF XWD NUMBER_OF_
!		CONTIGIOUS_PARTICLES, PARTICLE_NUMBER_OF_THE_FIRST_PARTICLE
!	(6)	IF THE NUMBER_OF_PARTICLES IS -1 THEN THE PARTICLE_
!		NUMBER_OF_THE_FIRST_PARTICLE IS THE POINTER TO THE NEXT
!		EXTENSION.
!	(7)	THE HEADER INFORMATION IN THE EXTENSIONS IS JUST A COPY
!		OF THAT IN THE FIRST
!
!
!
! CHUNK_PARTICLE FORMAT:
!
!	SAME AS IN-CORE:
!
!	-------------------------------------------------
!	!  POS	! SIZE	! ENDI	!	GARBAGE		!
!	-------------------------------------------------
!	!	WORD COUNT	!	BYTE COUNT	!
!	-------------------------------------------------
!	!		       DATA			!
!	-------------------------------------------------
!	!			.			!
!	-------------------------------------------------
!	!			.			!
!	-------------------------------------------------
!	!			.			!
!	-------------------------------------------------
FORWARD
	DSKMSG(4),
	STOREDSKGH(6),
	STORENEXTMH(1),
	DELDSKGH(2),
	FINDNEXTMH(1),
	ZAPDSKMHS(1);

EXTERNAL TRMDSKTAB,
	 LEAFDSKTAB;
	COMMENT;
	! 1. FAILSOFT GROUP HEADER
	! == ========== ===== ======
	!    CALLING ARGUEMENTS:
	!		1) LEAF NUMBER THIS TRANSACTION BELONGS TO
	!		2) TRANSACTION SEQUENCE NUMBER
	!		3) THE SENDER
	!		4) POINTER TO MESSAGE HEADER
	!    RETURNS:			DISK GH ADDR
	!    SIDE EFFECTS:		DISK ADDRESS SET IN MH
	!    BLISS CALLING SEQUENCE:	IF .FAILSOFTING THEN
	!				    CHKGH( .LEAFNO, .TSN, .SENDERID, .MHPTR )

	!    FUNCTION:	WRITE THE MESSAGE ON A FAILSOFT FILE
	!		AND MODIFY THE LEAF DISK ADDR TABLE

	GLOBAL ROUTINE CHKGH( LEAFNO, TSN, SENDERID, MHPTR)=
	    BEGIN
		REGISTER FIRSTMH,
			DSKGH;
		MAP FORMAT MHPTR;

		MHPTR[ M0DSKADDR ] _ FIRSTMH _ DSKALL( 1 );			! ALLOCATE THE FIRST MH OF THE GROUP

		DSKGH _ STOREDSKGH(.LEAFNO,.FIRSTMH,.TSN,.SENDERID,LEAFDSKTAB,.IBASE);
				!ADD ENTRY TO FAILSOFT FILE

		STORENEXTMH( .DSKGH, .FIRSTMH);		! ADD AN ENTRY TO THE NEXTMH TABLE

		DSKMSG( .MHPTR[ M0INCHNK ],		! FAILSOFT
			.MHPTR[ M0DATE ],				! THE CHUNK
			.MHPTR[ M0TIME],
			.DSKGH,
			.MHPTR[ M0SOT ]);

							! RETURN XWD
		.DSKGH				! OFFSETT,,DSKGHADDR

	    END;
	COMMENT;
	! 2. FAILSOFT MESSAGE
	! == ========== =======
	!    CALLING ARGUEMENTS:
	!		1) DISK ADDR OF DSKGH
	!		2) THE DATE THIS MESSAGE WAS RECEIVED
	!		3) THE TIME THIS MESSAGE WAS RECEIVED
	!		4) POINTER TO THE CHUNKS OF THE MESSAGE
	!		5) START OF TEXT
	!    RETURNS:			    LH) 0
	!				    RH) DISK ADDRESS OF THE MESSAGE
	!    BLISS CALLING SEQUENCE:	IF .FAILSOFTING THEN
	!				     CHKMSG(.DSKADDR,.DATE,.TIME,
	!					.CHUNKPTR,.SOT)

	!    FUNCTION:	WRITE THE MESSAGE ON A FAILSOFT FILE

	GLOBAL ROUTINE CHKMSG(DSKGHADDR,DATE,TIME,CHUNKPTR,SOT)=
	    BEGIN
		DSKMSG( .CHUNKPTR, .DATE, .TIME, .DSKGHADDR, .SOT)
	    END;
	COMMENT;
	! 3. GPURGE
	! == ======
	!    CALLING ARGUEMENTS:	DISK ADDRESS OF THE DSKGH
	!    RETURNS:			0 ALWAYS
	!    BLISS CALLING SEQUENCE:	GPURGE(.DSKADDR)

	!    FUNCTION:	PURGE THE GROUP FROM THE FAILSOFT/ROLLOUT FILE ON DISK

	GLOBAL ROUTINE GPURGE(DSKGHADDR)=
	    BEGIN
		REGISTER
			DSKMH;

		DSKMH _ DELDSKGH( .DSKGHADDR, LEAFTYPE);	! DELETE THE DISK GROUP HEADER
		DPAT();				! FORCE THE PARTICLE ALLOCATION TABLES TO BE WRITTEN
		! NOTE: IF THE SYSTEM CRASHES BEFORE THIS ROUTINE IS
		!	COMPLETED, THERE WILL BE LOST PARTICLES, BUT
		!	THE GROUP WILL BE DELETED AS FAR AS DSKGHTAB
		!	IS CONCERNED.

		ZAPDSKMHS( .DSKMH);			! KILL ALL THE MESSAGES IN THIS GROUP

		DPAT();				! FORCE THE PARTICLE ALLOCATION TABLES TO BE WRITTEN

	    END;
	COMMENT;
	! 4. ROLLIN
	! == ======
	!    CALLING ARGUEMENTS:	1) DISK ADDRESS OF MESSAGE TO ROLLIN
	!				2) THE DISK ADDRESS OF THE GROUP HEADER
	!				   FOR THIS MESSAGE
	!    RETURNS:			POINTER TO FIRST CHUNK OF THE MESSAGE
	!    BLISS CALLING SEQUENCE:	POINTER _ ROLIN(.DSKADDR,.DSKGHADDR)

	!    FUNCTION:	ROLL IN A MESSAGE FROM THE ROLLOUT OR FAILSOFT FILE
	!		MAKES CHUNKS IN CORE.

	GLOBAL ROUTINE ROLIN(DSKADDR, DSKGHADDR)=
	    BEGIN
		OWN	DSKMH[PTLSIZE];
		STRUCTURE	STR[I,J,K] = .STR[.I]<.J,.K>;
		MAP	STR DSKMH;
		LABEL	LOOP;

		REGISTER
			J,
			PTLTOREAD,
			CHUNKPTR,
			PTLLEFT;
		LOCAL
			FIRST,
			LAST,
			AMT,
			BUFFADDR;
		MAP	FORMAT CHUNKPTR;
		MAP	FORMAT BUFFADDR;
		MAP	FORMAT LAST;

		MOVE( DREAD( .DSKADDR), DSKMH, PTLSIZE);
						! GET THE FIRST DSKMH OF THE MSG TO ROLL IN

		J _ FIRST _ 0;			! INITALIZE

		PTLTOREAD _ .DSKMH[DM0RIPTL(.J)];	! GET RETRIEVAL INFORMATION (RI)
		PTLLEFT _ .DSKMH[DM0RICOUNT(.J)];

LOOP:		REPEAT
		    BEGIN
			BUFFADDR _ DREAD( .PTLTOREAD);	! READ IN A BUFFER
			CHUNKPTR _ GETMEM( AMT _ .BUFFADDR[C0WORDS] + C0HEADSIZE);
							! GET ROOM FOR THE CHUNK IN CORE
			MOVE( .BUFFADDR, .CHUNKPTR, .AMT);
							! AND MOVE IT INTO CORE

			IF .FIRST EQL 0 THEN		! IF FIRST CHUNK
				LAST _ FIRST _ .CHUNKPTR	! SAVE ADDR OF FIRST CHUNK
								! AND POINTER TO PREVIOUS
				ELSE LAST _ LAST[C0LINK] _ .CHUNKPTR;
								! ELSE LINK PREVIOUS CHUNK TO THIS ONE
			IF .CHUNKPTR[C0LINK] EQL 0 THEN LEAVE LOOP;
							! IF LAST CHUNK THEN GET OUT OF THIS LOOP

			! OTHERWISE GET NEXT PARTICLE TO READ
			IF (PTLLEFT _ .PTLLEFT - 1) GTR 0 THEN
				PTLTOREAD _ .PTLTOREAD + 1
			    ELSE
			    BEGIN
				J _ .J + 1;

				SELECT (PTLLEFT _ .DSKMH[DM0RICOUNT(.J)]) OF
				    NSET
					#777777: BEGIN
						MOVE( DREAD( .DSKMH[DM0RIPTL(.J)]), DSKMH, PTLSIZE);
						J _ 0;
						PTLLEFT _ .DSKMH[DM0RICOUNT(.J)];
						PTLTOREAD _ .DSKMH[DM0RIPTL(.J)]
						END;
					0:  INFORM ( PAZ 'FRI NOT FOUND@', 0, 0, 0, 0, 0);
					OTHERWISE: PTLTOREAD _ .DSKMH[DM0RIPTL(.J)];
				    TESN
			    END
		    END;

		.FIRST			! RETURN POINTER TO THE FIRST CHUNK

	    END;
	COMMENT;
	! 5. ROLLOUT GROUP HEADER
	! == ======= ===== ======
	!    FUNCTION:	FAILSOFT THE GROUP HEADER, MESSAGE HEADER, RETURN
	!		DISK ADDRESS. DEALLOCATE THE CORE BELONGING TO THE
	!		CHUNKS OF THE MESSAGE.

	GLOBAL ROUTINE ROLGH(LEAFNO, TSN, SENDERID, MHPTR)=
	    BEGIN
		EXTERNAL DCHNKS;
		REGISTER FIRSTMH,
			DSKGH;
		MAP FORMAT MHPTR;

		MHPTR[ M0DSKADDR ] _ FIRSTMH _ DSKALL( 1 ); ! ALLOCATE THE FIRST MH OF THE GROUP

		DSKGH _ STOREDSKGH(.LEAFNO,.FIRSTMH,.TSN,.SENDERID,LEAFDSKTAB,.IBASE);

		STORENEXTMH( .DSKGH, .FIRSTMH);		! ADD AN ENTRY TO THE NEXTMH TABLE

		DSKMSG( .MHPTR[ M0INCHNK ],0,0,.DSKGH,.MHPTR[ M0SOT ]);

		DCHNKS(.MHPTR);				! RETURN THE CHUNKS

		.DSKGH					! OFFSETT,,DSKGHADDR

	    END;
	COMMENT;
	! 6. ROLLOUT MESSAGE
	! == ======= =======
	!    CALLING ARGUEMENTS:	1) THE DISK GH ADDRESS
	!				2) POINTER TO FIRST CHUNK OF THE MESSAGE
	!				3) START OF TEXT
	!    RETURNS:			LH) 0
	!				RH) DISK ADDRESS OF THE MESSAGE
	!    BLISS CALLING SEQUENCE:	IF NOT .FAILSOFTING THEN
	!				    DSKADDR _ ROLMSG(.DSKGHADDR,.CHUNKPTR,.SOT)
	!    FUNCTION:	ALLOCATE DISK SPACE, INITIATE DISK WRITE, AND RETURN
	!		DISK ADDRESS. DEALLOCATE THE CORE BELONGING TO THE
	!		CHUNKS OF THE MESSAGE.

	GLOBAL ROUTINE ROLMSG(DSKGHADDR,CHUNKPTR,SOT)=
	    BEGIN
		REGISTER VALUE,
			CHNK;
		MAP FORMAT CHNK;

		VALUE _ DSKMSG( .CHUNKPTR, 0, 0, .DSKGHADDR, .SOT);
		CHNK _ .CHUNKPTR;
		DO DELCHNK(.CHNK) WHILE (CHNK _ .CHNK[ C0LINK ]) NEQ 0;
	      .VALUE
	    END;
	COMMENT;
	! 7. DEFERRED GROUP ROLLOUT
	! == ======== ===== =======
	!    CALLING ARGUEMENTS:	1) THE LOGICAL TERMINAL NUMBER
	!				2) THE DISK ADDRESS OF THE DSKMH
	!				3) THE TSN ASSIGNED
	!				4) THE SENDER
	!    RETURNS:			DISK GROUP ADDRESS
	!    BLISS CALLING SEQUENCE:	DSKADDR _ DROLGH(.SRCPTR[S0LTTYNO],
	!						.DSKMHADDR,
	!						.MHPTR[M0TSN],
	!						.MHPTR[M0SOT);

	!    FUNCTION:	TO WRITE A DISK GROUP HEADER AND LINK IT TO THE
	!		DSK DESTINATION POINTER TABLE

	GLOBAL ROUTINE DROLGH( LTTYNO, DSKMH, TSN, SENDERID)=
	    BEGIN
		STOREDSKGH( .LTTYNO, .DSKMH, .TSN, .SENDERID, TRMDSKTAB, .OBASE ) ! ENTER INTO FAILSOFT
	    END;
	COMMENT;

	! 8. DEFERRED MESSAGE ROLLOUT
	! == ======== ======= =======
	!    CALLING ARGUEMENTS:	1) POINTER TO THE FIRST CHUNK OF THE
	!				   MESSAGE
	!				2) THE DISK ADDRESS OF THE MESSAGE HEADER
	!				   OF THE LAST MESSAGE
	!    RETURNS:			LH) 0
	!				RH) DISK ADDRESS OF THE MESSAGE
	!    BLISS CALLING SEQUENCE:	DSKADDR _ DROLMSG(.CHUNKPTR,
	!					.DSKMHADDR)

	!    FUNCTION:	ALLOCATE DISK SPACE, INITIATE DISK WRITE, DELETES CHUNKS,
	!		 AND RETURN DISK ADDRESS.

	GLOBAL ROUTINE DROLMSG(CHUNKPTR, DSKMHADDR)=
	    BEGIN
		REGISTER
			NEXT,
			FIRSTMH,
			RET;

		MAP	FORMAT CHUNKPTR;

		RET _ IF .DSKMHADDR EQL 0 THEN
		    BEGIN
			FIRSTMH _ DSKALL( 1 );			! ALLOCATE THE FIRST MH OF THE GROUP
			FIRSTMH _ .FIRSTMH<RH>;			!WANT ONLY PARTICLE NUMBER

			STORENEXTMH( .FIRSTMH, .FIRSTMH);	! ADD AN ENTRY TO THE NEXTMH TABLE

			DSKMSG( .CHUNKPTR,			! FAILSOFT
				0,				! THE CHUNK
				0,
				.FIRSTMH,
				0 )
		    END
		    ELSE DSKMSG( .CHUNKPTR, 0, 0, .DSKMHADDR, 0);

		WHILE .CHUNKPTR NEQ 0 DO
		    BEGIN
			NEXT _ .CHUNKPTR[C0LINK];
			DELCHNK( .CHUNKPTR );
			CHUNKPTR _ .NEXT
		    END;

		.RET

	    END;
	COMMENT;
	! 9. DEFERRED GROUP DELETION
	! == ======== ===== ========
	!    CALLING ARGUEMENTS:	1) DISK ADDRESS OF GROUP TO DELETE
	!				2) SAVE FLAG - IF TRUE THEN DON'T DELETE
	!				   THE MESSAGES OF THIS GROUP
	!				   BECAUSE THEY BELONG TO SOMEONE ELSE
	!				   ALSO
	!    RETURNS:			0 ALWAYS
	!    BLISS CALLING SEQUENCE:	POINTER _ DGPURGE(.DSKGHADDR,.SAVEFLAG)

	!    FUNCTION:	

	GLOBAL ROUTINE DGPURGE(DSKGHADDR,SAVEFLAG)=
	    BEGIN
		REGISTER
			DSKMH;

		DSKMH _ DELDSKGH( .DSKGHADDR, TERMINAL );		! DELETE THE GROUP HEADER

		DPAT();				! FORCE THE PARTICLE ALLOCATION TABLES TO BE WRITTEN

		IF .SAVEFLAG THEN RETURN;

		ZAPDSKMHS( .DSKMH );		! THEN DELETE THE MESSAGES

		DPAT();				! FORCE THE PARTICLE ALLOCATION TABLES TO BE WRITTEN

	    END;
	COMMENT;
	! AUX ROUTINES

	! ROUTINE DSKMSG
	! ======= ======

	! THIS ROUTINE WRITES A MESSAGE TO THE DISK.
	! IT CREATES A DSKMH, AND WRITES EACH PARTICLE OUT, STORING
	! THE PARTICLE'S ADDRESS IN THE DSKMH.  WHEN ALL THE PARTICLES
	! ARE WRITTEN, THE DSKMH IS ITSELF WRITTEN TO DISK

	ROUTINE DSKMSG( CHUNKPTR, DATE, TIME, DSKGHADDR, SOT) =
	    BEGIN
		OWN	DSKMH[PTLSIZE];
		STRUCTURE	STR[I,J,K] = .STR[.I]<.J,.K>;
		MAP	STR DSKMH;

		LABEL	LOOP;
		REGISTER
			BUFFADDR,
			PTLLEFT,
			J,
			PTLTOFILL;
		LOCAL	NEXTMH,
			DSKMHPTL,
			INDEX,
			PTLREQ,
			CHUNK,
			ENDI,
			PTLGOTTEN;
		MAP	FORMAT INDEX;
		MAP	FORMAT CHUNK;
		MAP	FORMAT BUFFADDR;
		MAP	FORMAT CHUNKPTR;

		IF (INDEX _ FINDNEXTMH( .DSKGHADDR)) EQL -1 THEN
			INFORM( PAZ 'FDSKMSG CALLED BUT NO NEXT GH@',0,0,0,0,0);
		DSKMHPTL _ .INDEX[ NM0NEXTMH ];
		ZERO( DSKMH, DSKMH + PTLSIZE - 1);

		DSKMH[DM0DATE] _ .DATE;
		DSKMH[DM0TIME] _ .TIME;
		DSKMH[DM0DSKGHADDR] _ .DSKGHADDR;
		DSKMH[DM0SOT] _ .SOT;

		PTLREQ _ 0;
		CHUNK _ .CHUNKPTR;

		WHILE .CHUNK NEQ 0 DO
		    BEGIN
			PTLREQ _ .PTLREQ + 1;
			ENDI _ .CHUNK[ C0ENDI];
			CHUNK _ .CHUNK[C0LINK]
		    END;

		DSKMH[DM0ENDI] _ .ENDI;

		IF .ENDI GEQ EGI THEN
		    BEGIN
			INDEX[ NM0GHADDR ] _ INDEX[ NM0NEXTMH ] _ NEXTMH _ 0
		    END
		    ELSE
		    BEGIN
			NEXTMH _ INDEX[ NM0NEXTMH ] _ DSKALL( 1 );
		    END;

		J _ PTLLEFT _ BUFFADDR _ 0;

		WHILE .CHUNKPTR NEQ 0 DO
		    BEGIN
			! GET PTL TO FILL
			IF (PTLLEFT _ .PTLLEFT - 1) GTR 0 THEN PTLTOFILL _ .PTLTOFILL + 1
			    ELSE
			    BEGIN
LOOP:				REPEAT
				    BEGIN

					PTLGOTTEN _ DSKALL( .PTLREQ);
					PTLLEFT _ .PTLGOTTEN<LH>;
					PTLTOFILL _ .PTLGOTTEN<RH>;
					PTLREQ _ .PTLREQ - .PTLLEFT;

					IF .J EQL LASTRI AND .PTLREQ NEQ 0 THEN
					    BEGIN
						DSKMH[DM0RICOUNT(.J)] _ -1;
						DSKMH[DM0RIPTL(.J)] _ .PTLTOFILL;
						PTLREQ _ .PTLREQ + .PTLLEFT;
						BUFFADDR _ DGET( .DSKMHPTL);
						MOVE( DSKMH, .BUFFADDR, PTLSIZE);
						DWRITE( .BUFFADDR, .DSKMHPTL);
						ZERO( DSKMH + FIRSTRI, DSKMH + PTLSIZE - 1); !ZAP RI'S IN NEW DSKMH
						DSKMHPTL _ PTLTOFILL;
						IF .PTLLEFT GTR 1 THEN DSKDEA(.PTLLEFT-1,.PTLTOFILL+1);
						J _ 0
					    END
					    ELSE
					    BEGIN
						DSKMH[DM0RI(.J)] _ .PTLGOTTEN;
						J _ .J + 1;
						LEAVE LOOP
					    END
				    END
			    END;

			! GOT PTL TO FILL
			BUFFADDR _ DGET( .PTLTOFILL);	! GET THE BUFFER TO STUFF
			MOVE( .CHUNKPTR, .BUFFADDR<RH>, .CHUNKPTR[C0WORDS] + C0HEADSIZE);	! AND STUFF AWAY
			DWRITE( .BUFFADDR<RH>, .PTLTOFILL);	! THEN SEND IT OUT
			CHUNKPTR _ .CHUNKPTR[C0LINK]
		    END;

		DSKMH[DM0NEXTMH] _ .NEXTMH;

		BUFFADDR _ DGET( .DSKMHPTL);
		MOVE( DSKMH, .BUFFADDR, PTLSIZE);
		DWRITE( .BUFFADDR, .DSKMHPTL);

		IF .NEXTMH NEQ 0 THEN	!IF AWAITING MORE INPUT
			BEGIN
				BUFFADDR _ DGET(.NEXTMH); !GET THE NEXT MH PARTICLE
				ZERO(.BUFFADDR, .BUFFADDR + PTLSIZE -1); !AND CLEAR IT
				DWRITE(.BUFFADDR, .NEXTMH) !CAUSE IT'S REALLY EMPTY
			END;

		DPAT();					!FORCE OUT FAILSOFT FILE
		.DSKMHPTL
	    END;
	COMMENT;
	
	! ROUTINE ZAPDSKMHS
	! ======= =========
	
	! THIS ROUTINE DELETES ALL THE MESSAGES OF A GIVEN HEAD MH
	
	ROUTINE ZAPDSKMHS( DSKMH) =
	    BEGIN
	
		REGISTER
			BUFFADDR,
			NEXTMH,
			PREVMH;

		LOCAL
			COUNT;
	
		MAP	FORMAT BUFFADDR;
	
		LABEL
			LOOP,
			BIGLOOP;
	
		BUFFADDR _ DREAD(.DSKMH);		! MAKE SURE THE BUFFER IS STILL AVAILABLE
		NEXTMH _ .BUFFADDR[DM0NEXTMH];		! SAVE THE NEXT ONE TO GET
	
	BIGLOOP:
		REPEAT					! FOR EACH DSKMH IN THIS GROUP
		    BEGIN
	LOOP:		INCR RI FROM 0 TO LASTRI DO	! ZAP ALL THE CHUNKS IN EACH
			    BEGIN
				SELECT (COUNT _ .BUFFADDR[DM0RICOUNT( .RI)] ) OF
				    NSET
					#777777: BEGIN	! IF EXTENDED DSKMH, PICK UP THE EXTENSION
						PREVMH _ .DSKMH;
						DSKMH _ .BUFFADDR[DM0RIPTL(.RI)];
						BUFFADDR _ DREAD( .DSKMH);
						DSKDEA( 1, .PREVMH);
						RI _ -1		! RESTART THE INCR LOOP
					    END;
					0:  LEAVE LOOP;	! IF NONE LEFT THEN DONE
							! ALSO DONE IF INCR LOOP FINISHES
					OTHERWISE: DSKDEA( .COUNT, .BUFFADDR[DM0RIPTL(.RI)]);
							! IF NORMAL RI THEN ZAP THE CHUNKS/PARTICLES
				    TESN
			    END;
			DSKDEA( 1, .DSKMH);		! ZAP THE DSKMH
			IF (DSKMH _ .NEXTMH) EQL 0 THEN LEAVE BIGLOOP;
							! IF OUT OF DSKMH'S THEN DONE
			BUFFADDR _ DREAD( .DSKMH);	! OTHERWISE TRY ANOTHER
			NEXTMH _ .BUFFADDR[DM0NEXTMH]
		    END
	
	    END;
	COMMENT;

	! ROUTINE STOREDSKGH
	! ======= ==========

	! THIS ROUTINE PUTS AN ENTRY INTO THE DSKGHTAB OF THE TABLE.

	ROUTINE STOREDSKGH( LOFFSET, MHPTR, TSN, SENDERID, GHTABLE, BASEPTL ) =
	    BEGIN

		MACRO
			STOREARGS(X) =
					BUFFADDR[DG0MH( X)] _ .MHPTR;
					BUFFADDR[DG0TSN( X)] _ .TSN;
					BUFFADDR[DG0SENDER( X)] _ .SENDERID$;

		MACRO MAKENEW( LINKS, LOFFSET ) =
		    BEGIN
			BUFFADDR _ DGET(DSKALL(1));			! GET A PARTICLE FOR A DSKGH
			ZERO( .BUFFADDR, .BUFFADDR + PTLSIZE - 1);	! ZERO IT
			BUFFADDR[DG0LINKS] _ LINKS;			! STORE THE FORWARD AND REVERSE LINKS
			BUFFADDR[DG0LEAF] _ LOFFSET;			! STORE A POINTER TO THE OWNER
			STOREARGS(1);					! STORE THE INFO ABOUT THE CORE GH
			DWRITE( .BUFFADDR<RH>, .BUFFADDR<LH>);		! WRITE IT OUT
			(.GHTABLE + LOFFSET)<RH> _ .BUFFADDR<LH>;	! SAVE THE PTL NUMBER
			J _ 1;						! AND MARK THE OFFSET IN THE PTL AS ONE

			.BUFFADDR<LH>					! RETURN THE PARTICLE NUMBER
		    END$;

		REGISTER
			BUFFADDR,
			J,
			PTL,
			LAST;
		LOCAL	LINKS,
			RET;
		MAP	FORMAT BUFFADDR;

		! BEGIN !

		IF .(.GHTABLE + .LOFFSET) EQL 0 THEN			! IS THERE ALREADY A DSKGH
		    BEGIN						! *NO* THEN
			PTL _ MAKENEW( 0, .LOFFSET);			! MAKE A DSKGH
			BUFFADDR _ DREAD( .BASEPTL + .LOFFSET / LEAFPERPTL); !PUT IT IN THE DISK LEAF POINTER TABLE
			BUFFADDR[DD0LEAF((.LOFFSET MOD LEAFPERPTL))] _ .PTL;	! ...
			DWRITE( .BUFFADDR<RH>, .BUFFADDR<LH>)		! ...
		    END
		    ELSE
		    BEGIN						! *YES* ALREADY A DSKGH
			IF ( J _ .(.GHTABLE + .LOFFSET)<LH>) EQL 0 THEN	! ANY ROOM LEFT IN IT
			    BEGIN					! *NO*
				LINKS _ 0;				! MAKE NEW LINKAGE
				LAST _ LINKS<LH> _ .(.GHTABLE + .LOFFSET)<RH>;	! GET PTL NUMBER OF LAST DSKGH
				PTL _ MAKENEW( .LINKS, .LOFFSET);	! MAKE A NEW DSKGH
				BUFFADDR _ DREAD( .LAST);		! READ THE OLD ONE
				BUFFADDR[ DG0NEXT] _ .PTL;		! LINK THE OLD TO THE NEW
				DWRITE( .BUFFADDR, .LAST)		! WRITE THE OLD
			    END
			    ELSE
			    BEGIN					! *YES* ROOM LEFT IN THE DSKGH
				BUFFADDR _ DREAD( PTL _ .(.GHTABLE + .LOFFSET)<RH>);	! READ THE OLD DSKGH
				STOREARGS(.J);				! STORE WHAT WE'RE HERE TO SAVE
				DWRITE( .BUFFADDR<RH>, .BUFFADDR<LH>)	! WRITE THE OLD DSKGH
			    END
		    END;

		RET<LH> _ .J;
		RET<RH> _ .PTL;
		(.GHTABLE + .LOFFSET)<LH> _ IF (J _ .J + 1) GTR DG0MAX THEN 0 ELSE .J;
				! STORE THE NEXT AVAILABLE SLOT ( IF ANY )
				! IN THE DSKGH

		DPAT();		!FORCE OUT DSKGHTABLE

		.RET

	    END;
	COMMENT;
	! ROUTINE DELDSKGH
	! ======= =========

	! THIS ROUTINE REMOVES AN ENTRY FROM THE LEAFGHTAB OR TRMGHTAB AND CLEANS
	! UP AS REQUIRED

	GLOBAL ROUTINE DELDSKGH( PAIR, WHICHTAB ) =
	    BEGIN
		REGISTER
			BUFFADDR,
			PREV,
			NEXT;
		MAP	FORMAT BUFFADDR;

		LABEL	LOOP;

		LOCAL	OFFSETT,
			GH,
			PTL,
			DSKMH,
			LEAFNO;


							! SPLIT PAIR INTO PTL AND INDEX INTO PTL
		PTL _ .PAIR<RH>;
		OFFSETT _ .PAIR<LH>;

		BUFFADDR _ DREAD( .PTL);		! READ THE DSKGHTAB PARTICLE

		DSKMH _ .BUFFADDR[DG0MH( .OFFSETT)];	! GET THE POINTER TO THE DSKMH

		GH _ BUFFADDR[DG0ENTRY( .OFFSETT)];	! GET A POINTER TO THE ENTRY WE ARE INTERESTED IN
		ZERO( .GH, .GH + DG0SIZE - 1);		! AND ZAP IT

		IF					! SEE IF THE WHOLE PARTICLE IS ZERO
LOOP:		    BEGIN
			INCR I FROM BUFFADDR[DG0ENTRY(1)] TO BUFFADDR[DG0ENTRY(DG0MAX)] DO
				IF @@I NEQ 0 THEN LEAVE LOOP WITH TRUE;
			FALSE
		    END
		THEN	RETURN( DWRITE( .BUFFADDR<RH>, .PTL); .DSKMH );	! IF NOT ALL ZERO REWRITE IT AND EXIT

		NEXT _ .BUFFADDR[DG0NEXT];
		PREV _ .BUFFADDR[DG0PREV];

		IF .NEXT EQL 0 THEN	! NEVER REMOVE THE LAST TABLE
		    BEGIN
			IF .WHICHTAB EQL TERMINAL THEN
				TRMDSKTAB[ .BUFFADDR[DG0LEAF]] _ 1 ^ 18 + .PTL
			    ELSE
				LEAFDSKTAB[ .BUFFADDR[DG0LEAF]] _ 1 ^ 18 + .PTL;
			RETURN( DWRITE( .BUFFADDR, .PTL); .DSKMH )
		    END;

		IF .PREV NEQ 0 THEN
		    BEGIN
			BUFFADDR _ DREAD( .PREV);

			BUFFADDR[ DG0NEXT] _ .NEXT;

			DWRITE( .BUFFADDR, .PREV)
		    END
		    ELSE
		    BEGIN
			LEAFNO _ .BUFFADDR[ DG0LEAF ];
			IF .WHICHTAB EQL TERMINAL THEN
			    BEGIN
				BUFFADDR _ DREAD( .OBASE + .LEAFNO / LEAFPERPTL);
				BUFFADDR[DD0LEAF((.LEAFNO MOD LEAFPERPTL))] _ .NEXT;
				DWRITE( .BUFFADDR<RH>, .BUFFADDR<LH>)
			    END
			    ELSE
			    BEGIN
				BUFFADDR _ DREAD( .IBASE + .LEAFNO / LEAFPERPTL);
				BUFFADDR[DD0LEAF((.LEAFNO MOD LEAFPERPTL))] _ .NEXT;
				DWRITE( .BUFFADDR<RH>, .BUFFADDR<LH>)
			    END
		    END;


		IF .NEXT NEQ 0 THEN
		    BEGIN
			BUFFADDR _ DREAD( .NEXT);

			BUFFADDR[DG0PREV] _ .PREV;

			DWRITE( .BUFFADDR, .NEXT)
		    END;

		DSKDEA( 1, .PTL);					! AND DEALLOCATE IT

		.DSKMH

	    END;
	COMMENT;
	OWN	NEXTMHTAB;

	! ROUTINE STORENEXTMH
	! ======= ===========

	! THIS ROUTINE FINDS AN AVAILABLE LOCATION IN THE NEXTMHTABLE
	! AND STORE THE VALUE GIVEN THERE

	ROUTINE STORENEXTMH( DSKGHADDR, MHPTR ) =
	    BEGIN
		REGISTER
			ADDR;
		MAP	FORMAT ADDR;

		IF (ADDR _ FINDNEXTMH(.DSKGHADDR)) NEQ -1 THEN
			BEGIN
				ADDR[NM0NEXTMH] _ .MHPTR;
				RETURN
			END;

		IF (ADDR _ .NEXTMHTAB) EQL 0 THEN	! IF NO TABLE YET THEN
		    BEGIN
			ADDR _ NEXTMHTAB _ GETMEM( NM0SIZE);	! MAKE SOME ROOM
			ZERO( .ADDR, .ADDR + NM0SIZE - 1);	! AND ZAP IT
			ADDR[NM0END0] _ -1;	! MARK THE END
			ADDR[NM0END1] _ -1;	! MARK THE END
			ADDR[NM0GHADDR] _ .DSKGHADDR;	! STORE WHAT WE WANTED
			ADDR[NM0NEXTMH] _ .MHPTR;	! SAVE MH'S
			RETURN				! ALL DONE FOR NOW
		    END;

		REPEAT					! IF WE HAVE A TABLE ALREADY THEN
		    BEGIN				! UNTIL WE FIND A PLACE TO PUT THE DSKGHADDR GO
			SELECT .ADDR[NM0GHADDR] OF
			    NSET
				-1: IF .ADDR[NM0NEXTMH] EQL #777777 THEN	! IF END OF TABLE THEN
					BEGIN
					    ADDR _ ADDR[NM0NEXTMH]  _ GETMEM( NM0SIZE);	! MAKE SOME ROOM
					    ZERO( .ADDR, .ADDR + NM0SIZE - 1);	! AND ZAP IT
					    ADDR[NM0END0] _ -1;	! MARK THE END
					    ADDR[NM0END1] _ -1;	! MARK THE END
					    ADDR[NM0GHADDR] _ .DSKGHADDR;	! STORE WHAT WE WANTED
					    ADDR[NM0NEXTMH] _ .MHPTR;	! SAVE MH'S
					    RETURN		! ALL DONE FOR NOW
					END
					ELSE ADDR _ .ADDR[NM0NEXTMH]; !STEP TO NEXT TABLE

				0:  ( ADDR[NM0GHADDR] _ .DSKGHADDR;
					ADDR[NM0NEXTMH] _ .MHPTR;	! SAVE MH'S
					RETURN);	! FOUND A PLACE SO STORE WHAT WE WANTED
				OTHERWISE: ADDR _ .ADDR + NM0ENTRYSIZE;		! OTHERWISE TRY THE NEXT LOCATION
			    TESN
		    END
	    END;

	COMMENT;
	! ROUTINE FINDNEXTMH
	! ======= ==========
	! THIS ROUTINE IS USED TO SEARCH THE NEXTMHTAB TO FIND THE NEXT
	! DSKMH TO USE FOR THE GROUP WHOSE DSKMH IS PROVIDED.

	! RETURNS:	INDEX INTO THE NEXTMH TABLE

	ROUTINE FINDNEXTMH( DSKGHADDR) =
	    BEGIN
		REGISTER
			ADDR,
			GHADDR;
		MAP	FORMAT ADDR;

		GHADDR _ .DSKGHADDR;			! PUT THE DSKGHADDR IN A REGISTER FOR SPEED

		IF (ADDR _ .NEXTMHTAB) EQL 0 THEN RETURN -1;	! ERROR RETURN (NO TABLE)

		REPEAT						! UNTIL MATCH OR END OF TABLE DO
		    BEGIN
			SELECT .ADDR[NM0GHADDR] OF
			    NSET
				-1: IF (ADDR _ .ADDR[NM0NEXTMH]) EQL #777777 THEN RETURN -1;	! OOPS END OF TABLE
				.GHADDR: RETURN .ADDR;				! JUST WHAT WE WANTED
				OTHERWISE: ADDR _ .ADDR + NM0ENTRYSIZE;			! TRY ANOTHER
			    TESN
		    END

	    END;
COMMENT;

! THIS FILE CONTAINS THE ROUTINES USED TO WRITE THE LOG OR AUDIT
! FILES.  THESE ROUTINES TAKE RELATIVELY COMPLEX CALLS AND
! BREAK THEM DOWN INTO PRIMATIVE WRITE WORD AND WRITE VECTOR FOR KERNEL
! CALLS


	COMMENT;
	! 1. INLOGMSG
	! == ========
	!    CALLING ARGUEMENTS:	1) THE TRANSACTION SEQUENCE NUMBER OF
	!				   THE MESSAGE (ONE WORD)
	!				2) THE DATE THIS MESSAGE WAS RECEIVED
	!				   (ONE WORD, EQUIVALENT OF RESULT OF
	!				   DATE UUO)
	!				3) THE TIME THIS MESSAGE WAS RECEIVED
	!				   (ONE WORD, EQUIVALENT OF RESULT OF
	!				   MSTIME UUO)
	!				4) POINTER TO THE LOGICAL NAME OF THE
	!				   TERMINAL WHO SENT THIS MESSAGE
	!				   (THE LEFT HALF OF THE GLOBAL SYMBOL
	!				   SRCTAB CONTAINS THE MAXIMUM NUMBER OF
	!				   CHARACTERS IN A LOGICAL SOURCE NAME)
	!				5) POINTER TO THE TRANSACTION CODE OF
	!				   THE MESSAGE (TRCODELENGTH WILL BE
	!				   A GLOBAL SYMBOL WHICH CONTAINS THE
	!				   MAXIMUM LENGTH OF A TRANSACTION CODE)
	!				6) A POINTER TO THE FIRST CHUNK OF THE
	!				   MESSAGE
	!    RETURNS:			0 ALWAYS
	!    BLISS CALLING SEQUENCE:	IF .INLOGGING THEN INLOGMSG(.TSN,.DATE,
	!				    .TIME,.SENDERID,.TRCODEPTR,.CHUNKPTR)

	!    FUNCTION:	WRITE THE LOG FILE (IF THE USER HAS REQUESTED IT AND
	!		THE LOGGING DEVICE IS AVAILABLE)


	!    THE FORMAT OF A LOG FILE RECORD:

	!	WORD 1: -1
	!	WORD 2:	WORD COUNT OF THE WORDS IN THIS RECORD (M+1)
	!	WORD 2:	CHECKSUM OF THE WORDS IN THIS RECORDS (EXCLUDING THE CHECKSUM WORDS)
	!	WORD 3:	TRANSACTION SEQUENCE NUMBER OF THIS MESSAGE
	!	WORD 4:	DATE OF THIS MESSAGE
	!	WORD 5:	TIME THIS MESSAGE WAS RECEIVED
	!	WORDS 6 THRU 8:
	!		SOURCE TERMINAL NAME
	!	WORD 9:	SIZE OF THE TRANSACTION CODE FIELD
	!	WORD 10 THRU WORD (10 + CONTENTS OF WORD 9):
	!		THE TRANSACTION CODE OF THIS MESSAGE
	!	WORD (11 + CONTENTS OF WORD 9):
	!		THE NUMBER OF CHUNKS TO FOLLOW
	!	WORD (12+CONTENTS OF WORD 9) THRU WORD M
	!		THE CHUNKS OF THIS MESSAGE
	!		(M DEPENDS ON THE LENGTH AND NUMBER OF THE CHUNKS)
	!	WORD M+1: -1

	GLOBAL ROUTINE INLOGMSG(TSN,DATE,TIME,SENDERPTR,TRCODEPTR,CHUNKPTR)=
	    BEGIN

		REGISTER
			CHUNK,
			COUNT,
			CHUNKCOUNT;

		MAP FORMAT CHUNKPTR;
		MAP FORMAT CHUNK;


		COUNT _ 11 + .TRCW;		! LENGTH OF RECORD IS 11 + TRANSACTION CODE LENGTH + MSG LENGTHS
		CHUNKCOUNT _ 0;
		CHUNK _ .CHUNKPTR;
		DO
		    BEGIN
			CHUNKCOUNT _ .CHUNKCOUNT + 1;
			COUNT _ .COUNT + ( .CHUNK[C0WORDS] + C0HEADSIZE )
		    END
		WHILE (CHUNK _ .CHUNK[C0LINK]) NEQ 0;

		INAUDW( -1 );
		INAUDW(.COUNT);
		INAUDW(.TSN);
		INAUDW(.DATE);
		INAUDW(.TIME);
		INAUDVECTOR( .SENDERPTR<RH>, SRCNAMLEN);	! OUTPUT THE SENDER ID.
		INAUDW( .TRCLEN );				! WRITE THE TRANSACTION CODE LENGTH
		SELECT .TRCODEPTR OF
		   NSET
			0:	DECR I FROM .TRCW - 1 TO 0 DO INAUDW(0);
			#777777: DECR I FROM .TRCW - 1 TO 0 DO INAUDW(.(PAZ '??????????'));
			OTHERWISE: INAUDVECTOR(.TRCODEPTR<RH>,.TRCW);
		    TESN;
		INAUDW( .CHUNKCOUNT );

		DO
		    BEGIN
			INAUDVECTOR(.CHUNKPTR<RH>, .CHUNKPTR[C0WORDS] + C0HEADSIZE)
		    END
		WHILE (CHUNKPTR _ .CHUNKPTR[C0LINK]) NEQ 0;
		INAUDW( -1 );			! OUTPUT THE FINAL -1

	    END;			! END OF INLOGMSG
	COMMENT;
	! 2. OUTLOGMSG
	! == =========
	!    CALLING ARGUEMENTS:	1) THE TRANSACTION SEQUENCE NUMBER OF
	!				   THE MESSAGE
	!				2) THE DATE THIS MESSAGE WAS SENT
	!				3) THE TIME THIS MESSAGE WAS SENT
	!				4) POINTER TO THE MESSAGE CLASS OF THIS
	!				   MESSAGE IN THE LIBOL-MCS PAGE (THE
	!				   MESSAGE CLASS IS ALWAYS EIGHT
	!				   CHARACTERS LONG)
	!				5) THE JSN OF THE JOB WHICH SENT
	!				   THIS MSG
	!				6) THE DESTINATION COUNT
	!				7) POINTER TO THE DESTINATION TABLE IN
	!				   THE LIBOL-MCS COMMUNICATIONS PAGE
	!				   (SEE THE LIBOL SPEC FOR THE FORMAT)
	!				   THIS PAGE CONTAINS THE LOGICAL
	!				   DESTINATION NAMES.
	!				8) A POINTER TO THE FIRST CHUNK OF THE
	!				   MESSAGE
	!    RETURNS:			0 ALWAYS
	!    BLISS CALLING SEQUENCE:	IF .OUTLOGGING THEN OUTLOGMSG(.TSN,
	!				    .DATE,.TIME,.MSGCLASS,.JSN,
	!				     .DESTCNT,.DESTTAB,.CHUNKPTR)

	!    FUNCTION:	WRITE THE LOG FILE (IF THE USER HAS REQUESTED IT AND
	!		THE LOGGING DEVICE IS AVAILABLE)


	!    THE FORMAT OF A LOG FILE RECORD:

	!	WORD 1:	-1
	!	WORD 2: WORD COUNT OF THE WORDS IN THIS RECORD
	!	WORD 3:	TRANSACTION SEQUENCE NUMBER OF THIS MESSAGE
	!	WORD 4:	DATE OF THIS MESSAGE
	!	WORD 5:	TIME THIS MESSAGE WAS RECEIVED
	!	WORD 6 THRU WORD 7:
	!		MESSAGE CLASS
	!	WORD 8 AND 9:	DEVICE NAME (ASCIZ)
	!	WORD 10 AND 11:	FILE NAME (ASCIZ)
	!	WORD 12:	PPN (HALF WORD PAIR)
	!	WORD 13:	A COUNT OF THE NUMBER OF DESTINATIONS ( CALL THE
	!		CONTENTS OF THIS WORD N)
	!	WORD 14 THRU WORD 14+N*3:
	!		DESTINATION NAMES
	!	WORD 15+N*3:
	!		THE NUMBER OF CHUNKS TO FOLLOW
	!	WORD 16+N*3 THRU WORD M:
	!		THE CHUNKS OF THIS MESSAGE
	!		(M DEPENDS ON THE LENGTH AND NUMBER OF THE CHUNKS)
	!	WORD M+1: -1

	!    NOTE:	THE TWO TYPES OF LOGS ARE DISTINGUISHABLE BECAUSE THE
	!		TRANSACTION SEQUENCE NUMBER OF AN INPUT MESSAGE HAS A
	!		ZERO RIGHT HALF, AND AN OUTPUT MESSAGE HAS A NON-ZERO
	!		RIGHT HALF

	GLOBAL ROUTINE OUTLOGMSG(TSN,DATE,TIME,MSGCLASSPTR,JSN,DESTCOUNT,DESTTABPTR,CHUNKPTR)=
	    BEGIN

		EXTERNAL	?JB$MPP;	! CONTAINS A POINTER TO THE MPP PROTOTYPE
						! INDEXED BY THE JSN

		BIND	DEV = 1,
			NAME = 3,
			PPN = 5,

			DEBUGMPP = PLIT(	0,		! DEBUGGING MPP'S JUST GET THIS IN THE JOURNAL NOW
						0,
						0,
						'DEBUG',
						0,
						0 );


		REGISTER
			COUNT,
			CHUNKCOUNT,
			PROTOBLOCK,
			CHUNK;

		MAP FORMAT CHUNKPTR;
		MAP FORMAT CHUNK;



		IF (PROTOBLOCK _ .?JB$MPP[.JSN] ) EQL 0 THEN PROTOBLOCK _ DEBUGMPP;

		COUNT _ 15 + ( .DESTCOUNT * 3 );	! RECORD LENGTH IS 15 + DESTINATION TABLE SIZE + MSG LENGTHS

		CHUNKCOUNT _ 0;
		IF ( CHUNK _ .CHUNKPTR ) NEQ 0 THEN
		DO
		    BEGIN
			CHUNKCOUNT _ .CHUNKCOUNT + 1;
			COUNT _ .COUNT + ( .CHUNK[C0WORDS] + C0HEADSIZE )
		    END
		WHILE (CHUNK _ .CHUNK[C0LINK]) NEQ 0;


		OTAUDW( -1 );
		OTAUDW(.COUNT);
		OTAUDW(.TSN);
		OTAUDW(.DATE);
		OTAUDW(.TIME);
		OTAUDVECTOR( .MSGCLASSPTR<RH>, 2);			! OUTPUT MESSAGE CLASS
		OTAUDVECTOR( (.PROTOBLOCK)[DEV], 2);			! OUTPUT THE MPPSPEC
		OTAUDVECTOR( (.PROTOBLOCK)[NAME], 2);
		OTAUDW( .(.PROTOBLOCK)[PPN]);
		OTAUDW( .DESTCOUNT );
		OTAUDVECTOR( .DESTTABPTR<RH>, .DESTCOUNT * 3);		! OUTPUT THE DESTTAB

		OTAUDW( .CHUNKCOUNT );

		IF .CHUNKPTR NEQ 0 THEN DO
		    BEGIN
			OTAUDVECTOR( .CHUNKPTR<RH>, .CHUNKPTR[C0WORDS] + C0HEADSIZE )
		    END
		WHILE (CHUNKPTR _ .CHUNKPTR[C0LINK]) NEQ 0;
		OTAUDW( -1 )

	    END;			! END OF OUTLOGMSG
END;