;-*- Mode: Midas; Fonts: MEDFNT -*-
SUBTTL Basic symbol defs and insrts
.SYMTAB 5001.,7000.

;; WARNING:	This is the experimental version of COMSAT using the DQ:
;;		resolver device.  Among other things, this code assumes
;;		that RESOLV"HSTADR will -always- return the Chaosnet
;;		address of a machine over the Internet address if there
;;		is a choice (the alternative is to do twice as many resolves
;;		and do string comparisons).  Yukko scuz.  Should be fixed
;;		as soon as somebody has a better method.

TITLE NEW COMSAT
	; System Communications Satellite
	;(analogy pat. pend.-- running job has various actual sat. names)

	F=0	; Flags
	A=1	; A-E consecutive utility regs
	B=2	;	( routines save all not used for value returning,
	C=3	;	  so as to minimize clobberage)
	D=4
	E=5
	;=6	; Someday...
	MF=7	; Message flags (perhaps flush this)
	N=10	; Index to current net host
	L=11	; Base register into index table for LSE addressing

	OC=12	; Output Channel - used by OUT package.
	U1=13	; UU   UU   UU   UU    OOOOO
	U2=14	; UU   UU   UU   UU   OO   OO    HANDLER
	U3=15	; UU   UU   UU   UU   OO   OO	    ACCS
	U4=16	;  UUUUU     UUUUU     OOOOO

	P=17	; PDL ptr

; "Hard" channel assignments
	LOCKCH==0	; LOCK device channel for inbox locking
	NETD==1		; Net data channel for ICP and MLFL
	NETI==2		; Net input (telnet) channel
	NETO==3		; Net output(telnet) channel
	DKIC==4		; Disk input
	DKOC==5		; Disk output
	SC==6		; Stats disk output
	DMPCH==7	; Disk output channel for pdump'ing self.
	CLIC==10	; CORE link channel (output only).
	ERRCHN==11	; ERR device channel.
	USRI==12	; Inferior input.
	USRO==13	; Inferior output.
	DBC==14		; Debugging output channel.
	LSR1C==15	; Channel open to LSR1 file.
	DQCH==16	; Channel to DQ: resolver device

;; UUO "channels"
	 TMPC==17	; Temporary UUO chan for short jobs.
			; Is theoreticly a UUO channel only, but in fact
			; is used as a real channel in a few places, sigh.

	 SAOCH==20	; Another UUO channel for output into String LN's.
			; (That's right, channel 20, so don't use elsewhere!)
			; Perhaps merge the two, or better yet introduce
			; DEFCHN, DEFCHX macros?? (Latter for #'s > 17)
	
	;LH flag vars
;==1
;==2
%QULOS==200	; Set when flushing messages for bad host from queue
%PGMRN==400	; Set while running inferior job.
%INCLI==1000	; Set when inside CLISND (realt flag)
%MSTMD==2000	; Set when MASTER modified.
%LSRIN==4000	; Set when LSR full-name data mapped into core.
%SCOPN==10000	; Set when SC channel open
%MSGMD==20000	; Set when current MSG-LSE modified
%QMLMD==40000	; Set when QML modified.
%SCHED==100000	; Set when scheduler is sleeping; flag for REALT interrupt.
;==200000	;
%RMLMD==400000	; Set when RML modified.

	; RH flag vars
%RBFH==1
%RNOQC==2	; in RSND50, indicates sender doesn't want queue-confirm.
%NETDV==4	; Set by DEVCHK if device name is net-type
%RBHD==10
%NOTEL==20	; Used by NOTEW/NOTEL
;==40
;==100
%NDATO==200	; Used by ntout to tell whether outputting on neto or netd
%DSKDV==400	; Set by DEVCHK if device name is dsk-type
%NOCOM==1000	; RCPPUT statistics output uses to help avoid extra commas
%TMP==2000	; Temp flag for various stuffs.
;==4000
%LMAPN==10000	; Used by WRTMSG for 'append-msg' flag.
%MAPC==20000	; To distinguish MAPCA/MAPCAR.
%BRKT==40000	; LREADR when reading bracketed atom
%LRLIT==100000	;   "    when reading quoted string or bracketed atom.
%IOCER==200000	; Used by XCTIOC uuo
%ILOPR==400000	; Used by XCTILO uuo

	; .OPTION user variable bits that COMSAT needs.
%OPALL==%OPINT+%OPOPC+%OPLOK+%OPLKF
		; %OPINT gets new interrupt scheme, and
		; %OPOPC says set interrupted PC properly.
		; %OPLOK gets switch and lock hacking so that only one COMSAT
		;	is ever active, and
		; %OPLKF says unlock locks if top-level fatal int. happens,
		;	so a new COMSAT can be started to replace previous.

	; First thing is purification macros...
	PURPGB==3	; Start pure on page 3, have lots of impure.
.INSRT KSC;IVORY >

	; Define our own error handling macros for PAGSER library.

DEFINE	PAGSER"PSRERR ERRCOD
	CALL [	PUSH P,U4
		MOVEI U4,ERRCOD
		JSR PAGLUZ ]
TERMIN

DEFINE	PAGSER"CORLUZ
	JSR CORLOS
TERMIN

	; Time manipulating routines
DATIME"$$DSTB==1	; DST bit in time words
DATIME"$$ABS==1		; Absolute days/seconds conversions
DATIME"$$OUTT==1	; Tables for pretty output
DATIME"$$UPTM==1	; Rtns for system time-in-30'ths conversions
;DATIME"$$OUT==0		; Don't need output rtns since OUT pkg has em
.INSRT DSK:SYSENG;DATIME >

	; Routines for hacking hostnames table (formerly NETWRK stuff)
T==:U1	; Alternate names for smashable ACs.
TT==:U2 ; This is mostly for NETWRK/RESOLV.
IFE U2-OC,.ERR RESOLV temp ACs lose!!

$$DQ==1		; Domain interface code in use (mostly for NETRTS)
$$DQRN==1	; Use RENMWO hack to cut down on resolver overhead
;; These are IFNDEFs for now so that I can play with them without
;; editing the sources every time.
IFNDEF $$DQCH,$$DQCH==1	; RESOLV is allowed to find Chaosnet addresses
IFNDEF $$DQIN,$$DQIN==1	; RESOLV is allowed to find Internet addresses
;; $$DQIN should be turned off as soon as the NAMES > files etc can be
;; fixed to use "foo@bar" syntax for everything.
.INSRT SYSNET;RESOLV >

	; UUO Handler and routines

ULISTS==1	; Assemble list hackery UUOS
USCALL==1	; and special .CALL hackery (ugh)
UAREAS==1	; Area-hacking UUOS too.
$$OUT==1	; Ask for winning new OUT package!!
$$OUUO==0	; Turn off old output UUOs!!!
$$OBUF==1	; Ask OUT package for buffered-output option.
$$OERR==1	; and for ERR output type.
$$OHST==1	; and for host-name output frobs.
$$OTIM==1	; and for time output items.
$$PDBG==1	; Include debugging stuff for PAGSER.
$$CHMX==21	; Need at least this many "channels" for OUT, sigh**2.
.INSRT DSK:KSC;NUUOS >

	; LSR1 (INQUIR) database package
$$HSNM==1	; Do assemble HSNAME cruft.
.INSRT DSK:SYSENG;LSRTNS >

IFNDEF $.ARPA,$.ARPA==0 ; No longer use .ARPA kludge by default

CONSTANTS	; Dump out any accumulated literals.

	; Patch area and PDL hackery.
BVAR
PATLEN==100
PAT:
PATCH:	BLOCK PATLEN
PDLLEN==500.
PDL:	BLOCK PDLLEN+1

JUNK:	0	;for infinite-sink random writes, as in POP P,JUNK
EVAR

POPBA1:	POP P,B
POPAJ1:	POP P,A
POPJ1:	AOS (P)
APOPJ:	RET

PPCBAJ:	POP P,C
POPBAJ:	POP P,B
POPAJ:	POP P,A
CPOPJ:	RET

POPDC1:	AOSA -2(P)
POPCJ1:	 AOSA -1(P)
POPDCJ:	POP P,D
POPCJ:	POP P,C
	RET

PPDCBJ:	POP P,D
POPCBJ:	POP P,C
POPBJ:	POP P,B
	RET

POPBJ1:	POP P,B
	AOS (P)
	RET

SUBTTL Main Calling Paths

IFN 0,[

MAIN			;top level loop
  IRQGET		;get and process an input request (e.g. from QMAIL)
    SPCFND		;get all the specifications
      IPATTR		;input one attribute (name plus argument as raw text)
      PARRCP		;hairiest attribute parsing routine - for recipients
	LREADR		;Lispish reader
	APRCPT		;Process list structure after it has been read in
	  EQRCPL	;Recipient parsing routine (used also by NAMES file readin)
      RCPEQV		;look up recipient in EQV list (NAMES file)
			;called here only to hack sender's NOQC option (weird)
      HPARSE		;parse header if mail from foreign host
    MAIL		;takes a message expressed as an LSE and mails it off
      RCPEXP		;Expand recipient list (doing eqvs and so forth)
	RCPPUT		;Process one rcpt and his attribute list
	  RCPE		;See if rcpt with same name already on list
	  RCPOPT	;Get rcpt's options from NAMES file
	    RCPEQV	;look up rcpt in EQV list (NAMES file)
	  RXPNAM	;Expand local name not on EQV list (look in INQUIR and so forth)
	  RXPBUG	;Expand BUG-type rcpt not on EQV list
	  RXPAFL	;Expand @FILE-type rcpt
	  RCPEXT	;Shove expansions into the rcpt-list of the message
	    RCPPUT	;Recursively called here
	RCPSRT		;Once all rcpts have been found, sort and remove duplicates
      HEADER		;Format the necessary headers
      RCPSND		;Send message to all recipients
	SNDMSG		;Send message to one recipient
	  SNDNET	;send over the net
	  SNDFIL	;simply send to a file
	    WRTMSG	;actually writes into mail files
	  SNDBUL	;send to a .MSGS. file
	    WRTMSG	;write it
	  SNDNAM	;send to local user
	    WRTMSG	;actually writes into mail files
	  SNDPGM	;send to a program (run in inferior)
	    MAIL	;calls MAIL recursively to report program lossage
	MAIL		;calls MAIL recursively if permanent error
	QUEUE		;if temporary error, queue the message
  REMSND		;send a reminder
    MAIL		;calls MAIL to actually send the stuff
  QUESND		;try again to send queued messages to a particular host
    QSTSND		;subroutine to send one queued message
      SNDMSG		;as above
      NOTEW		;send positive acknowledge
	MAIL
      NOTEL		;send negative acknowledge
	MAIL
];IFN 0
SUBTTL Attribute Definitions

; Macro to define attributes:
;	ATTRIB <internal #>,<assembly sym>:,<P-name>,<input routine if external>

DEFINE ATTRIB COD,SYMC,*NAME*,IRTN
IF1 [IRPS SYM,,[SYMC]
	SYM==COD
	TERMIN
IFGE COD-ATRLIM,.ERR Attribute code too large!
IRP N,,[102,103,104]	; List of unused codes.
IFE N-COD,.ERR Using "unused" attribute?!
TERMIN
IFE N,.ERR Using attribute 0?!
]
%%S==.
LOC ATTRTB+COD ? ASCNT [NAME]
LOC ATTRIR+COD ? IRTN
LOC %%S
TERMIN


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;			ATTRIBUTE DEFINITIONS
;;;	
;;;	Attributes declared are all "internal" unless an input routine
;;;	exists for them.  Flags can be stored in the LH of ATTRIR if this
;;;	later proves desirable.  See doc file for more info.  To add an
;;;	attribute, first check the list of Unused Codes in the ATTRIB
;;;	macro definition to see if there's one to assign.  If none,
;;;	increment the ATRLIM definition and use its old value.  0 should
;;;	never be used as a code!!
;;;
;;; Some of these attributes have read-in dispatch entries for the NAMES file,
;;;  and others for "ATTRIBUTE:" in IRQ files.

ATRLIM==110	; Current limit for attrib codes. All must be less than this!!

A$==,,-1		; For bit-typeout mode
ATTRTB:	BLOCK ATRLIM	; Table indexed by <code>, holds ASCNT ptr to name.
ATTRIR:	BLOCK ATRLIM	; Table to hold routine addr for xct'ing on input.

	; General Purpose Attributes
ATTRIB 1,A$ATTR:,|ATTRIBUTE|		; S Name of attribute for succeeding A$AVAL (if any)
ATTRIB 2,A$AVAL:,|ATTRIBVAL|		; * Holds value for preceding A$ATTR
ATTRIB 3,A$XATR:,|X-ATTRIBUTE|		; S Like A$ATTR but used for Net-header attribs.
ATTRIB 4,A$XVAL:,|X-ATTRIBVAL|		; * Like A$AVAL.
ATTRIB 5,A$PREQ:,|SPECIAL-REQ|,	APQTL	; L Indicates special (non-mail) processing.
;ATTRIB 6,A$PARG:,|SPECIAL-ARG|,	APLST	; L Holds list of args for A$PREQ.

	; Message Attributes
ATTRIB 10,A$ID:,  |MESSAGE-ID|		; S Message ID for message.
ATTRIB 11,A$SNM:, |SENDER|,	APSTR	; S sender's name
ATTRIB 12,A$CSN:, |SENT-BY|,	APSTR	; S sender's claimed-to-be name.
ATTRIB 107,A$FFM:, |FAKE-FROM|,	APSTR	; S sender's name for "From"
ATTRIB 100,A$SNH:,|SENDER-HOST|		; V sender's host (for net mail)
ATTRIB 105,A$SRTP:,|RETURN-PATH|,APSTR	; S SMTP return path spec
	;; this used to be called MAINT-PATH, hence the odd name
ATTRIB 106,A$SMRP:,|ERRORS-TO|,APSTR	; S sender's error return path
ATTRIB 13,A$MTXT:,|TEXT|,	APSTR	; S input message-text
ATTRIB 7, A$MTXF:,|TEXT-FLAG|		; V flag saying if ^_ or crlf.crlf in message.
ATTRIB 14,A$MHDR:,|HEADER|		; S Header for message.
ATTRIB 15,A$KHDR:,|KLUDGE-HEADER|	; S Kludge to prevent ITS headers from going out
ATTRIB 16,A$NMH:, |NET-MAIL-HOST|	; V Host # origin of network-input.
ATTRIB 17,A$SBJ:, |SUBJECT|,	APSTR	; S Subject if any
ATTRIB 20,A$TIM:, |DATE|		; V Time msg created on dsk
ATTRIB 101,A$UHDR:,|USER-HEADER|,APSTR  ; S User-specified header line.
ATTRIB 21,A$HFRC:,|HEADER-FORCE|,APSTR	; S Type of header force if any
ATTRIB 22,A$RLN:, |RCPT-LIST-NAME|,APSTR; S Rcpt list name for "To":
ATTRIB 23,A$XPIR:,|EXPIRES|,	APNUM	; V # days until this MSG expires
ATTRIB 24,A$MFN1:,|MSG-FN1|,	AP6W	; V Sixbit FN1 for type *MSG.
ATTRIB 25,A$MFN2:,|MSG-FN2|,	AP6W	; V Sixbit FN2 for *MSG.
ATTRIB 26,A$CNF:, |CONFIRMATION|,APSTR	; S Confirmation option (ALL, or FAIL only)
ATTRIB 36,A$IDRM:,|ID-REMINDER|		; S Holds ID of parent, when msg generated from reminder
ATTRIB 30,A$TEXP:,|REM-EXPIRES|,APNUM	; V Time or count for Reminder expiration
ATTRIB 31,A$TNXT:,|REM-NEXT|,	APNUM	; V Abs time for very next sending of this reminder.
ATTRIB 32,A$TLST:,|REM-SPEC-LIST|,APLST ; L Holds list for 1 reminder time specification.

	; Remind-time Attributes, stored under a REM-SPEC-LIST.
ATTRIB 33,A$TBAS:,|REM-TIME-BASE|,APNUM ; V Holds time base to start from (optional)
ATTRIB 34,A$TINC:,|REM-TIME-INC|,APNUM 	; V Holds repeat-increment specifications.
ATTRIB 35,A$TSPC:,|REM-TIME-SPEC|,APNUM ; V Holds date/time repeat specifications.


	; Message Attributes - Recipient nodes
ATTRIB 54,A$RCP:, |RCPT-LIST|		; L Holds active RCPT attribs.
ATTRIB 55,A$RCPF:,|RCPT-LIST-FAIL| 	; L ditto but marks as failed.
ATTRIB 56,A$RSNT:,|RCPT-LIST-SENT| 	; L ditto but marks as sent.
ATTRIB 27,A$RDON:,|RCPT-LIST-DONE|	; L ditto but marks "done", inactive.

	; Recipient Attributes
ATTRIB 40,A$RTYP:,|R-TYPE|,	APSTR	; S Type of recipient (BUG, PGM, etc)
ATTRIB 41,A$RHST:,|R-HOST|,	APHST	; V Destination host # for rcpt
ATTRIB 42,A$RNAM:,|R-NAME|,	APSTR	; S Name of rcpt
ATTRIB 43,A$RPSN:,|R-PSEUDO|		; V Existence indicates rcp name is psuedo.
ATTRIB 37,A$RHDR:,|R-HEADER|		; S Rcpt's very own header.
ATTRIB 57,A$RHFC:,|R-HEADER-FORCE|,APSTR; S Header force, for this rcpt.
ATTRIB 44,A$RRES:,|R-RESULT|		; S Result of SNDMSG (failed, queue, sent)
ATTRIB 45,A$RRMG:,|R-RESULT-MSG| 	; S Err msg if A$RRES==failed.
ATTRIB 74,A$RFCT:,|R-FAILURE-COUNT|	; V Number of times tried to send and lost
ATTRIB 47,A$RMDS:,|R-MODE-SEND|,APNUM	; V Sending mode (see comments for SENDSW, MAILSW)
ATTRIB 50,A$RMDM:,|R-MODE-MAIL|,APNUM	; V Mailing mode
ATTRIB 51,A$RPMR:,|R-PGM-MNTNR|,APRCPT	; L If type = PGM, specifies maintainer to notify.
ATTRIB 67,A$RPGD:,|R-PGM-DISOWN|,APNUM	; V If type = PGM, control bits for job.
ATTRIB 73,A$RPGU:,|R-PGM-USET|,	AP2NUM	; L List holds two values, .USET sym and value.
ATTRIB 52,A$ROPT:,|R-OPTION|,	APSTRS 	; S Recipient Option name
ATTRIB 53,A$RNK:, |R-NOT-KNOWN|		; V Existence means rcpt not known.
ATTRIB 46,A$RHSN:,|R-HSNAME|		; V Holds 6bit HSNAME of local rcpt without a dir.

 	; Attribs for Scheduler List.
ATTRIB 60,A$S:,   |SCHED-ITEM-LIST|	; L List holding schedule requests
ATTRIB 61,A$STIM:,|SCHED-TIME|		; V System time at which to execute.
ATTRIB 62,A$SRTN:,|SCHED-RTN|		; V Routine to execute.
ATTRIB 63,A$SARG:,|SCHED-ARG|		; V Argument for routine.

 	; Attribs for Master List.
ATTRIB 64,A$I:,   |MASTER-ITEM-LIST|	; L List holding Message-ID/Disk addr correspondence.
ATTRIB 65,A$IDAD:,|DISK-ADDR|		; V Disk addr of LSE for this message.
ATTRIB 66,A$IDBL:,|DISK-LENGTH|		; V Length of its LSE on disk.

 	; Attribs for Queued Message List.
ATTRIB 70,A$Q:,   |QUEUE-SITE-LIST|	; L List holding Site/Message-ID correspondence.
ATTRIB 76,A$QFL:, |QUEUE-FINALIZE-LIST|	; L List holding ID's to finalize.
ATTRIB 71,A$QHST:,|QUEUE-SITE|		; V Site # which following ID's are queued for.
ATTRIB 77,A$QFCT:,|QUEUE-FAILURE-CNT|	; V # times this site failed.

	; Attribs for Reminder Message List.
ATTRIB 75,A$T:,	 |REMINDER-LIST|	; L Entry on the RML.

	; Attribs for EQV list
ATTRIB 72,A$E:,   |EQV-LIST|,	APEQVL	; L Equivalence list for recipient.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; IARCOD - Given SLP in A to uppercase string, checks to see if it
;	matches a non-internal attribute name, and if so skips with
;	internal code in RH(B).

IARCOD:	MOVSI B,-ATRLIM
	SKIPE ATTRIR(B)		; Skip if internal name (a no-no)
	 SLNEA A,ATTRTB(B)	; This one is OK, does name match?
	  AOBJN B,.-2
	CAIGE B,0	; Don't skipret if counted out.
	 AOS (P)
	RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	; Table of known recipient types.
RTYPTB:	ASCNT [BUG]
	ASCNT [FILE]
	ASCNT [NAME]
	ASCNT [PGM]
	ASCNT [*MSG]
	ASCNT [@FILE]
NRTYPS==:.-RTYPTB

	; Table of sending routines to dispatch on, corresponding to RTYPTB.
SNDRTB:	SNDBUG
	SNDFIL
	SNDNAM
	SNDPGM
	SNDBUL
	SNDAFL
IFN NRTYPS-<.-SNDRTB>,.ERR SNDRTB loses

	; Message-Result definitions
	; These values are returned by certain routines such as SNDMSG
	; responsible for sending a message.  Note that all temporary
	; errors are even (low bit zero), and permanent errors are odd.
	; When an error message is furnished, it should not begin or
	; end with a CRLF; if a CRLF is embedded, a tab should follow it.
	; This is just for prettiness when string is put into a receipt msg.

MR$WIN==:0 ; Message won, sent to rcpt.  This is the only return
		; which is NOT expected to furnish some sort of err msg.

MR$PER==:1 ; Perm Err for Rcpt.  This rcpt failed forever, but
		; can keep trying site for other rcpts.
		; This is most often the result of a 450 reply from remote
		; host, but might also result from rare local errors.

MR$TER==:2 ; Temp Err for Rcpt, must queue.  Can keep trying site for
		; other rcpts.
		; This return happens for such things as "dir full" (local)
		; or "mailbox busy", "job cap exceeded", etc. (remote)

MR$PEH==:3 ; Perm Err for Host; this host failed forever.
		; Should never happen for local site (!).  Will happen for
		; remote sites which have been getting MR$TEH's for too long.
		; Can also be generated "by hand" to flush a queue.

MR$TEH==:4 ; Temp err for Host; don't bother trying site further at
		; moment.  This return can happen if disk space gronked
		; (local) or remote host dies during sending (IOC error,
		; timeout, failed ICP, etc.)

MR$MAX==:4		; Max possible MR$ value
MR$TAB==:MR$MAX+1	; For checking dispatch tables.

comment |
MR$TEM ? - Temp Err for Msg; implies some temporary error associated with
	the message itself, rather than with the host or a specific rcpt.
	This might possibly happen for weirdness in a message such as
	^C's or the like, but that is supposed to be circumvented
	by MLFL or quoting.  May happen for overly long msg text,
	if a remote site has a very low length tolerance; in this case
	we won't want to keep re-trying for every rcpt!!
MR$PEM ? - Perm Err for Msg; Ditto, but permanent.
	Same mumblings as for MR$TEM.

|


SUBTTL Various variables and standard filenames

;; Hostname strings are 63 words (255. bytes) long, per RFC883.

BVAR
DEBUG:	-1	;non-z means (mainly) to stop on errors instantly.
XVERS:	-1	; Version type -
		; 0 - Normal operational mailer
		; 1 - "New" mailer, operational but not completely tested.
		;-1 - Experimental mailer, for debugging/development.
OWNHST:	0	;Our own host address on the network we're really connected to.
OWNHS2:	0	;Alternate host address to consider local .SEE TCPGAT
		;With domains, we always need this if we are dual-net.  Foo.
MYUIND:	-1	;COMSAT's user index, used by SUNAME

OWNNAM: BLOCK 63	;holds asciz string which is name of own site
GATNAM: BLOCK 63	;holds asciz string which is name of TCP relay host
TMPNAM:	BLOCK 63	;holds asciz string which is name of random host

PRGNAM:	.FNAM1		; FN1, FN2 of source file assembled from.
VERSHN:	.FNAM2

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	; Names for mail directory files

; SATF "<norm>",<xper>,<new>	- specifies a SIXBIT filename
;	which varies depending on whether COMSAT is running as
;	the normal, experimental, or new-operational mailer.

NSTFLS==0
DEFINE SATF &NORM&,XPER,NEW
%%SLOC==.
IF1 ?	[SIXBIT |XPER|],,[SIXBIT |NEW|]	; Goddam constants lossage
IF2 [	SIXBIT NORM
	TMPLOC XVARTB+NSTFLS,{%%SLOC,,[SIXBIT |XPER|]}
	TMPLOC NXVRTB+NSTFLS,{%%SLOC,,[SIXBIT |NEW|]}
]
NSTFLS==NSTFLS+1
TERMIN

SATDEV:	SIXBIT /DSK/	; Satellite Device
SATDIR:	SIXBIT /.MAIL./	; Satellite Directory
			; Directory is .BULK. for bulk mailer

MSTFN1:	SATF "  LIST",XLIST,NLIST	; MASTER LSE file
MSTFN2:	SIXBIT /MASTER/
NMSFN1:	SATF "  LIST",XLIST,NLIST	; FNM to use when GC'ing MSGS.
	SIXBIT /NMASTR/

QMLFN1:	SATF "  LIST",XLIST,NLIST	; QML LSE file.
QMLFN2:	SIXBIT / QUEUE/

RMLFN1:	SATF "  LIST",XLIST,NLIST	; RML LSE file.
RMLFN2:	SIXBIT /REMIND/

MSGFN1:	SATF " LISTS",XLIST,NLIST	; File of MSG LSEs.
MSGFN2:	SIXBIT /  MSGS/
NMGFN1:	SATF " LISTS",XLIST,NLIST	; FNM to use when GC'ing MSGS.
	SIXBIT / NMSGS/

EQAFN1:	SATF "NAMES",XNAMES,NNAMES	; ASCII name equivalence/options file
EQAFN2:	SIXBIT />/
EQAFR1:	0		; Used to hold real translated filenames.
EQAFR2:	0

EQRFIL::			; Holds file blk for compilation-report file.
EQRDEV:	0
EQRDIR:	0
EQRFN1:	SATF "NAMED",XNAMED,NNAMED
EQRFN2:	0

EQVFN1:	SATF "  LIST",XLIST,NLIST	; Binary name equivalence/options file.
EQVFN2:	SIXBIT /   EQV/

LSRFN1:	SIXBIT / FROM/	; Indicator file left by INQUIR, fresh
LSRFN2:	SIXBIT /INQUIR/	; file means fresh LSR waiting to be gobbled.

HSTDEV:	SIXBIT /DSK/	; File name of the host tables.
	SIXBIT /SYSBIN/
	SIXBIT /HOSTS3/
	SIXBIT />/

IRQFN1:	SATF "MAIL",XMAIL,NMAIL	; Filename of mail requests.
IRQFN2:	SIXBIT /</		;  note FIFO sequence!
IRIFN1:	SATF "MAILIN",XMAILI,NMAILI	; Filename of recognized mail requests.
IRIFN2:	SIXBIT />/		;  note FILO sequence!
IREFN1:	SATF "BADREQ",XBADRQ,NBADRQ	; Erroneous input reqs renamed to this.
IREFN2:	SIXBIT />/

IRRFIL:	0 ? 0		; Used by RFNAME call to find true input-req FN.
IRRFN1:	0
IRRFN2:	0

IDFN1:	SATF "    ID",XID,NID	; Name used to obtain unique Message-ID number.
IDFN2:	SIXBIT />/

	; Names for statistics files
STFN1:	SATF "STATS",XSTATS,NSTATS	; Name of actual stats file
STFN2:	SIXBIT />/
STOFN1:	SATF "OSTATS",XOSTAT,NOSTAT	; Name for old stats files
STOFN2:	SIXBIT />/

	; Names for locked switch file (to ensure uniqueness)
LCKFN1:	SATF " LOCK",XLOCK,NLOCK
LCKFN2:	SIXBIT /UNIQUE/

UNMPRF:	SIXBIT /COMSAT/		; Preferred UNAME
JNMPRF:	SATF "IV",XPER,NEW	;    "      JNAME (major version #, sort of)
				; JNAME is BULK for bulk mailer
EVAR

	; Table of vars to change when running in experimental mode!
XVARTB:	BLOCK NSTFLS		; To hold sat filenames which change.
	; Can have other vars here in form
	; <addr>,,[<new value>]
NXVARS==:.-XVARTB

	; Vars to change when running in "New operational" mode!
NXVRTB:	BLOCK NSTFLS		; To hold sat filenames which change.
	; Can have other vars here in form
	; <addr>,,[<new value>]
NNXVRS==:.-NXVRTB


XVRSRT:	SKIPN XVERS		; If normal,
	 RET			; don't change anything.
	PUSHAE P,[A,B,C]
	MOVE C,[-NXVARS,,XVARTB]
	SKIPL XVERS		; Use XVARTB if experimental
	 MOVE C,[-NNXVRS,,NXVRTB] ; Else use "new operational" vars.
XVRSR1:	MOVE A,(C)
	HLRZ B,A
	MOVE A,(A)	; Get xper. value to use
	MOVEM A,(B)	; Store in indicated loc
	AOBJN C,XVRSR1
	POPAE P,[C,B,A]
	RET

SUBTTL Initializations for bulk mailer kludge

.scalar	bootxj			; Original XJNAME (for RESET, ugh)

bulkck:	push p,a
	.suset [.rxjname,,bootxj]
	move a,bootxj
	came a,[sixbit /VDNBRG/]
	 jrst popaj
	move a,[sixbit /.BULK./]
	movem a,satdir
	move a,[sixbit /BULK/]
	movem a,jnmprf
	;; need to break local delivery
	jrst popaj

SUBTTL Statistics file routines

	; Macro to write stats with.  (see SCOPN)
	; Generates a <CRLF><time><TAB> before writing given OUT-style list.
	; Saves and restores OC output channel so it can appear anywhere;
	; this isn't usually necessary, but better safe than sorry.
DEFINE STAT (LIST)
	PUSH P,OC
	CALL STTLIN
	OUT!LIST
	CALL STTFIN
TERMIN

	; ditto, but no <CRLF><time><TAB> insertion.
DEFINE CSTAT (LIST)
	PUSH P,OC
	MOVEI OC,SC
	OUT!LIST
	CALL STTFIN
TERMIN

	; Like STAT, but can be skipped over.
DEFINE PSTAT (LIST)
	CALL [	PUSH P,OC
		CALL STTLIN
		OUT!LIST
		CALL STTFIN
		RET ]
TERMIN

; STTLIN - Start stat report with time line
;	Assumes that C/STAT macros are saving/restoring OC.

STTLIN:	OUT(,CH(SC),EOL)	; Set std output chan, and do a CRLF.
	SOS SCLCNT	; Decrement line cnt, but don't put date cruft in yet.
	.RTIME U4,		; Get hhmmss in U4
	OUT(,6F(U4),TAB)	; Output time plus indentation.
	RET

; STTFIN - force stats channel and also restore saved OC
STTFIN:	OUT(,FRC)
	POP P,OC
	EXCH OC,(P)
	RET

SCMAXL:	5*50.*2000	; Max length of stats file before renaming, in chars.
LVAR SCLCNT:	0	; Cntdn of lines - when < 0, put full date in stats.
STTLIM==40.		; # of lines to count down.
STBFLN==50.		; Use 50 wds for SC output buffer.
LVAR STBUF: BLOCK STBFLN	; Need fixed buffer because a dynamically
			; allocated one could be screwed by various lossages.

; SCOPN -  Open SC channel to STATS file

SCOPN:	PUSHAE P,[A,B]
	TLZ F,%SCOPN		; Clear flag

	; Try to open stats file in write-over mode.
	MOVE B,[%DOWOV+.UAO,,SC]
	ECALL SCOBLK,[[4,SCOPN4],[*,23,SCOPN1]]
	 JSR SYSLOS		; If 4 = File not found, create it at SCOPN4
				; If 23= File locked, hack it at SCOPN1
				; Else horrible error!
	SYSCAL FILLEN,[CIMM SC ? CRET A]	; Got it, find current length.
	 JSR SYSLOS		; gasp choke?
	.ACCESS SC,A		; Point to end of file.
	OUT(SC,OPEN(UC$BUF,,[-STBFLN,,STBUF]))	; Open buffered UUO chan.
	TLO F,%SCOPN		; Indicate SC channel now open!
	CAMG A,SCMAXL		; Have length.  Is current file too long?
	 JRST SCOPN5		; It's OK, continue blissfully.
	JRST SCOPN0		; Too long, have to close and rename.

	; Entry point from MAIN loop when file is too long.
SCROPN:	PUSHAE P,[A,B]		; Real programmers don't use co-routines....

	; STATS file too long, time to rename it and start another.
SCOPN0:	STAT (,("Renaming stats file..."),TIM(MDYT))
	OUT(SC,CLS)		; Close channel, making sure all's output.
	TLZ F,%SCOPN		; (must close to let RENAME below win.)

	; Some cretin has the STATS file locked, but let's outsmart the loser.
	; This stuff also done when STATS file too long.
SCOPN1:	SYSCAL RENAME,[['DSK,,] ? STFN1 ? [SIXBIT /</]	; Rename oldest STATS
			SATDIR ? STOFN1 ? STOFN2	; to newest OSTATS
	 		CERR A]
	 JRST [	CAIE A,4	; Didn't rename?  File still there?
		 CAIN A,23	; or file locked (must be for writing)?
		  JRST SCOPN4	; One of these, renaming done.
		JSR SYSLOS]	; Neither 4 nor 23? Argh, horrible error?!
	JRST SCOPN1		; If rename successful, continue til all gone.

	; Here to create new STATS file.  Comes here if none exists, or after
	; all STATS files have been renamed to OSTATS (same thing).
SCOPN4:	MOVE B,[.UAO,,SC]
	ECALL SCOBLK,[[5,SCOPNX]]	; watch out for dir full.
	 JSR SYSLOS		; Foo?
	OUT(SC,OPEN(UC$BUF,,[-STBFLN,,STBUF]))	; Open buffered UUO chan.
	TLO F,%SCOPN		; Say stats file open!
	STAT (,("TELEMETRY FILE CREATED, DATE "),TIM(F1))
	STAT (,("This is version "),6F(PRGNAM),(" "),6F(VERSHN))
	STAT (,(";;; NOTE!!! If you are reading this file with ^R or :PRINT,
please type ^S or ^G to stop at once!
	You are damaging mailer response!"))
	JRST SCOPN8		; Skip writing full date again

SCOPN5:	SKIPLE SCLCNT		; Now have SC open, must we insert full date?
	 JRST SCOPN9		; Nope, that's all.
	OUT(SC,CRLF,EOL)
	STAT (,("Date is now: "),TIM(MDYT))
SCOPN8:	MOVEI A,STTLIM		; This many lines per full-date insertion
	MOVEM A,SCLCNT
SCOPN9:	POPAE P,[B,A]
	RET

	; Come here if get DIR FULL error trying to open stats file.
SCOPNX:	CALL SDFULL	; Attempt to fix, die if can't.
	TLNE F,%SCOPN
	 JRST SCOPN5	; recovered! continue depending on %SCOPN state.
	JRST SCOPN4

SCOBLK:	SETZ ? SIXBIT /OPEN/ ? B ? SATDEV ? STFN1 ? STFN2 ? SETZ SATDIR

CONSTANTS

SUBTTL ERROR system call analyzer macro

; Example:
;	SYSCAL OPEN,[[.UAI,,DKIC] ? [SIXBIT /DSK/] ? [SIXBIT /MICKEY/]
;			[SIXBIT /MOUSE/] ? [SIXBIT /USERS/] ? CERR MMERR]
;	 ERROR [[%ENSFL,FOO20],[ERR,MMERR],[CALL,%ENADV,FOO40]]

; ERROR - Macro to put in non-skip error returns from system calls.
;	The easiest way to explain this is to explain the above
;	example, which is trying to open DSK:USERS;MICKEY MOUSE is
;	unit ascii input mode, and asking for an explicit error
;	code returned in MMERR.   Here is what the ERROR macro following
;	it will do:  first, it will find the error code in MMERR
;	because of the [ERR,MMERR] clause. (Had there been no ERR
;	clause it would have used the STATUS of the .BCHN channel).
;	Then if there were a "File not found" (%ENSFL) it would
;	JRST to FOO20.  If there were a "Device not availible", it
;	would have gone to FOO40 leaving the stack set up to
;	point at the saved ACs D, C, B, and A in that order, and
;	below that the address of the SYSCAL.  In other words,
;	the routine at FOO40 would retry the SYSCAL by doing
;	a "POPAE P,[D,C,B,A] ? RET" .  The routine is passed
;	one argument (in A) which is the error code.

;		The ERR is optional, and there may be an arbitrary
;	number of handlers for error codes, each of which may be
;	a plain JRST (i. e. [%ENSFL,FOO20]) or a CALL (i. e. 
;	[CALL,%ENADV,FOO40]) depending on which the programmer
;	thinks is cleaner for the occasion.
;		The ERROR macro expands into a call to CALERR.

DEFINE ERROR LIST
	%%ERCT==0
	CALL [PUSH P,[[
	IRP ITEM,,[LIST]
	  %%ERCT==%%ERCT+1
	  IRP ERRCOD,VECTOR,[ITEM]
	    IFSE ERRCOD,ERR,{	C$ECL_C%OPSH,,VECTOR
				.ISTOP}
	    IFSE ERRCOD,CALL,{	IRP ERCD,VECT,[VECTOR]
				  C$CAL_C%OPSH+ERCD,,VECT
				  .ISTOP
				  TERMIN
				.ISTOP}
	    ERRCOD,,VECTOR
	    .ISTOP
	    TERMIN
	  TERMIN
	](-%%ERCT)]
	PJRST CALERR]
	TERMIN

; CALERR - Routine called by ERROR (see above).  Figure out how to use
;	ERROR before you read this...
;	ERROR will generate a calling sequence of the form
;	.CALL FOO
;	 CALL [PUSH P,[-n,,[param1 ? param2 ? ... ]]
;		PJRST CALERR]

;	There is one parameter for each clause in the ERROR call.
;	n is the number of parameters.  A parameter is decoded by opcode:
C%OPCD==410300			; Byte spec for the opcode field
C%OPSH==17			; (LH shift amount)

;	Here are the currently defined opcodes:
;OPCODE:			WHAT THE PARAMETER WORD MEANS
;==0				; Bits 3.6-3.1 are an error code, and the
				;   RH is an address to jump to if that
				;   error occurs (the simple ERROR clause).
C$ECL==1			; RH is loc of word containing error code (for ERR)
C$CAL==2			; Like 0, except generates the hairier
				;    calling sequence (CALL)

CALERR:	PUSHAE P,[A,B,C,D]
	SKIPGE C,-4(P)		; Get argument off stack.
	 JSR AUTPSY		; If nothing, die.
	HRLI A,-3(P)		; Now move the three saved ACs up one slot
	HRRI A,-4(P)		;   on the stack.  (if you think THIS is bad,
	BLT A,-1(P)		;   take a look at TRANTR sometime.)
	SUBI P,1		; And adjust the stack pointer.
	MOVE A,C		; Get a copy of the AOBJN pointer.
CALR10:	LDB B,[C%OPCD,,(A)]	; Now search for a parameter with opcode
	CAIE B,C$ECL		;   C$ECL, loc of err code.
	 AOBJN A,CALR10
	JUMPL A,CALR20		; Found it.
	.SUSET [.RBCHN,,A]	; Not found, figure it out.
	SYSCAL STATUS,[MOVEI A ? MOVEM A]
	 JSR AUTPSY
	LDB A,[220600,,A]	; Magic field which holds the error code is 3.6-3.1)
	CAIA
CALR20:	 HRRZ A,@(A)		; Now have err code in A.
CALR30:	LDB B,[220600,,(C)]	; Check each param. for this code.
	CAIE B,(A)
	 AOBJN C,CALR30
	JUMPL C,CALR40		; Found!
	JSR AUTPSY		; Not there, die horribly.
CALR40:	HRRZ B,(C)		; Get the address in B.
	LDB D,[C%OPCD,,(C)]	; Get opcode.
	CAIE D,C$CAL		; Is it a CALL clause?
	 JRST CALR50
	SOS -4(P)		; Fix the saved return address to point
	SOS -4(P)		;    at the .CALL instruction itself.
	JRST (B)		; Dispatch to handler.

CALR50:	MOVEM B,-4(P)		; Slight kludge to restore ACs and
	POPAE P,[D,C,B,A]	;   jump to given location.
	RET

SUBTTL Interrupt handlers - IOC, MPV, PDLOV, ILOPR

%BADPI==%PIIOC+%PIMPV+%PIPDL+%PIILO+%PIWRO	; Bad conditions

;;; Interrupt vector table for new style ITS interrupts.

TMPLOC 42,{-LINTBLK,,INTBLK}	; New mode interrupt vector

%NINTS==400000			; 4.9 bit means push .JPC, .SUUOH, LSPCL also
INTBLK:	%NINTS,,INTPDP		; PDL ptr addr

	; .REALT interrupt vector, defer more RLT's
	%PIRLT ? 0
	%PIRLT ? 0
	INTRLT

	; IOC error interrupt vector. defer all but MPV and PDL OV.
	%PIIOC ? 0
	-1#<%PIMPV\%PIPDL> ? -1
	INTIOC

	; PDL overflow int vec.  Defer all but MPV.
	%PIPDL ? 0
	-1#<%PIMPV> ?	-1
	INTPDL

	; MPV int vec.  defer everything.
	%PIMPV ? 0
	-1 ? -1
	INTMPV

	; WRO int vec.  defer everything.
	%PIWRO ? 0
	-1 ? -1
	INTWRO

	; Illegal Operation int vec, defer more ILOPR's
	%PIILO ? 0
	%PIILO ? 0
	INTILO

	; Inferior Interrupt int vec.  defer more of same.
	0 ? 377,,0
	0 ? 377,,0
	INTUSR

;	; Sys going down int vec.  defer more of same.
;	%PIDWN ? 0 ?	%PIDWN ?	0 ?	INTDWN

LINTBLK==.-INTBLK

BVAR
	LIPDL==50.	; Enough for 6 or so nestings of ints.
INTPDP:	-LIPDL,,IPDL-1	; Interrupt PDL pointer.
IPDL:	BLOCK LIPDL	;     "      "  stack.
EVAR

	; Macro to ease dismissing interrupts.  DISMIS restores with old PC,
	; DISMIS [addr] restores with given new PC.
DEFINE DISMISS (ADDR)
.CALL [SETZ ? 'DISMIS ? CTLI %NINTS
IFB [ADDR] SETZ INTPDP
.ELSE INTPDP ? SETZ ADDR
]
TERMIN

	; Macros to ease saving/restoring stuff for stats output.
	; Basically saves/restores UUO vars.
DEFINE IUSAVE
PUSHAE P,[U40,U1,U2,U3,U4]
IFN OC-U2, PUSH P,OC
IFE $$UCAL,PUSH P,UUORPC
TERMIN

DEFINE IUREST
IFE $$UCAL,POP P,UUORPC
IFN OC-U2, POP P,OC
POPAE P,[U4,U3,U2,U1,U40]
TERMIN

;***********************  IOC INTERRUPT  *********************************
;			I/O Channel Error
BVAR
IOCBCH:	0	; BCHN, # of channel IOC happened on.
IOCIOS:	0	; IOS word for channel, holds error code.
IOCDEV:	0	; Word 1 returned by RFNAME, usually device name.
IOCFN1:	0	; Word 2  "
IOCFN2:	0	; Word 3  "
IOCDIR:	0	; Word 4  " , usually directory name
IOCMOD:	0	; Word 5  " , mode channel open in.
EVAR

INTIOC:	TRNE F,%IOCER		; "Expected" IOC? (i.e. XCTIOC)
	 JRST IOCLUZ		; Yep, whew... let XCTIOC code handle safely.
	PUSHAE P,[A,B,C]
	.SUSET [.RBCHN,,IOCBCH]	; Get # of bad channel
	SYSCAL STATUS,[IOCBCH ? CRET IOCIOS] ; and IOS word for it.
	 JSR SYSLOS
	SYSCAL RFNAME,[IOCBCH ? CRET IOCDEV
		CRET IOCFN1 ? CRET IOCFN2 ? CRET IOCDIR]
	 JSR SYSLOS		; shouldn't happen.
	MOVE A,IOCBCH		; Now getum channel into AC.
	CAIN A,SC		; Before we try any reporting, see if 'twas
	 TLZ F,%SCOPN		; the stats channel itself that's zapped!!!
	LDB B,[330500,,IOCIOS]	; Isolate IOC error #.

	; Check for outright and obvious bugs
	CAIE B,5		; Over-IOPOP,
	 CAIN B,6		; or IOPUSH?
	  JRST IIOC90

	; Passed initial checking, now see if it happened while hacking net;
	; Network IOC errors are facts of life and ok...
	CAIN A,NETD		; Net data channel?
	 JRST IIOC10
	CAIE A,NETO		; Net output channel?
	 CAIN A,NETI		; Net input channel?
	  JRST IIOC10		; Dispatch if hacking net.

	; Ugh, error is very likely fatal... see if it's anything that is
	; a recognized problem.
	CAIE B,11		; Device full?
	 CAIN B,14		; or directory full?
	  JRST IIOC20		; Yep, storage problems.
	CAIN B,15		; (DM only??) Dir alloc exhausted?
	 JRST IIOC20

	; Error is completely unrecognized, scream loudly.
	; Also come here if no way to resolve some known problem.
IIOC90:	JSR PRESRV		; Save stuff, swap PDL, etc.
	CAIN A,SC		; Of course, if SC was at fault,
	 JRST [	JSR BURY	; screaming is unlikely to win.
		JSR UDEATH]
	TLNN F,%SCOPN		; If SC simply not open,
	 JRST AUTPY1		; let AUTPSY do the re-open attempt & reporting
	STAT (,(" ==="),RABR,(" IOC ERR, BCHN="),OCT(A),(", IOS="),H(IOCIOS),("
DEV=|"),6F(IOCDEV),("|
FN1=|"),6F(IOCFN1),("|
FN2=|"),6F(IOCFN2),("|
DIR=|"),6F(IOCDIR),("|
MOD="),H(IOCMOD))
	DISMISS [AUTPY1]	; Get out of int. level.

	; Network IOC.  Dismiss to net ioc handler, if one exists.
IIOC10:	SKIPN NTIOCV	; So does a handler exist?
	 JRST IIOC90	; Foo, a wild IOC has burst loose!  Go faint...
	POPAE P,[C,B,A]
	DISMISS NTIOCV	; Win, leave int level & punt to handler.

	; Storage-exhaustion IOC.  See if it's for COMSAT's own dir.
IIOC20:	MOVE B,IOCDIR
	CAMN B,SATDIR	; Same dir as Comsat?
	 JRST IIOC30	; Yes, there's still a slim hope...

	; Here, problem is essentially an unguarded IOT to some
	; loser's directory.  There is basically nothing to do besides
	; ensuring that the cruft we're writing doesn't contribute
	; to the lossage, and making sure that an XCTIOT is slapped
	; around the guilty instruction in the source.
	MOVE B,IOCFN1
	CAME B,[SIXBIT /_MAIL_/]
	 JRST IIOC90	; Sigh
	MOVE B,IOCFN2
	CAME B,[SIXBIT /OUTPUT/]
	 JRST IIOC90	; also sigh
	SYSCAL DELETE,[IOCDEV ? IOCFN1 ? IOCFN2 ? IOCDIR]
	 JRST IIOC90	; Well, it was worth a try.
	JRST IIOC90

	; Here, ran out of storage on COMSAT's own sacred directory!
IIOC30:	POPAE P,[C,B,A]
	CALL SDFULI	; Attempt desperation measures at interrupt level.
	DISMISS		; If we return at all, we've won!!

; Stuff for handling expected IOC errors

UUODFE XCTIOC,UXCTIO	; skip unless IOCER occurs
UXCTIO:	TRO F,%IOCER
IFE $$UCAL,PUSH P,UUORPC	; Ensure ret addr on stack since may xct UUO.
	PUSH P,U40	; Must also save due to interrupt lossage (40 zapped)
	MOVEM P,XIOCP	; Save PDL pointer...
	XCT @(P)	; Execute instruction...
	 CAIA
	  AOS -1(P)
	AOS -1(P)
IOCRET:	TRZ F,%IOCER
	SUB P,[1,,1]	; Flush saved loc 40
	RET		; Return from UUO.

.SCALAR XIOCP	; Place to save PDL pointer for XCTIOC.

	; Here when IOCERR happens with flag set
IOCLUZ:	SKIPE MAKING	; IOCERR under DEBUG is expected in MAKMST
	 SKIPN DEBUG
	  CAIA
	   .VALUE	; Halt here with expected IOC error in DEBUG mode.
	MOVE P,XIOCP	; Restore PDL pointer...
	DISMISS [IOCRET]


;*************************  RLT INTERRUPT  ******************************
;			Real-Time Clock Interrupt

; The new way to do real time interrupts is to put the address of the handler
; you want to use into RLTVEC; when a real-time interrupt occurs, the
; routine will be jumped to.  If the sign bit is set, the dispatch
; will happen at interrupt level, and the handler is responsible for
; DISMISS'ing!
; Be sure to CLKOFF and SETZM RLTVEC in the handler, and also
; if you decide the timeout is no longer in effect.

LVAR RLTVEC:	0		; (See above)

INTRLT:	SKIPGE RLTVEC		; Does a int-level handler exist?
	 JRST @RLTVEC		; Yes, jump there!  Handler must DISMISS.
	SKIPE RLTVEC		; Does a prog-level handler exist?
	 DISMISS RLTVEC		; Yes, dismiss and dispatch there!

	; Unknown cause of RLT interrupt.
	PUSH P,A
	IUSAVE		; Save UUO vars since doing stats output.
	MOVE A,INTPDP
	MOVE A,-3(A)
	STAT (,(" ==="),RABR,(" BUG: Random REALT int, PC="),RHV(A),(", continuing."))
	IUREST		; Restore UUO vars.
	POP P,A
	DISMISS		; Returns normally


; PDL OV, MPV, and ILOPR Interrupt handlers.

INTPDL:	JSR PRESRV
	TLNN F,%SCOPN
	 JRST AUTPY1
	STAT (,(" ==="),RABR,(" BUG: PDL OVERFLOW!"))
	JRST AUTPY1		; Jump into autopsy routine

INTMPV:	JSR PRESRV
	TLNN F,%SCOPN
	 JRST AUTPY1
	STAT (,(" ==="),RABR,(" BUG: MPV!"))
	JRST AUTPY1		; Jump into autopsy routine

INTWRO:	JSR PRESRV
	TLNN F,%SCOPN
	 JRST AUTPY1
	STAT (,(" ==="),RABR,(" BUG: WRO!"))
	JRST AUTPY1

INTILO:	TRNE F,%ILOPR		; Skip if error not foreseen,
	 DISMISS [ILORET]	; else was predicted, return to special loc!
	JSR PRESRV		; Perform autopsy, unexpected ILOPR.
	TLNN F,%SCOPN
	 JRST AUTPY1
	STAT (,("	==="),RABR,(" BUG: ILOPR!"))
	JRST AUTPY1

UUODFE XCTILO,UXCTIL	; Like XCT but will skip unless ILOPR occurs.
UXCTIL:	TRO F,%ILOPR	; Set flag telling ILOPR handler it's OK.
	XCT @U40	; Do whatever.
	 CAIA		; a non-skipping instr really skips once
	  AOS UUORPC	; a skipping instr really skips twice
	AOS UUORPC
ILORET:	TRZ F,%ILOPR
	UUOXRT		; Return (w/o skipping if ILOPR occurred)


;*******************  USR INTERRUPT  **********************
;		Inferior Job Interrupt

INTUSR:	TLNN F,%PGMRN				; Running a pgm?
	 JRST [	IUSAVE			; Nope, random int.  Save UUO vars
		STAT (,("Note: Spurious USR int")) ; since doing stats output.
		IUREST
		DISMIS]
	PUSHAE P,[A,B]
	MOVE A,INTPDP
	MOVE B,INFBIT
	IORM B,-4(A)	; Set bit in old .DF2 so more inf. ints are deferred.
	POPAE P,[B,A]
	DISMIS [SNDP60]	;and dismiss to non-int-level handler.

;; INTDWN - %PIDWN interrupt handler.
;
;INTDWN:	PUSHAE P,[A,B]
;	SYSCAL SSTATU,[MOVEM A]	; See what's going on.
;	 JSR AUTPSY
;	JUMPGE A,[SETOM GODOWN	; If sys going down, signal main loop.
;		  DISMISS]
;	CAMN A,[-1]		; Sys up normally?
;	 JSR AUTPSY		; (shouldn't happen, not in OPTLIV mode).
;	SKIPE GODOWN		; Was it once going to go down?
	


SUBTTL Error handlers - AUTPSY, SDFULL

; AUTPSY - Called by a one-way JSR AUTPSY for purposes of leaving a trail,
;	saves info about bug and writes out on stat file, dumping job
;	if permissible.  Nearly ALL fatal errors wind up here.
;	really should have some different levels of bugs-- system type,
;	call type, soft type, etc. to replace filbug/corbug and furnish
;	more specificity for other types as well.

LVAR 	AUTPSY:	0 ? JRST AUTPY0	; Jump to pure

AUTPY0:	JSR PRESRV		; Save state of job (hopefully in sufficient detail)
AUTPY1:	TLNE F,%SCOPN		; And now if STATS channel is open,
	 JRST AUTPY2		; Jump to report lossage.
	MOVE B,[%DOWOV+.UAO,,SC]
	.CALL SCOBLK		; Not open??? Try gingerly to open...
	 JRST AUTPY7		; And quit immediately on any error.
	SYSCAL FILLEN,[CIMM SC ? CRET A]
	 JRST AUTPY7		; Also quit if can't find length???
	.ACCESS SC,A		; Set length to EOF
	OUT(SC,OPEN(UC$IOT))	; And open UUO channel so hairy output works.
	TLO F,%SCOPN		; and indicate open!
AUTPY2:	STAT (,(" ==="),RABR,(" BUG: FATAL ERROR "),LABR,("===  Date: "),TIM(MDYT),("
	Autopsy from "),RHV(AUTPSY),(" Preserved from "),RHV(PRESRV),("
	Last UUO = "),H(U40SAV),(" at "),RHV(UUHSAV),EOL)
	; Do this part if interrupt was triggered.
	STAT (,("	INT. FROM "),RHV(IPCSAV),("	.JPC/"),RHV(JPCSAV),("
	Registers:	
"))
	PUSH P,A
	MOVSI A,-20
AUTPY4:	CSTAT (,RHV(A),("/ "),H(ACCSAV(A)),EOL)
	AOBJN A,AUTPY4
	POP P,A

	JSR RNMREQ		; Try to rename current input request!
	 JRST [	STAT (,("No "),6F(IRIFN1),(" "),6F(IRIFN2),(" file present during burnup."))
		JRST AUTPY6]
	STAT (,("Input req file "),6F(IRRFN1),(" "),6F(IRRFN2),(" renamed to "),6F(BRQFN1),(" "),6F(BRQFN2))
AUTPY6:	JSR BURY		; Now try to dump self...
	STAT (,("Job dumped to CRASH;"),6F(DMPFN1),(" "),6F(DMPFN2))
	JSR UDEATH		; And now die.

	; Here after preserving state, if STATS channel can't be opened.
AUTPY7:	JSR RNMREQ		; As above, but without stats.
	 JFCL
	JSR BURY
	JSR UDEATH



; SYSLOS - for use when ITS does something wrong, eg when system
;	call inexplicably fails or returns strange error code.

LVAR	SYSLOS:	0 ? JSR AUTPSY


; CORLOS - Suport routine for PAGSER; JSRd to when a CORBLK fails.
;   CBKERR/ error code from failing .CALL
;   If arg not provided, checks most recent erring channel.

LVAR	CORLOS:	0 ? JRST CORLS0

CORLS0:	PUSH P,A
	SKIPE A,PAGSER"CBKERR	; Error code present?
	 JRST CORLS2		;  Yes, go check it.
	.SUSET [.RBCHN,,A]	; Else ask recent erring channel for error.
	SYSCAL STATUS,[A ? CRET A]
	 JSR SYSLOS
	LDB A,[$ERRCD,,A]	; Get # of error
CORLS2:	CAIE A,37		; Error = no core available?
	 JSR AUTPSY		; Nope, something horrible. die.
	TLNN F,%SCOPN		; Hurray, can re-try! If stats open, say so.
	 JRST CORLS5
	IUSAVE			; Save UUO vars since doing stats output.
	STAT (,(" ==="),RABR,(" CORBLK HUNG, SLEEPING..."))
	IUREST			; Then restore.
CORLS5:	MOVEI A,30.		; Now prepare to
	.SLEEP A,		; sleep for a second.
	POP P,A
	SOS CORLOS		; Then adjust return addr so that
	SOS CORLOS		; it points to call which failed.
	JRST @CORLOS		;and go try again.

;;; PAGLUZ - JSRd to by the PSRERR macro, which is used inside PAGSER.

LVAR 	PAGLUZ:	0 ? JRST PAGLU0	; Jump to pure
LVAR	PAGLCD:	0		; Error code.

PAGLU0:	MOVEM U4,PAGLCD		; Remember the PAGSER lossage code.
	JSR PRESRV		; Preserve COMSAT state.
	CAIN U4,PAGSER"EROOM	; Examine PAGSER error code.
	 JRST PAGLU2
PAGLU1:	POP P,U4		; If random error jump to our death.
	JSR AUTPSY
PAGLU2:	POP P,U4
	TLNE F,%SCOPN		; Else COMSAT ran out of address space.
	 JRST PAGLU3		; Ensure STATS file is open.
	MOVE B,[%DOWOV+.UAO,,SC]
	.CALL SCOBLK		
	 JRST PAGLU7		; And quit immediately on any error.
	SYSCAL FILLEN,[CIMM SC ? CRET A]
	 JRST PAGLU7		; Also quit if can't find length???
	.ACCESS SC,A		; Set length to EOF
	OUT(SC,OPEN(UC$IOT))	; And open UUO channel so hairy output works.
	TLO F,%SCOPN		; and indicate open!

PAGLU3:	STAT (,(" ==="),RABR,(" BUG: FATAL ERROR "),LABR,("===  Date: "),TIM(MDYT),("
	Autopsy from "),RHV(AUTPSY),(" Preserved from "),RHV(PRESRV),("
	Last UUO = "),H(U40SAV),(" at "),RHV(UUHSAV))
	STAT (,("	INT. FROM "),RHV(IPCSAV),("	.JPC/"),RHV(JPCSAV))
	STAT (,(" ==="),RABR,(" Out of address space! "),LABR,("==="),EOL)
PAGLU7:	OUT(SC,CLS)	; Close stats channel, forcing out any buffered stuff.
IFN 0,	JSR DEATH	; Die without leaving any kind of corpse.
	SKIPN DIDONE	;If we haven't won even once yet
	 JSR RNMREQ	; then current input file is probably the troublemaker.
	  NOP
	 JSR RESET	;Flush.



BVAR
OPRSRV:	0		; To prevent clobberage by nested lossage.
PRESRV:	0		; JSR'd to, so accs not disturbed
	JRST PRSRV0	; Jump to pure.
EVAR
PRSRV0:	SKIPE DEBUG
	 JSR DEATHV
	SKIPE OPRSRV
	 JSR UDEATH		; If err within error handling, die instantly.
	MOVEM P,PDLSAV		; Save P (must be ac 17!)
	MOVE P,PRESRV
	MOVEM P,OPRSRV		; Save return loc as clobberage protection.
	MOVE P,[0,,ACCSAV]
	BLT P,PDLSAV-1		; Save all accs
	MOVE P,[-XPDLLN,,XPDLOC]
	PUSHAE P,[A,B]
IFE $$UCAL,MOVE A,UUORPC ? MOVEM A,UUHSAV
	MOVE A,U40
	MOVEM A,U40SAV
	.SUSET [.RBCHN,,BCHSAV]
	MOVSI A,-20
	SYSCAL STATUS,[MOVEI (A) ? CRET STASAV(A)]
	 JFCL
	AOBJN A,.-2
	.SUSET [.RMPVA,,MPVA]
	MOVE B,[SQUOZE 0,USER]
	.EVAL B,
	 .VALUE
	MOVSS B
	HRRI B,B
	.GETLOC B,		; B gets my user index
	IRP SYM,,[LSPCL,LEXFDR]
	 MOVE A,[SQUOZE 0,SYM]
	 .EVAL A,
	  .VALUE
	 ADD A,B
	 MOVSS A
	 HRRI A,SYM
	 .GETLOC A,
	 TERMIN
	SETZM IPCSAV
	SETZM JPCSAV
	MOVE B,INTPDP		; Get interrupt PDL ptr
	CAMN B,[-LIPDL,,IPDL-1]	; Were we interrupted?
	 JRST PRESR2		; Nope, don't try to read interrupt stack.
	MOVE A,-3(B)		; Yes! Get loc interrupted from.
	MOVEM A,IPCSAV
	MOVE A,-2(B)		; and loc of last jump instr.
	MOVEM A,JPCSAV
PRESR2:	POPAE P,[B,A]
	JRST @PRESRV
	
	;;; STUFF SAVED FOR DEBUGGING CRASHES
BVAR
ACCSAV:	BLOCK 17	; All accs including...
PDLSAV:	0		; saved P (17).
UUHSAV:	0		; value at UUOH if using JSR (loc of last UUO)
U40SAV:	0		; value at U40 (last UUO)
JPCSAV:	0		; loc of last jump instr (if interrupted)
IPCSAV:	0		; loc interrupted from if any
BCHSAV:	0		; saved .BCHN
STASAV:	BLOCK 20	; saved STATUS of all channels
MPVA:	0		; Referencing address of last MPV/WRO
LSPCL:	0		; Last two exec PCs pclsred at
LEXFDR:	0		; fault bits,,PC of last error page fault from exec mode

XPDLLN==25
XPDLOC:	BLOCK XPDLLN	; Emergency PDL - avoid clobbering regular.
EVAR

; RNMREQ - Renames input request file (if any) to signify bad request.
;	Called by JSR.

LVAR RNMREQ:	0 ? JRST RNMRQ0
RNMRQ0:	SYSCAL RENAME,[SATDEV ? IRRFN1 ? IRRFN2 ? SATDIR
		IREFN1 ? IREFN2]
	 JRST @RNMREQ		; Assume nothing to rename.
	SYSCAL OPEN,[[.UII,,DMPCH] ? ['DSK,,]
		IREFN1 ? IREFN2 ? SATDIR]
	 JSR UDEATH
	SYSCAL RFNAME,[CIMM DMPCH ? CRET JUNK ? CRET BRQFN1 ? CRET BRQFN2]
	 JSR UDEATH
	AOS RNMREQ
	JRST @RNMREQ
BVAR
BRQFN1:	0
BRQFN2:	0
EVAR

; BURY - PDUMP's self to DSK:CRASH;BURNUP >
;	Called by JSR

.SCALAR BURNSV,BURYF,BURYP

LVAR BURY:	0 ? JRST BURY0	; PDUMP corpse so have something to look at.
BURY0:	SKIPE DEBUG
	 JSR DEATHV
	SYSCAL OPEN,[[.BIO,,DMPCH] ? ['DSK,,0] 
			['BURNUP] ? [SIXBIT />/] ? [SIXBIT /CRASH/]]
	 JSR UDEATH
	MOVEM A,BURNSV		; Save acc
	MOVE A,[DMPCH,,DMPDAT]
	.RCHST A,
	MOVEM F,BURYF
	MOVEM P,BURYP
	MOVSI 17,ACCSAV		; restore ACs so PDUMP can save them
	BLT 17,17
	SETZM DMPDAT
	SYSCAL PDUMP,[CIMM %JSELF ? CIMM DMPCH ? DMPDAT] ;Dump self.
	 JSR UDEATH
	MOVE A,[-8,,[
		JUMPA MAIN	; First start address
		-4,,2		; Indirect symbol table pointer
		SIXBIT/DSK/ ? SIXBIT/COMSAT/ ? SIXBIT/LAUNCH/ ? SIXBIT/.MAIL./
		0		; Hopefully no one cares about the checksum
		JUMPA MAIN ]]	; Second start address
	.IOT DMPCH,A
	.CLOSE DMPCH,
	MOVE A,BURNSV
	MOVE F,BURYF
	MOVE P,BURYP
	JRST @BURY

BVAR
DMPDAT:	0		;returned by .rchst to get true dump file names.
DMPFN1:	0
DMPFN2:	0
	0 ? 0
EVAR

; UDEATH - Kill Unique COMSAT.  Does special hacks to relinquish
;	uniqueness, and decides whether to disappear or stay around.
;	(If not unique, just disappears.)

LVAR UDEATH:	0 ? JRST UDEAT0	; Jump to pure.
UDEAT0:	SKIPE DEBUG	; If debugging, just halt.
	 JSR DEATHV
	OUT(SC,CLS)	; Close stats channel, forcing out any buffered stuff.
	.CLOSE LSR1C,
	.close lockch,
	.CLOSE DKIC,
	.CLOSE DKOC,
	.CLOSE NETD,
	.CLOSE NETI,
	.CLOSE NETO,

	; Decide whether to log out or leave corpse around.
	SKIPN HAVLKS	; Do we have the locks set up?
	 JSR DEATHV	; No, horrors... should always be able to get
			; at locks.  Hang around system for hacker to inspect.
	SKIPN UNIQUE	; If have locks, but job is not unique,
	 JSR DEATH	;  die, period.

	; Job is the one and only system comsat, and we have locks.
	; See if already too many dead comsats around for leaving corpse
	MOVEM A,DIESVA
	MOVE A,LOCK2		; Not really a lock; used as count.
	CAIL A,3		; Less than 3 so far?
	 JSR DEATH		; Too many, disappear.

	; Hang around.  Unlock switch 1 so other comsats can be started.
	MOVEM P,DIESVP
	MOVEI P,PATCH+PATLEN-10	; Set up small PDL to make sure we win.
	MOVEI A,LOCK1
	CALL LKFREE		; Free the lock!  A new COMSAT can now run...
	AOS LOCK2		; Increment count of dead comsats
	JSR DEATHV		; and start swinging in the breeze

.SCALAR DIESVA,DIESVP

BVAR	; JSR here when all ready to pull trigger.
DEATH:	0
	.LOGOUT
	CAIA
	; JSR here if only want to stop, or if trigger is jammed.
DEATHV:	0
	.VALUE		;if not toplevel, this will have to do.
	JRST .-1	;insist.
EVAR



; SDFULL - Satellite Directory Full.  Called when something happens
;	(IOC error, OPEN lossage) that indicates .MAIL. needs
;	to get more room before COMSAT can proceed at all.
;	Note that this routine must distinguish between true directory
;	gronkage, and general system disk shortage.
; SDFULI - same, but called at Interrupt level.

LVAR SDFISW:	0	; -1 if SDFULI, 0 if SDFULL.
LVAR SDFCNT:	-1	; Set -1 if all is well at top-level .HANG,
			; AOS'd each time SDFULL called.  Used to
			; detect SDFULL looping.

SDFULI:	SETOM SDFISW		; Interrupt level.
	IUSAVE			; Save UUO vars.
	CAIA
SDFULL:	 SETZM SDFISW		; Not at interrupt level.
	AOS SDFCNT		; Bump cnt of calls.
	PUSHAE P,[A,B,C]

	; First thing is to determine independently what the problem is.
	.SUSET [.RBCHN,,C]	; Find bad channel #
	SYSCAL STATUS,[C ? CRET B]	; and get IOS word.
	 JSR SYSLOS
	LDB A,[330500,,B]	; Find IOC error code.
	CAIN A,11		; Device full?
	 JRST SDFL20		; Yes, system running out of disk space.
	CAIN A,14		; Directory full?
	 JRST SDFL40		; Yes, satellite directory gronked.
	LDB A,[$ERRCD,,B]	; Hmm, no IOC error matched, try normal err.
	CAIN A,5		; Directory full?
	 JRST SDFL40		; Yes, go handle.

	; Come here when error cannot be recognized as either device full
	; (no disk space) or dir full (no room in comsat's dir).  For now,
	; and probably for ever, die.
	JSR AUTPSY

	; Return!! - jump here if one of the routines below thinks it's
	; succeeded (heh) in resolving the problem.
SDFL90:	POPAE P,[C,B,A]		; Hurray...
	SKIPN SDFISW		; If at interrupt level,
	 RET
	IUREST			; also restore UUO handler stuff.
	RET

	; Device Full - system is running out of disk space, and COMSAT
	; can't get enough disk to write something it needs.  Never die;
	; keep trying, and wait until room opens up.

LVAR QDSKN:	0	; Holds address of QDSKN table in system
LVAR QSFT:	0	; Holds address of QSFT table in system

SDFL20:
	; Try examining actual space left on drive being used by channel.
	LDB A,[000600,,B]
	CAIE A,43	; Is physical device code 43 = DSK?
	 JSR AUTPSY	; If not, something's screwy.
	HRLZ A,C
	ADD A,[.RIOC,,A]	; Cons up appropriate cruft to get IOCHNM+ch
	.SUSET A
	HLRZ A,A	; Get LH = disk channel #.
	SKIPN B,QDSKN	; Get system address of QDSKN table,
	 JRST [	MOVE B,[SQUOZE 0,QDSKN]
		.EVAL B,	; And get from system if haven't already.
		 JSR SYSLOS
		MOVEM B,QDSKN
		JRST .+1]
	ADDI A,(B)	; Get address in system to inspect (index by dsk ch #)
	HRLZ A,A
	HRRI A,A	; Now have <sys addr>,,<result addr>
	.GETLOC A,	; Examine it - now have physical drive # in A.
	SKIPN B,QSFT	; Now do similar kludgery for QSFT (tracks free on drive)
	 JRST [	MOVE B,[SQUOZE 0,QSFT]
		.EVAL B,
		 JSR SYSLOS
		MOVEM B,QSFT
		JRST .+1]
	ADDI A,(B)
	HRLZ A,A
	HRRI A,A
	.GETLOC A,	; Now have # tracks free in A!

	; If very small number free, shout.  Otherwise perhaps there's
	; now enough room...
	CAML A,SDFLIM
	 JRST [	MOVE A,SDFCNT	; Should be enough, but check count of calls
		CAILE A,3	; and be highly suspicious of prognosis
		 JRST SDFL35	; if calls are repetitive - try sleeping.
		JRST SDFL90]	; Else bravely return.

	; Try shouting.
	CALL DATIME"TIMGT	; Find current time,
	CALL DATIME"TIMSEC	; convert to abs # secs,
	MOVE B,A
	SUB B,LSHTIM	; And subtract last shout time.
	CAMGE B,SDFTLM	; Enough time since then to send another?
	 JRST SDFL35	; No, wait.

	; OK, we'll shout...
	MOVEM A,LSHTIM	; Save as new last-shout-time.
	SYSCAL PGWRIT,[CIMM LCKPAG]	; and make sure it's written on disk.
	 JSR SYSLOS
	MOVEI A,3
	MOVEM A,CLITYP	; Specify "TTY message from ..."
	MOVE A,[ASCNT [COMSAT]]
	MOVEM A,CLIANC	; "...COMSAT".
	SETZB A,CLIZSW	; With character limit of
	HLLOM A,CLIZMX	; 0,,-1 (effectively infinite).
	SETZM CLIHDR	; Don't bother with header, and use following
	MOVE A,[ASCNT [EVERYBODY: Disk space very low, please delete unnecessary files!]]
	MOVEM A,CLITXT	; as message text.
	SETZ A,		; Send to all HACTRN's...
	MOVEI B,CLISND	; using standard routine.
	CALL XCTALU	; Do it!
			; Now drop thru to sleep for a while...

SDFL35:	MOVE A,SDFSLP	; Get time to sleep
	.SLEEP A,
	JRST SDFL90	; Return and re-try whatever it was that lost.

LVAR SDFLIM:	50.	; # dsk blocks below which to shout about.
LVAR SDFTLS:	0	; Time (.rdtime) of last SHOUT
LVAR SDFTLM:	15.*60.	; Time to wait between shouts. (15 min)
SDFSLP:	30.*30.		; Half minute naps while waiting.

	; Directory Full - COMSAT's directory has been stuffed to the gills,
	; and nothing can be done (no matter how much disk the system has)
	; until some files are flushed!

;  In no case should COMSAT simply give up and die.  Persevere!
;   If problem is .MAIL. directory itself:
;    o SHOUT at selected people?
;    o Flush a special "buffer" file, giving enough room 
;      to finish and yell for help
;    o Write a MAIL > file directed to mail-maintainers
;    o Examine OSTATS, NAMES, NAMED for possible flush candidates; 
;	   don't flush most recent good NAMES, and don't flush an 
;	   OSTATS if not dumped, etc. nor if down to very last OSTATS.
;    o Can try some sleeping so someone can respond to cry for help.
; If problem persists, very last resort should be to die without freeing locks.

SDFL40:	JSR AUTPSY ;For now, just die!

IFN 0,[	;; Shout out knowledgable people to clean up .MAIL.; directory.
	;; Then go to sleep.
	;; If complete loss after a while, die without freeing locks.
	MOVE A,[ASCNT [MAIL-DIR-MAINT]]
	SETZ B,			; Match to any type.
	CALL RCPEQV		;get ptr to option list if any
	 JRST SDFL49
];IFN 0


SUBTTL *------------------- Entry & Init ---------------------------*
SUBTTL PURIFY$G - Purification

LVAR PURESW:	0	; If -1, program has been purified.

;;; PURIFY$G entry point - use when setting up a just-assembled comsat
;;;	for dumping as appropriate file on .MAIL.

PURIFY:	MOVE A,[<PURPGB-PURPGE>,,PURPGB]	; Get page AOBJN
	SYSCAL CORBLK,[CIMM %CBNDR ? CIMM %JSELF ? A ? CIMM %JSELF]
	 .LOSE 1000
	SETOM PURESW	; Set flag saying pgm is pure.
	MOVEI A,[ASCIZ /:PDUMP DSK:.MAIL.;COMSAT LAUNCH/]
	SKIPGE XVERS	; Experimental mailer?
	 MOVEI A,[ASCIZ /:PDUMP DSK:.MAIL.;COMSAT XPER/]
	SKIPLE XVERS	; New mailer?
	 MOVEI A,[ASCIZ /:PDUMP DSK:.MAIL.;COMSAT NEW/]
	.VALUE (A)	; Valret appropriate string.
	JRST INIT	; and go start up if continued.

; IPURIF - Internal Purify routine.

IPURIF:	PUSH P,A
	MOVE A,[<PURPGB-PURPGE>,,PURPGB]	; Get page AOBJN
	SYSCAL CORBLK,[CIMM %CBNDR ? CIMM %JSELF ? A ? CIMM %JSELF]
	 JSR SYSLOS	; Shouldn't happen.
	SETOM PURESW	; declare pure.
	POP P,A
	RET

	; Network code - must be here because NETRTS uses STAT and DISMISS.
.INSRT DSK:SYSNET;NETRTS >

SUBTTL MFINIT$G - Emergency File Directory Initialization

;	The following code initializes the satellite's data
; directory, and will never be executed unless started here
; manually.  The reasoning is that if a file required by the
; mailer is lost or garbaged, it is far better to halt and
; await human intervention than attempt to fix things automatically.
; If the lossage is impossible to remedy, this code will try
; to create a new set of files.
;	Interrupts are NOT enabled, and return is by .VALUE.
; This will re-create all existing mailer files, (except if ID exists
;   it will increment it instead of creating it.)  So be careful.

MFINIT:	MOVE P,[-PDLLEN,,PDL]	; Set up PDL
	SETZB F,MF		; and clear all flags.
	SETOM DEBUG		; Always indicate debugging.
	SYSCAL USRVAR,[CIMM %JSELF ? CIMM .ROPTION ? CIMM ; Set up options: Locked switch
			[TLO 0,%OPALL]]	; feature and winning new interrupts.
	 .LOSE 1000
	CALL MYADDR		; Set our address(es)
	SKIPE XVERS
	 CALL XVRSRT		; Set up right filenames if not normal!
	SKIPN PURESW		; and make sure
	 CALL IPURIF		; that what can be pure is pure!

	; The following routines need to be in the order given.  Since
	; it is desirable to enable the SC stats channel ASAP, the
	; first order of business is to determine if this incarnation
	; of the mailer is unique, i.e. can it reference & write to
	; the directory?
	CALL DATIME"UPINI
	 CALL MTMHNG		; UPINI lost, do something appropriate.
	CALL MLINIT		; And ensure uniqueness.
	SETZM LSHTIM		; (after grabbing locks, clear last shout time)
	CALL SCOPN		; Now initialize statistics file!
	CALL MCINIT		; Initialize core management.

	; Essential initializations done, now to create new data files.
	SYSCAL DELETE,[SATDEV ? NMGFN1 ? NMGFN1+1
		SATDIR ? CERR A]	; Delete NMSGS if it exists.
	 JRST [	CAIN A,4
		 JRST .+1
		JSR AUTPSY]
	SYSCAL DELETE,[SATDEV ? NMSFN1 ? NMSFN1+1
		SATDIR ? CERR A]	; Ditto NMASTR.
	 JRST [	CAIN A,4
		 JRST .+1
	 	JSR AUTPSY]

	SYSCAL OPEN,[[.BIO,,DKOC] ? SATDEV ? MSGFN1 ? MSGFN2 ? SATDIR]
	 .VALUE [ASCIZ /: Can't create MSGS file! 
/]
	.CLOSE DKOC,		; Close it, now exists with length 0.
	SYSCAL RENAME,[SATDEV ? IDFN1 ? IDFN2 ? SATDIR
		IDFN1 ? IDFN2]	; Attempt to produce new ID file
	 CAIA			; Skip to create if lost.
	  JRST MFINI5		; Jump if won, need not start over.
	SYSCAL OPEN,[[.BIO,,DKOC] ? SATDEV ? IDFN1 ? IDFN2 ? SATDIR]
	 .VALUE [ASCIZ /: Can't create ID file! 
/]
	.CLOSE DKOC,		; Close, now ID file exists...

	; Now create MASTER file.  First set up LSE...
MFINI5:	CALL MSTNIL		; Create nil MASTER.
	CALL MSTPUT		; Now write out, creating file!

	; Now have new MASTER file, must create RML and QML files.
	CALL MAKQFI		; Special routine to generate RML/QML given MASTER.

	; Now compile the EQV file...
	CALL EQVGOB		; Compile it!
	 .VALUE [ASCIZ /: Error(s) during EQV file compilation!  Read STATS or error file.
/]			; Foo?  Error during compilation, STATS should show why.

	; Now all done!
	.VALUE [ASCIZ /: File Directory Initialization successfully completed...
Proceeding will launch Comsat. 
/]
	MOVEI A,LOCK1		; Unlock switch 1 so can re-grab later.
	CALL LKFREE
	JRST INIT		; Go!

$LHMFG==$LHSIZ+0	; Define these to be located at end of LSE HDR area.
$LHMFL==$LHSIZ+1

SUBTTL MAKMST$G, etc - Emergency regeneration of MASTER and/or QML/RML files.

; Note: This is NOT FULLY DEBUGGED!!!
; ********** Cant work without STATS open.  Fix. **********

; MAKMST - Re-creates the MASTER file from the MSGS file.  This code
;	is never executed unless started here manually, and after it is
;	run, it should be killed and a new COMSAT started.  (On the fatal
;	day 7/9/77 the MC MASTER file got clobbered and this code was
;	typed into DDT by hand (by MOON and DLW), and we created MASTER all
;	right but accidentally clobbered MSGS! I don't ever want that to
;	happen again, so...  --dlw)

; Instructions: Save the old LIST MASTER (if it's worth it), and delete LIST
;   MASTER.   Then start at MAKMST.  If anything is wrong, it will tell you.
;   When done, kill this and start another COMSAT.

LVAR MAKING:	0		; Flag that we were started in this code.

MAKMST:	SETOM MAKING		; Flag.
	MOVE P,[-PDLLEN,,PDL]
	SETZ F,
	SETOM DEBUG
	SYSCAL USRVAR,[CIMM %JSELF ? CIMM .ROPTION ? CIMM ; Set up options: Locked switch
			[TLO 0,%OPALL]]	; feature and wining new interrupts.
	 .LOSE 1000
	CALL MYADDR	   	; Set address(es)
	SKIPE XVERS
	 CALL XVRSRT
	CALL DATIME"UPINI	; Get the time for MLINIT.
	 CALL MTMHNG
	CALL MLINIT		; Will go to MAKMNU if we are not unique.
	CALL MCINIT		; We need area hacking, etc.

	CALL MSTNIL		; Initialize MASTER LSE to NIL.
	MOVEI A,MSGFN1		; Open MSGS.
	CALL OPNMFI
	 .VALUE
	.SUSET [.SIMASK,,[%PIIOC]]	; Enable IOC handler for XCTIOC in LSEIN
	.SUSET [.SPICLR,,[-1]]		; And turn on interrupt system.
MAKM10:	MOVEI A,TMPAR		; Read the next message into MSGS.
	SETO B,
	CALL LSEIN
	 JRST MMCONT		; Got an IOC error, we must have hit the end.
	MOVE L,$ARLOC+TMPAR
	FINDA A,[A$ID,,[$LLLST(L)]]	; Get message ID.
	 .VALUE [ASCIZ \: ERROR! The current msg has no ID!!! \]
	MOVE L,$ARLOC+MASTER
	LNCOPY A,[$ARLOC+TMPAR ? A]	; And copy it into MASTER area.
	MAKELN B,[A$IDBL,,0	; Now get the disk length and addresses.  LSEIN handily
		  %LTVAL,,[ULSDBH]]	;   leaves them in these convenient locations.
	MAKELN B,[A$IDAD,,[B]
		  %LTVAL,,[ULSIPT]]
	HRRM B,LISTAR(A)	; NCONC these two onto the ID (order of these LNs is critical.)
	MAKELN A,[A$I,,[$LLLST(L)]	; And put this info onto one big list.
		  %LTLST,,[A]]
	MOVEM A,$LLLST(L)
	JRST MAKM10

	; LSEIN got an IOC error.
MMCONT:	SYSCAL STATUS,[CIMM DKIC ? CRET A]
	 .LOSE 1000
	LDB B,[330500,,A]	; Get the IOC error code.
	CAIE B,2		; Is it EOF?
	 .VALUE [ASCIZ \: ERROR: IOC error not EOF.  STATUS of DKIC in A. \]
	SYSCAL OPEN,[[.BIO,,DKOC] ? SATDEV ? [SIXBIT /_MAKMO/] ? [SIXBIT />/] ? SATDIR]
	 .LOSE %LSFIL
	MOVEI A,MASTER		; Write out MASTER to temporary file name.
	SETO B,
	CALL LSEOUT
	SYSCAL RENMWO,[CIMM DKOC ? MSTFN1 ? MSTFN2]
	 .LOSE %LSFIL
	.CLOSE DKOC,
	.VALUE [ASCIZ \: Super win.  Now kill this job, and start another COMSAT. \]
	JRST .-1		; In case loser proceeds.

MAKMNU:	.VALUE [ASCIZ \: ERROR -- There is ANOTHER COMSAT running! Stop it!!!
(Then start all over.)\]
	JRST .-1

;;; This routine, intended to be called by hand, will delete from
;;; MASTER any entries which aren't in QUEUE.  Ought to know about
;;; REMIND at some point also.
;;; This routine is not recommended for use except in dire emergency,
;;; because almost always MASTER contains valid stuff and the QML list
;;; is missing some messages it should have.  If called, the procedure should
;;; be to first call MAKQFS, to make sure that everything which can
;;; possibly be queued is definitely on the QML (or RML).  Then CLNMST
;;; can be called to flush anything which has no business being there at all.

CLNMST:	MOVE P,[-PDLLEN,,PDL]
	SETZ F,
	SETOM DEBUG
	SYSCAL USRVAR,[CIMM %JSELF ? CIMM .ROPTION ? CIMM ; Set up options: Locked switch
			[TLO 0,%OPALL]]	; feature and wining new interrupts.
	 .LOSE 1000
	CALL MYADDR	   	; Set our address(es)
	SKIPE XVERS
	 CALL XVRSRT
	CALL DATIME"UPINI		; Get the time for MLINIT.
	 CALL MTMHNG
	CALL MLINIT		; Will go to MAKMNU if we are not unique.
	CALL MCINIT		; We need area hacking, etc.
	.OPEN DBC,[.UAO,,'TTY]	; Will type-out about deleted msgs on DBC
	 .VALUE
	OUT(DBC,OPEN(UC$IOT))
	CALL QMLGET		; Get QUEUE into core
	CALL MSTGET		; Get MASTER into core
	;; Go over MASTER, deleting guys not in QUEUE
	MOVE L,$ARLOC+MASTER	; Make it current LSE
	MOVE A,$LLLST(L)	; Point to whole list as arg for MAPC
	MOVEI B,CLNMS1		; Apply this routine to each entry in MASTER.
	CALL MAPC		; Do it!
	 .VALUE			; won't ever happen
	.VALUE [ASCIZ/: Type $P to write it out 
/]
	.VALUE [ASCIZ/: Are you SURE?  Examine your conscience... 
/]
	.VALUE [ASCIZ/: All right, you have this one last chance...
/]
	.VALUE [ASCIZ/: So be it... 
 p/]
	CALL MSTPUT		; Next GC will clean up MSGS
	.VALUE [ASCIZ/: Done 
/]

;A has a MASTER element, B and C may be clobbered
CLNMS1:	PUSHAE P,[L,D,E]
	FINDA C,[A$ID,,[LISTAR(A)+1]]	; Find ID of this MASTER entry
	 .VALUE
	MOVE C,LISTAR(C)+1	; Get SPT
	ADD C,$LSLOC(L)		; Make absolute
	MOVE L,$ARLOC+QMLAR	; Select QUEUE area
	HRRZ D,$LLLST(L)	; Loop over hosts
	JUMPE D,CLNMS9
CLNMS2:	FINDA E,[%HSIGN+A$ID,,[LISTAR(D)+1] ? C]	; See if this ID is on this site
	 JRST CLNMS4		; No, keep looking
CLNMS3:	POPAE P,[E,D,L]		; Yes, this guy is OK, return
	JRST POPJ1		; Success return to MAPC

CLNMS4:	HRRZ D,LISTAR(D)	; CDR to next host
	JUMPN D,CLNMS2
CLNMS9:	EXCH A,C		; A gets ASCNT to message-ID
	CALL MSGGTA		; Get message into core
	 .VALUE			; Should be there
	EXCH A,C
	MOVE L,$ARLOC+MSGAR
	FINDA B,[A$MHDR,,[$LLLST(L)]]	;Get message header
	 .VALUE			; Should be there
	HLRZ D,LISTAR(B)+1	; Get length of header
	JUMPN D,CLNMS8		; Jump if OK
	FINDA B,[A$MTXT,,[$LLLST(L)]]	;Extract network header from message text
	 .VALUE
	MOVE D,LISTAR(B)+1	;Convert to absolute SPT
	ADD D,$LSLOC(L)
	HLRZ E,D
	HRLI D,440700
	PUSHAE P,[A,B]
CLNMS7:	ILDB A,D
	LSH B,7
	IOR B,A
	TLZ B,776000
	CAMN B,[<<<<<15*200>+12>*200>+15>*200>+12]
	 JRST [	POPAE P,[B,A]
		HLRZ D,LISTAR(B)+1	;Make shorter
		SUB D,E
		HRLM D,LISTAR(B)+1
		JRST CLNMS8 ]
	SOJG E,CLNMS7
	POPAE P,[B,A]
CLNMS8:	OUT(DBC,("Deleting message:"),EOL,TLS(B))
	MOVE L,$ARLOC+MASTER	; Delete from MASTER (code copied from IDDELE)
	FINDA B,[A$IDBL,,[LISTAR(A)+1]]	; Get length of LSE-blk on disk.
	 .VALUE
	MOVE B,LISTAR(B)+1	; Get # words used by LSE.
	ADDM B,$LHMFG(L)	; Increment cnt of garbage wds.
	LNDEL A,$LLLST(L)	; Now kill it.
	JRST CLNMS3		; And return to MAPC


;;; Routine to print the QML onto a disk file.
;;; Useful when queue is so bloated that SHOW-Q can't work.

SHOWQ:	MOVE P,[-PDLLEN,,PDL]
	SETZ F,
	SETOM DEBUG
	SYSCAL USRVAR,[CIMM %JSELF ? CIMM .ROPTION ? CIMM
			[TLO 0,%OPALL]]	    ; Locked switch and New Interrupts.
	 .LOSE 1000
	CALL MYADDR		; Set our address(es)
	SKIPE XVERS
	 CALL XVRSRT
	CALL DATIME"UPINI	; Get the time for MLINIT.
	 CALL MTMHNG
	CALL MLINIT		; Will go to MAKMNU if we are not unique.
	CALL MCINIT		; We need area hacking, etc.
	TLNN F,%SCOPN
	 CALL SCOPN		; Make sure stats channel is open!
	STAT (,("Service stopped for SHOWQ - listing QML to disk file."))
	SKIPN $AROPN+QMLAR
	 CALL QMLGET		; Get QUEUE into core
	SKIPN $AROPN+MASTER
	 CALL MSTGET		; Get MASTER into core
	.CALL [SETZ ? SIXBIT/OPEN/ ? [.UAO,,DBC] ? [SIXBIT/DSK/]
		[SIXBIT/GUBBLE/] ? SETZ [SIXBIT/>/] ]
	 .LOSE %LSSYS
	OUT(DBC,OPEN(UC$IOT))	; Open output file as standard-output.
	OUT(,CH(DBC))
	CALL PRTQML		; Try SHOW-Q routine now.
	 JFCL
	TLZ F,%MSTMD		; Get rid of in-core copy of MASTER.
	UARCLS MASTER
	UARCLS TMPAR
	TLZ F,%QMLMD		; Get rid of in-core copy of QUEUE.
	UARCLS QMLAR
	UARCLS TMPAR
	OUT(DBC,CLS)		; Close channels, making sure all's output.
	OUT(SC,CLS)
	.VALUE [ASCIZ /:Done
/]


; Hand-callable routine to GC the QUEUE and MASTER files.

GCQ:	MOVE P,[-PDLLEN,,PDL]
	SETZ F,
	SETOM DEBUG
	;; Set up options: Locked switch feature and wining new interrupts.
	SYSCAL USRVAR,[CIMM %JSELF ? CIMM .ROPTION ? CIMM [TLO 0,%OPALL]]
	 .LOSE 1000
	CALL MYADDR		; Set our address(es)
	SKIPE XVERS
	 CALL XVRSRT
	CALL DATIME"UPINI	; Get the time for MLINIT.
	 CALL MTMHNG
	CALL MLINIT		; Will go to MAKMNU if we are not unique.
	CALL MCINIT		; We need area hacking, etc.
	TLNN F,%SCOPN
	 CALL SCOPN		; Make sure stats channel is open!
	CALL GCQ0
	.VALUE [ASCIZ/: Done! 
/]

GCQ0:	STAT (,("Garbage collecting QUEUE and MASTER"))
	SKIPN $AROPN+QMLAR
	 CALL QMLGET		; Get QUEUE into core
	SKIPN $AROPN+MASTER
	 CALL MSTGET		; Get MASTER into core
	MOVEI A,TMPAR		; Make TMPAR be new MASTER (code from MSTNIL)
	CALL LSEOPN
	MOVEI A,2
	UAREXP A,TMPAR		; Expand HDR area by 2, to make sure
	MOVE L,$ARLOC+TMPAR	; that there's room for storage of
	MOVE A,$ARLOC+MASTER
	MOVE B,$LHMFG(A)	; Copy MSGS garbage data
	MOVEM B,$LHMFG(L)
	MOVE B,$LHMFL(A)
	MOVEM B,$LHMFL(L)
	MOVE B,$LLLST(A)
	LNCOPY A,[MASTER ? SETZ B]
	MOVEM A,$LLLST(L)
	MOVE A,[TMPAR,,MSTFN1]	; Output the results
	CALL DFPUT
	TLZ F,%MSTMD		; And get rid of in-core copy
	UARCLS MASTER
	UARCLS TMPAR
	MOVEI A,TMPAR		; Make TMPAR be new QUEUE 
	CALL LSEOPN
	MOVE L,$ARLOC+TMPAR
	MOVE A,$ARLOC+QMLAR
	MOVE B,$LLLST(A)
	LNCOPY A,[QMLAR ? SETZ B]
	MOVEM A,$LLLST(L)
	MOVE A,[TMPAR,,QMLFN1]	; Output the results
	CALL DFPUT
	TLZ F,%QMLMD		; And get rid of in-core copy
	UARCLS QMLAR
	UARCLS TMPAR
	RET


;;; Routine to convert a MSGS file into ascii (more or less)
;;; Patch MSTFN1 and MSGFN1 to appropriate file names first

ASCMSG:	MOVE P,[-PDLLEN,,PDL]
	SETZ F,
	SETOM DEBUG
	;; Set up options: Locked switch feature and wining new interrupts.
	SYSCAL USRVAR,[CIMM %JSELF ? CIMM .ROPTION ? CIMM [TLO 0,%OPALL]]
	 .LOSE 1000
	CALL MYADDR		; Set our address(es)
	SKIPE XVERS
	 CALL XVRSRT
	CALL DATIME"UPINI	; Get the time for MLINIT.
	 CALL MTMHNG
	CALL MLINIT		; Will go to MAKMNU if we are not unique.
	CALL MCINIT		; We need area hacking, etc.
	CALL MSTGET		; Get master LSE, used to drive this stuff
	;Note that we don't enable interrupts
	.CALL [SETZ ? SIXBIT/OPEN/ ? [.UAO,,DBC] ? [SIXBIT/DSK/]
		[SIXBIT/GUBBLE/] ? SETZ [SIXBIT/>/] ]
	 .LOSE %LSSYS
	OUT(DBC,OPEN(UC$IOT))
	MOVE L,$ARLOC+MASTER
	HRRZ D,$LLLST(L)
ASCMS1:	JUMPE D,ASCMS9
	FINDA A,[A$IDAD,,[LISTAR(D)+1]]
	 .VALUE
	MOVE A,LISTAR(A)+1	;Disk address
	MOVEM D,MASTRP
	PUSH P,D
	CALL MLSGET
	 .VALUE
	MOVE L,$ARLOC+MSGAR
	SETZM DEBLEV
	MOVE A,$LLLST(L)
	CALL DEBPL
	POP P,D
	MOVE L,$ARLOC+MASTER
	HRRZ D,LISTAR(D)
	OUT(,CH(DBC),C(^L),CRLF)	; Separator between messages
	JRST ASCMS1

ASCMS9:	.CLOSE DBC,
	.VALUE

;;;;;;;;;;;;;;;;;;;;
;	Emergency re-initialization of QUEUE and REMIND!!
;	You can run this standalone by starting at the usual place
;	with a breakpoint at WAKEUP, then CALL MAKQFSX

MAKQFI:	PUSH P,A		; Entry point to completely re-initialize
	MOVEI A,QMLAR		; the QML.
	CALL LSEOPN		; Create empty QML
	POP P,A
	CAIA
MAKQFS:	 CALL QMLGET		; Don't initialize QML, use existing.
	TLNE F,%SCOPN
	 CALL SCOPN		; STATS file must be open.
	PUSHAE P,[A,B,L]
	SETOM MKQFLG'		; Flag to decrease file i/o so goes fast
	SKIPN SCHDAR+$AROPN
	 CALL SCHINI		; Need this for QUEUE-ing.
	MOVEI A,RMLAR
	CALL LSEOPN		; Same thing for
	CALL MSTGET		; Ensure MASTER loaded.
	MOVE L,$ARLOC+MASTER	; Make it current LSE
	MOVE A,$LLLST(L)	; Point to whole list as arg for MAPC
	MOVEI B,MAKQMG		; Apply this routine to each entry in MASTER.
	CALL MAPC		; Do it!
	 .VALUE			; won't ever happen, MAKQMG catches own errs.
	CALL QMLPUT		; Done, write stuff out
	CALL RMLPUT
	CALL MSTPUT
	SETZM MKQFLG
	POPAE P,[L,B,A]
	RET

MAKQMG:	PUSH P,D		;not protected by MAPC
	FINDA A,[A$ID,,[LISTAR(A)+1]]	; Find the ID for entry
	 .VALUE [ASCIZ /: MASTER bad?? No message-ID for entry! 
/]
	CALL MSGGET		; Get this message into core
	 .VALUE [ASCIZ /: Can't retrieve message MASTER claims to have!! 
/]
	PUSH P,L
	MOVE L,$ARLOC+MSGAR
	FINDA A,[A$TLST,,[$LLLST(L)]]	; Reminder?
	 JRST [	CALL QUEUE		; No, Queue all live rcpts in it!
		JRST MAKQM7]
	CALL REMIND
MAKQM7:	POP P,L
	POP P,D
	AOS (P)		; MAPC wants win return
	RET

; MSTNIL - Set MASTER LSE to nil.  Used whenever creating new MASTER.
;	Note it doesn't actually write out the LSE, use MSTPUT for that.

MSTNIL:	PUSHAE P,[A,L]
	MOVEI A,MASTER
	CALL LSEOPN		; Open a fresh LSE as new MASTER.
	MOVEI A,2
	UAREXP A,MASTER		; Expand HDR area by 2, to make sure
	MOVE L,$ARLOC+MASTER	; that there's room for storage of
	SETZM $LHMFG(L)		; <# garbage wds in MSGS file>, and
	SETZM $LHMFL(L)		; <length of MSGS file>.
	POPAE P,[L,A]
	RET

SUBTTL	RESET INITIALIZATION

;;; RESET - This can be JSRd to when COMSAT runs out of address space. 
;;; Shuts down everything and starts all over.

;;; About as subtle as a tank, we just close everything down and reload
;;; from well-known filename.  Since this is essentially a controlled
;;; crash, it ought to be safe from just about any point in the code.
;;; If this still doens't work right, we can always just log out and
;;; let Puff reboot us in an hour....

LVAR 	RESET:	0 ? JRST RESET0	; Jump to pure

RESET0:	IFN 0,	JSR DEATH	;Doesn't work yet...
	TLNE F,%SCOPN
	 PSTAT (,("Note: RESET called from "),RHV(RESET),(", reloading."),EOL)
	TLZE F,%SCOPN		; Shutdown stats channel cleanly.
	 OUT(SC,CLS)
	MOVEI A,LOCK1		; Unlock switch 1 so can re-grab later.
	CALL LKFREE
	.IOPDL			; Flush the IOPDL
	REPEAT 20,[ ? .CLOSE .RPCNT, ]	; Close channels with sledgehammer.
	SYSCAL OPEN,[[.UII,,DKIC] ? [SIXBIT 'DSK'] ? [SIXBIT 'COMSAT']
			[SIXBIT 'LAUNCH'] ? [SIXBIT '.MAIL.']]
	 .LOSE %LSFIL		; Well known file, must be present
	.SUSET [.SXJNAME,,BOOTXJ]	; Barf, COMSAT BULK crock
	MOVSI 17,RSTACS		; Set up AC block
	BLT 17,17
	JRST 0			; And dispatch to it, reload or bust.

;; Stuff to load into ACs to reload self from file open on DKIC.
RSTACS:	OFFSET -.
	.CALL RSTLOD		; 0/  Load self
	 .LOSE %LSSYS		; 1/  Huh?
	.IOT DKIC,RSTSTA	; 2/  Get start address
	.CLOSE DKIC,		; 3/  Done with disk channel
	JUMPN RSTSTA,@RSTSTA	; 4/  Dispatch
	.LOSE			; 5/  Oops
RSTSTA:	0			; 6/  Filled in with start address
RSTLOD:	SETZ			; 7/  .CALL block
	SIXBIT 'LOAD'		; 10/
	MOVEI %JSELF		; 11/ Job is self
	MOVEI DKIC		; 12/ Shiny new disk channel
	SETZ .+1		; 13/ Address range to load
	20,,-1			; 14/ Everything but ACs
	REPEAT <17-.>,[ ? 0]	; Fill
	OFFSET 0		; End of AC block.

;; This stuff dates from CStacy, I think, and has never been used.
;; It was a nice try, though....
IFN 0,[
	TLNE F,%SCOPN
	 PSTAT (,(" ==="),RABR,(" RESET FROM "),RHV(RESET),LABR,("==="),EOL)
	SKIPE DEBUG		; If debugging just die.
	 JSR DEATHV
	TLZE F,%SCOPN		; Shutdown stats channel.
	 OUT(SC,CLS)
	.CLOSE LSR1C,		; Shutdown other I/O channels.
	.close lockch,
	.CLOSE DKIC,
	.CLOSE DKOC,
	.CLOSE NETD,
	.CLOSE NETI,
	.CLOSE NETO,
	MOVSI C,-NAREAS		; Shutdown all dynamic areas.
RESET1:	MOVE B,ARPTBL(C)
	UARCLS (B)
	AOBJN C,RESET1
	MOVEI B,SCHDAR		; Even zap the scheduler list!
	UARCLS (B)
	MOVE P,[-PDLLEN,,PDL]	; Reset PDL.
	SETZB F,MF		; Clear all flags
	.CORE LCKPAG+1		; Flush all dynamic and system core.
	 JSR DEATHV		; Leaves the locks intact!
	SETZM LSRTNS"LSRADR
	MOVEI A,400
	MOVEM A,LSYSPG
	SETZM SYSFPG
	SETZM FSYSWD
	SETZM NQMFWR
	SETZM FFPAG
	SETZM MAPPNT
	SETZM ARPAGS
	CALL SCOPN		; Reopen for business.
	OUT(SC,EOL)
	STAT (,("RESTARTING!  THE DATE IS "),TIM(F1),("
	BOOSTER WAS: "),6F(PRGNAM),(" #"),6F(VERSHN),EOL)
	CALL MCINIT		; Map core.
	SETOM GCQFLG		; Say GC when convenient.
	CALL SCHINI		; Init scheduler list.
	CALL SCHREM		; Init reminder list.
	CALL SCHNTA		; Schedule net sites on QML.
	JRST WAKEUP		; Go wakeup!
];0


SUBTTL INIT$G - NORMAL STARTUP INITIALIZATION

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;							;;;;;;;;
;;;;;;;;		STARTUP INITIALIZATION			;;;;;;;;


	; First, enable bad-condition interrupts as fast as possible.
INIT:	MOVE P,[-PDLLEN,,PDL]		; Set up PDL
	SETZB F,MF			; Clear all flags
	MOVE A,[-6,,[
		.ROPTION ? TLO 0,%OPALL	; Use new mode ints & lock hacking.
		.RMASK ? IOR 0,[%BADPI]	; Specify interruptable bad conditions,
		.RPICLR ? MOVE 0,[-1]]]	; and enable them.
	SYSCAL USRVAR,[CIMM %JSELF ? A]
	 JSR SYSLOS		; ??
	CALL MYADDR		; Set our address(es)
	CALL BULKCK		; Figure out if we are the bulk mailer
	SKIPE XVERS		; New or experimental version?
	 CALL XVRSRT		; If so, change various parameters before touching files
	SKIPN PURESW		; And now, make sure
	 CALL IPURIF		; that pages are pure!
	CALL DATIME"UPINI	; Now initialize time routines for MLINIT.
	 CALL MTMHNG		;   If can't get, hang waiting for time!!
	CALL MLINIT		; Now ensure uniqueness!

	; OK, this process is unique -  now committed to coming up!
	; Decide whether need to disown self or not.
	SKIPE DEBUG		; If debugging,
	 JRST INIT10		; don't disown.
	.SUSET [.RINTB,,A]	; See if we are top-level
	JUMPL A,INIT10		; If so, no need to disown!
	.SUSET [.ROPTION,,A]	; If not top-level, see if superior is DDT.
	TLNE A,%OPDDT		; Skip if superior not DDT.
	 .VALUE [ASCIZ /
5: Launched!  T plus 1 msec 
/]				; Disown as top-level demon.

	; Now assume we're disowned... open stats and print
	; startup message.
INIT10:	CALL SCOPN		; Initialize SC statistics channel.
	.SUSET [.RUNAM,,A]	; Get UNAME (here same as uname of creator)
	.SUSET [.RJNAM,,B]	; and JNAME
	OUT(SC,EOL)
	STAT (,("POWERING UP!  THE DATE IS "),TIM(F1),("
	BOOSTED BY "),6F(PRGNAM),(" #"),6F(VERSHN),(" LAUNCHED BY "),6F(A),(" AS "),6F(B),EOL)

	; OK, can now run assured that any lossage from this
	; point on will be recorded in stats file!
	CALL MCINIT		; Initialize core mappage and management.
	CALL MTINIT		; Check out time to ensure correct!
	CALL SUNAME		; Now that core is mapped, set name.

	; Initialize files if necessary, to recover from possible crash
	; during GC'ing of MSGS.
	CALL MGCFIX		; Fix whatever.
	SETOM GCQFLG		; Garbage collect the queues when convenient

	; Must initialize Schedule List.  This implies setting up
	; initial requests for any reminders or network sites which
	; are queued.
	CALL SCHINI		; Create initial schedule list.
	CALL SCHREM		; Find first reminder time due and schedule it.
	CALL SCHNTA		; Find and schedule delivery sites.

	; All done with initialization!  Now proceed as if waking up from
	; sleep...
	JRST WAKEUP

SUBTTL INIT - Time checking

;;; The following performs follow-up checking on system time, after
;;; DATIME"UPINI has been called.  Attempts to perform an error check -
;;; This can be done in 2 ways:
;;;	[1] Compare given time with other network sites.
;;;	[2] Compare given time with last reference date of a file.
;;; First we try [1].  If more agree than disagree, return quietly.
;;; Else if more disagree, attempt to get a human to fix it, and halt.
;;; If there is a tie (which happens when all hosts are down, also)
;;; then use [2] as a tiebreaker.

;;; This code is a bad idea.  ALAN, JTW, & SRA, 21 March 86.

MTINIT:	IFE 0,RET		; Code loses, disable it.
.ELSE {				; For posterity...
	PUSHAE P,[A,B,C,D]
	SETOM MTINIF'		; Set no call for assistance yet flag
MTINI0:	SETZ D,			; Clear match-mismatch counter
	MOVSI B,-NTMSIT		; Loop thru time-site table
MTINI3:	MOVE A,MTMSTB(B)
	CAMN A,OWNHST		; Don't do own site.
	 JRST MTINI5
	CALL NETIM		; Find time at that site.
	 JRST MTINI5		; Site down or can't get.
	MOVE C,A
	CALL DATIME"TIMGTN	; Get net-style time here.
	 JSR AUTPSY
	SUB A,C			; Find difference in time.
	MOVMS A			; Get magnitude
	CAMLE A,MTOKDF		; Compare with allowable difference.
	 AOSA D			; Ugh, bad!  mark 1 mismatch.
	  ADD D,[1,,0]		; Aha, mark 1 match.
MTINI5:	AOBJN B,MTINI3

	; Now compare matches with mismatches.  The majority rules!
	JUMPE D,MTINI9		; If neither match nor mismatch, try a file
	HLRZ A,D		; Get # matches in A
	CAILE A,(D)		; More matches than mismatches?
	 JRST MTINI6		; Yup, all clear!

	; Ugh, network time hacking says our time is wrong!
MTINI7:	PUSH P,A		; Save for ease in debugging
	.SUSET [.RINTB,,A]	; See if toplevel
	 JUMPL A,MTINI8		; Jump if so
	.SUSET [.ROPTION,,A]	; Not toplevel, see if under DDT
	TLNN A,%OPDDT		; Skip if under DDT.
	 JRST MTINI8		; Nope
	POP P,A
	.VALUE [ASCIZ /: System time is wrong or inaccessible!
/]
	JRST MTINI0		; If proceeded, try again.

; Time no good, and we are disowned.
; Call for help!  Maybe someone just brought the system up with
; the time wrong and is near the system console.
; Clobber instruction at MTINI8 to JSR AUTPSY if you think you
; are having problems.
MTINI8:	POP P,A			;Get back debugging info
	.OPEN TMPC,[.UAO,,'T00]
	 JRST MTIN8A
	AOSE MTINIF		;Only call for help once
	 JRST MTIN8A
	OUT(TMPC,OPEN(UC$IOT),("

"),C(7),C(7),("Hey! The time is messed up.  Fix it! (:PDSET?)
	Yr mst obdt svt,"),C(7),("
		"),6F(UNMPRF),SP,6F(JNMPRF),("
"))
MTIN8A:	.CLOSE TMPC,
	MOVEI B,30.*30.		;Wait half a minute then try again
	.SLEEP B,
	JRST MTINI0

MTINI9:	SYSCAL OPEN,[[.UAI,,NETD]	; No info from network, try a local file
		SATDEV ? STFN1 ? STFN2 ? SATDIR]
	 JRST MTINI6		; Oh well
	SYSCAL RFDATE,[CIMM NETD ? CRET A]
	 JSR SYSLOS
	.CLOSE NETD,
	CALL DATIME"TIMSEC	; Abs seconds that file last written
	MOVE C,A
	CALL DATIME"TIMGTN	; Abs seconds current time
	 JSR AUTPSY
	SUB A,C			; Number of seconds elapsed since last wrote
	JUMPL A,MTINI7		; Hmm, time must have run in reverse
	CAML A,[7*24.*60.*60.]
	 JRST MTINI7		; No mail sent for a week? I doubt it
MTINI6:	POPAE P,[D,C,B,A]	; Win.  time is satisfactory.
	RET

MTOKDF:	15.*60.		; # seconds of tolerable difference between site times.

			; Table of sites running TIMSRV's.
MTMSTB:	HN$MC			; MC
;	HN$AI			; AI
;	HN$DM			; DM
;	HN$ML			; ML
;	HN$A67			; AMES-67
;	HN$SAI			; SAIL
NTMSIT==.-MTMSTB

};.ELSE IFE 0


;;; Well known addresses.  This should be removed someday, or at least
;;; replaced with cells initialized at boot time....

DEFINE HNARP IMP,HOST
<RESOLV"NW%ARP>+<IMP>+<HOST_16.>!TERMIN
DEFINE HNCHA NUM
<RESOLV"NW%CHS>+<NUM>!TERMIN

; Arpanet is gone (well, almost gone), most of this is useless.
;HN$AI=<HNARP 6,2>
;HN$MC=<HNARP 54,3>
;HN$SAI==:<HNARP 13,0>
;HN$DM==:<HNARP 6,1>
;HN$ML==:<HNARP 6,3>
;HN$A67==:<HNARP 20,0>
;HN$MX==:<HNARP 6,1>
;HN$XX==:<HNARP 54,0>

; Let's try chaosnet addresses for the moment
HN$AI==:<HNCHA 3130>			; ITS machines
HN$MC==:<HNCHA 3131>
HN$MD==:<HNCHA 3132>
HN$ML==:<HNCHA 3133>
HN$LCS==:<HNCHA 15044>			; LCS.MIT.EDU mail relay machine

; Magic address to drop into TCPGAT if we're a chaos-only machine.
GATHST:	HN$LCS

; MTMHNG - Time Hanger... called immediately after determining that system
;	doesn't know time.  If running under DDT, will .VALUE appropriate
;	string and halt, else will attempt to wait it out.

MTMHNG:	PUSH P,A
	.SUSET [.RINTB,,A]	; See if toplevel
	JUMPL A,MTMHN5
	.SUSET [.ROPTION,,A]	; No, see if under DDT.
	TLNN A,%OPDDT		; Skip if so.
	 JRST MTMHN5
	.VALUE [ASCIZ /: System doesn't know proper time!! 
/]
	JRST .-1

	; Not under DDT, wait til we win.
MTMHN5:	CALL DATIME"UPINI
	 CAIA
	  JRST POPAJ		; Aha, have the time now!
	MOVEI A,30.*15.		; Else sleep 15 sec
	.SLEEP A,
	JRST MTMHN5		; and try again.


;;; MYADDR - Set OWNHST and OWNHS2.

MYADDR:	MOVE A,[RESOLV"NW%ARP]	; Normally use Arpanet
	CALL RESOLV"OWNHST	; Find # of host we're on
	 SETZ A,		; Don't appear to have Arpanet
	MOVEM A,OWNHST		; Store what we got
	MOVE B,GATHST		; Address of the default relay machine
;	CAME A,[HN$MX]		; Make Gumby happy by faking this for MX
	JUMPN A,.+3		; Are we on the Arpanet?
	  SKIPN HDRGAT		; No, if there isn't a patched in value
	   MOVEM B,HDRGAT	; Use the default.
	JUMPN A,.+3		; This doesn't happen for MX though
	  SKIPN TCPGAT		; Unless there's a patched in TCP relay
	   MOVEM B,TCPGAT	; Store the default relay.
	JUMPN A,.+3		; MX doesn't exist anymore anyway
	  SKIPN DOMGAT		; Unless there's a patched in domain relay
	   MOVEM B,DOMGAT	; Store the default relay.
	MOVE A,[RESOLV"NW%CHS]	; Now check Chaosnet
	CALL RESOLV"OWNHST	; Try Chaosnet now
	 SETZ A,		; No chaosnet
	SKIPN OWNHST		; No arpanet?
	 EXCH A,OWNHST		; Well, use Chaos as primary, then
	MOVEM A,OWNHS2		; Store secondary address
	SKIPN OWNHST		; No networks at all???
	 JSR AUTPSY		; Hopeless case, punt
	RET			; On Arpanet, win.


SUBTTL INIT - Memory management (allocation, mapping)

; Core in mailer satellite is arranged as follows:
; Beg of address space
;  PGM Impure
;  PGM Pure	20-some pages
;  LOCKS	1 page for lock switch vars, global impure.
;  NQMFWR	1 page mapped into sys for read access to NQMFWR variable
;  LSR1		Approx 10 pages for mapping the paged LSR1 file.
;  HOSTS3	Approx 60 (!) pages for mapping the entire HOSTS3 file.
;  FFPAG 	Freespace controlled by core manager (for areas, etc.)
;  SYSCOR	Enough core to pure-read-map into all system user-var storage.
; End of address space

BVAR
FFPAG:	0	; Number of the first truly FREE page to be given to CORSER.
MAPPNT:	0	; Argument to MAPIN.  (Core page AOBJN).
ARPAGS:	0	; Core page AOBJN defining free, dynamically useable pages.
SYSFPG:	0	; # of first page mapped into system
FSYSWD:	0	; Address of first word mapped into system (SYSFPG*2000)
LSYSPG:	400	; # of page following last sys-mapped page.
NQMFWR:	0	; Our address to access system NQMFWR at.
EVAR

; MCINIT - initializes core areas with exception of
;	locked-switch mapping.

MCINIT:	PUSHAE P,[A,B]
	CALL MAPSYS	; Get system pages mapped into high core.
	CALL MAPNQM	; Map in single page for system NQMFWR variable.
	MOVEI B,FREEPG	; Now generate a word with -<SYSFPG-FREEPG>,,FREEPG
	SUB B,SYSFPG	; as the initial "argument" to MAPIN.
	HRLZ B,B	; The real value of MAPPNT will get generated
	HRRI B,FREEPG	; later, after we know how much space they want.
	MOVEM B,MAPPNT	; Hopefully LSR1 won't grow too much
	CALL MAPIN	; during the life of the COMSAT.
	MOVEM A,FFPAG	; And this is the first page we will give to CORSER.
	SUBI A,FREEPG+1
	MOVN A,A
	HRLZ A,A
	HRRI A,FREEPG 	; Now have a "page aobjn" to  space for LSR1, HOSTS3.
	MOVEM A,MAPPNT	; Update ptr for MAPIN to use in the future.
	MOVE A,FFPAG
	SUB A,SYSFPG
	ADDI A,1
	HRLZ A,A
	HRR A,FFPAG		; Now we have a "page aobjn" to the dynamically
	MOVEM A,ARPAGS		; allocated space i.e. -<SYSFPG-FFPAG-1>,,FFPAG
	TLNE F,%SCOPN
	 PSTAT (,("Note: Dynamic freespace is: "),LHS(A),(",,"),RHV(A))
	UARINIT ARPAGS		; Initialize area UUOs and CORSER.
	MOVSI A,-NAREAS		; Make sure all ARBLKs declared closed,
	MOVE B,ARPTBL(A)	; by getting ARPT to each
	SETZM $AROPN(B)		; and zapping.
	AOBJN A,.-2
MCINI9:	POPAE P,[B,A]
	RET

	;Kludge to get maximal response to files on .mail. --  ITS has
	;variable "NQMFWR" which is aos'ed each time write files are closed on
	;.MAIL. directory.  The COMSAT maps a page into the proper area of
	;system to access this variable, and .hangs on it, so that satellite
	;is unhung and "started" as soon as something is written.

MAPNQM:	PUSHAE P,[A,B]
	MOVE A,[SQUOZE 0,NQMFWR]
	.EVAL A,		; Find location of NQMFWR in system.
	 JSR SYSLOS
	IDIVI A,2000		; Get page # in A, addr within page in B
	ADDI B,SYSWPG*2000	; Find address within own core to reference.
	SYSCAL CORBLK,[CIMM %CBRED+%CBNDR ; Must get read acc to syscor.
		CIMM %JSELF	;for us
		CIMM SYSWPG	;at spec'd page
		CIMM %JSABS	;from system (absolute),
		A]		;from specified absolute page.
	 JSR SYSLOS
	MOVEM B,NQMFWR		; Store addr to reference for NQMFWR access.
	POPAE P,[B,A]
	RET


; MAPIN - Map in the LSRTNS stuff.
;   MAPPNT/  page aobjn
; Returns in A: the number of the first page NOT used.
; LSR1C channel is left open so we can page that database.
; For historical reasons, we also set up things that used to be related
; to HOSTS3 (OWNNAM et al). 

MAPIN:	PUSHAE P,[B,D]
	MOVE D,FFPAG		; Assume free space isn't changing.
	.IOPUSH DKIC,		; Don't clobber chan.
	;; See if we need a new LSR1 database.
	SKIPN LSRTNS"LSRADR	; If LSR1 database is not mapped in
	 JRST MAPI10		;  we need to get it.
	MOVEI A,LSRFN1		; Already mapped in, but may be out of date.
	CALL OPNMFI		; Check for update notification file.
	 JRST MAPI20		;  None, so current version is fine.
MAPI10:	;; Let's just assume this works right, ok?
;	TLNE F,%SCOPN
;	 PSTAT (,("Note: Gobbling LSR1 database"))
	MOVEI A,LSR1C
	MOVE B,MAPPNT
	CALL LSRTNS"LSRMAP	; Map in LSR1 database, LSR1C remains open!
	 JSR AUTPSY
	HRRZ D,B		; New begining of free space
	ADDI D,3		; Allow some slop into which LSR1 can grow
	;; We have the latest LSR1 data, so delete the update notification.
	SYSCAL DELEWO,[%CLIMM,,DKIC]
	 NOP
MAPI20:	.CLOSE DKIC,
	PUSH P,D		; Stash # of 1st page not used up
	MOVE A,[440700,,OWNNAM]	; Get primary hostname (asciz string)
	MOVE B,OWNHST		; (Chaos and IP names are identical, by fiat)
	CALL RESOLV"HSTSRC	; Get string
	 JSR AUTPSY		;  We don't know about ourselves?
	SKIPN B,HDRGAT		; Same for gateway if present
	 JRST MAPI90		; Guess not
	MOVE A,[440700,,GATNAM]	; Is there, get its name for header munging
	CALL RESOLV"HSTSRC
	 JSR AUTPSY		; Better find it if are using it!
MAPI90:	POP P,A			; Return # of 1st page not used up.
MAPI99:	.IOPOP DKIC,		; Restore chan.
	POPAE P,[D,B]
	RET


; MAPSYS - hair which maps right section of system into our high
;	core, so that can access the user variables for all jobs, without
;	mapping in entire system.

MAPSYS:	PUSHAE P,[A,B,C,D]
	MOVE A,ITSYMC
	CALL EVALER		;evaluate constant syms
	MOVE A,ITSYML
	CALL EVALER		;and random locations
	MOVE A,ITSYMX
	CALL EVALER		;and indexable user var locations.
	.SUSET [.RUIND,,C]	;Set up MYUIND for SUNAME
	IMUL C,LUBLK
	MOVEM C,MYUIND
	MOVE C,MAXJ		;get max # jobs allowable
	IMUL C,LUBLK		;multiply by storage/luser to get # wds total storage
	ADD C,USRSTG		;add addr of first user var blk, to get highest sys addr
	MOVE A,USRHI		;now get addr of only non-user loc we must map.
	TRZ A,1777		;get addr of page containing usrhi.
	SUB C,A			;now get # wds we need mapped
	ADDI C,1777
	LSH C,-10.		;now have total # pages required, starting from (a).
	MOVEI B,400		;get total # pages avail in job
	SUB B,C			;get page # in us to start sys mapping at.
	MOVEM B,SYSFPG		;store as # of first sysmap page (400 is last+1)
	LSH B,10.		;get addr for it
	MOVEM B,FSYSWD		;also store
	SUB B,A			;find difference, <our mapping>-<its mapping>

	ADDM B,USRHI		;get our mapping for usrhi
	MOVE C,ITSYMX		;now get mappings for user vars
	HRLI B,C		;make them indexed by c!
	ADDM B,1(C)		;map
	ADDI C,1		;get next sym
	AOBJN C,.-2
	
	; a has first sys addr we want, lsyspg-sysfpg is # pages to insert at sysfpg.
	LSH A,-10.		;get system page # to start mapping at
	MOVE B,LSYSPG
	SUB B,SYSFPG		;get # pages desired
	IMUL B,[-1,,0]
	HRR B,SYSFPG		;now have -<# pgs>,,<sysfpg>
	SYSCAL CORBLK,[CIMM %CBRED+%CBNDR	;get read access, fail if can't
		CIMM %JSELF	;for us
		B		;starting at sysfpg, for n pages
		CIMM %JSABS	;from system
		A]		;at abs location in a.
	 JSR AUTPSY		;fooey?!? must be bad args.

	POPAE P,[D,C,B,A]	;whew, all done!
	RET


; XSRFND - given xuname in a, skips if hactrn exists with that xuname,
;	and returns actual uname in a.

XSRFND:	PUSHAE P,[B,C,D]
	MOVE D,XUNAME		;addr to indirect thru for xuname user var.
	JRST USRFN1		;jump into finder

; USRFND - given uname in a, skips if <uname> hactrn exists.

USRFND:	PUSHAE P,[B,C,D]
	MOVE D,UNAME		;addr to indirect thru for uname user var.
USRFN1:	MOVE B,[SIXBIT /HACTRN/] ;jname to look for
	MOVE C,@USRHI		;get current first free user-var index
USRFN2:	SUB C,LUBLK
	JUMPLE C,USRFN7		; 0 is sys job... jump if no more jobs to check
	CAME A,@D		;check uname
	 JRST USRFN2		;nope
	CAME B,@JNAME		;right uname, does he have hactrn?
	 JRST USRFN2		;nope
	MOVE A,@UNAME		;ah, won! get real uname in case xuname was checked.
	AOS -3(P)		;skipping return
USRFN7:	POPAE P,[D,C,B]
	RET



	;macro to generate table of its symbols so evaler can munch and
	;syms can then be accessed by name or indirected thru to sys core.
	;first wd of table is aobjn to rest, each sym has 1 wd for squoze repres.
	;and second labelled as that sym.
DEFINE SYMS LIST
%%S==. ? 0
IRPS SYM,,[LIST]
SQUOZE 0,/SYM/
SYM: 0
TERMIN
%%N==<.-%%S-1>/2 ; # syms
TMPLOC %%S,{ -%%N,,%%S+1 }
TERMIN

; EVALER - routine to .eval a table of its symbols
;	A has first entry of table generated by syms macro, which is aobjn ptr.

EVALER:	PUSHAE P,[A,B]
EVALR1:	MOVE B,(A)		;get squoze for symbol
	.EVAL B,		;find value
	 JSR AUTPSY		;any failures are assumed fatal.
	MOVEM B,1(A)		;store
	ADDI A,1		;increment since each sym takes 2 wds
	AOBJN A,EVALR1
	POPAE P,[B,A]
	RET

	; ITS symbols necessary for access to user variables
BVAR
ITSYMC:	SYMS [MAXJ LUBLK USRSTG] ; Constants.  MAXJ is max # jobs,
				; LUBLK length of a user var block,
				; USRSTG address of start of user var blocks.
ITSYML:	SYMS [USRHI]	; Locations.
			; USRHI is first non-existent user var block index.
ITSYMX:	SYMS [UNAME XUNAME JNAME USYSNM SUPPRO] ; User var locs, indexed into.
EVAR

; SUNAME - Come here to try to set UNAME, JNAME, and SNAME.
;	If this job is top-level, try to set UNAME to UNMPRF, and
;	JNAME to JNMPRF; SNAME is set to JNMPRF whether top-level or not.
;	Uses the fact that the user variables are mapped in to avoid doing
;	anything if the name is already OK.

SUNAME:	PUSHAE P,[A,B,C]
	MOVE C,MYUIND
	MOVE A,@USYSNM		; Get own SNAME
	CAME A,JNMPRF		; If not what we want, then
	 XCTILO [.SUSET [.SSNAME,,JNMPRF]]	; Set it.
	JFCL			; Should always win, but who knows.
	MOVE A,@UNAME		;get own UNAME
	MOVE B,@JNAME		;and own JNAME
	CAMN A,UNMPRF
	 CAME B,JNMPRF
	  SKIPL @SUPPRO
	   JRST SUNAM9		;Name OK or not top-level, exit
	.CLOSE DQCH,		;Gross kludge, make sure DQDEV has our UNAME
	.SUSET [.SXUNAME,,UNMPRF]
	.SUSET [.SHSNAME,,SATDIR]	;Make file author kludge happier
	XCTILO [.SUSET [.SUNAME,,UNMPRF]]
	 JRST SUNAM0		;Can't set UNAME
	XCTILO [.SUSET [.SJNAME,,JNMPRF]]
	 JRST SUNAM1		;Set UNAME OK but need different JNAME
SUNAM9:	POPAE P,[C,B,A]		;Set both OK, return
	RET

SUNAM0:	XCTILO [.SUSET [.SJNAME,,JNMPRF]]
	 JRST SUNAM9		;Can't set anything, try again later
	XCTILO [.SUSET [.SUNAME,,UNMPRF]]
	 JRST SUNAM1		;Try a different JNAME
	JRST SUNAM9		;winning now

SUNAM1: MOVE A,JNMPRF
	MOVSI B,770000          ;B mask for appended zero.
SUNAM3: TDNN A,B
         JRST SUNAM4
        LSH B,-6
        JUMPN B,SUNAM3
        MOVEI B,77              ;6 character name, clobber right-most
SUNAM4: ANDCM A,B               ;flush any existing digit in that place
        AND B,[010101010101]    ;multiplier for that place
        PUSH P,B
        IMULI B,'0
        ADD A,B                 ;put in a zero
        POP P,B
	MOVEI C,10.
SUNAM5:	XCTILO [.SUSET [.SJNAME,,A]]	;Try this JNAME
	 JRST [	ADD A,B ? SOJG C,SUNAM5 ? JRST SUNAM9 ] ;and successive ones
	XCTILO [.SUSET [.SUNAME,,UNMPRF]] ;Make sure UNAME is right
	 JRST @.-2		;lose, try another JNAME
	JRST SUNAM9		;win
SUBTTL INIT - Locks (establish uniqueness)

; MLINIT - initializes global-impure map for lock switches.
;  Has special function of checking the switches and dying instantly 
;  if it determines that it is a duplicate.

LVAR HAVLKS:	0	;set when page with locks is initialized and accessible
LVAR UNIQUE:	0	;set when have locked switch 1 = job is unique.
			;(others will die instantly)

MLINIT:	PUSHAE P,[A,B,C]
	SETZM UNIQUE		; Clear flag indicating whether unique
	SETZM HAVLKS		; Ditto for flag saying whether have page and locks
MLINI1:	.CALL LKFOPN		; Try to get dsk chan to lock-sw file
	 CAIA			;  If failed, skip and try to create it.
	  JRST MLINI3		; Have chan to lock-sw file... gobble locks!
	CAIE A,%ENSFL
	 JSR DEATH		; Error other than non-existent file
	.CALL LKFMAK		;  If failed, try to create
	 JSR DEATH		;   If even that fails, die instantly.
	MOVE A,[-2,,[0 ? 0]]	; Output 2 zero wds to clear LSWREQ, LSWDON.
	.IOT DKOC,A
	.CLOSE DKOC,		; Close, creating file
	.CALL LKFOPN		; Now try again to get channel.
	 JSR DEATH		; If failed after writing file....????

MLINI3:	SYSCAL CORBLK,[CIMM %CBNDW+%CBPUB ; Make public with write access.
			CIMM %JSELF	; put into self
			CIMM LCKPAG	; at page reserved for it
			CIMM DKIC]	; map from dsk file.
	 JSR DEATH			;  Ugh?????
	CALL LKINIT		; Initialize switches
	SETOM HAVLKS		; Have the page and locks!
	MOVEI A,LOCK1		; Concentrate on switch 1
	CALL LKGRAB		; Try once to gobble it
	 JRST [	SKIPE MAKING	;   Are we in the MAKMST routine?
		 JRST MAKMNU	;    Yes, if not unique yell at hacker!
		JSR DEATH]	;   Normal failure.  Die.
	SETOM UNIQUE		; Ah, we have locks and are unique, set flag.
	POPAE P,[C,B,A]		; We've gobbled it and won, stay alive and continue.
	RET

LKFOPN:	SETZ ? SIXBIT /OPEN/ ? [.BII,,DKIC] ? ['DSK,,0] ? %CLERR,,A
	LCKFN1 ? LCKFN2 ? SETZ SATDIR
LKFMAK:	SETZ ? SIXBIT /OPEN/ ? [.BIO,,DKOC] ? ['DSK,,0]
	LCKFN1 ? LCKFN2 ? SETZ SATDIR	


TMPLOC 43,{ 0 }			;pointer to locked switch list for lock hacking
TMPLOC 44,{ -LCCBLK,,CCBLK }	;aobjn ptr to critical code table for lock hacking

	; Critical code table pointed to by word 44 !

CCBLK:	LKINI2,,LKINI3		;for crashing in LKINIT
	MOVEM A,LSWREQ
	LKGRB1,,LKGRB2		;for crashing in LKGRAB
	SETOM @A
	LKFRE1,,LKFRE1+1	;for crashing in LKFREE
	SETOM @A
LCCBLK==.-CCBLK


; LKINIT - Initialize Lock Switches  (as per LOCKS documentation)

LKINIT:	PUSHAE P,[A,B]
	SYSCAL RQDATE,[CRET A ? CRET A]
	 JSR SYSLOS
	CAMN A,[-1]
	 JSR AUTPSY		; Foo? System doesn't know time... 
	MOVE B,A		; Save date/time sys started
LKINI1:	EXCH A,LSWREQ		;claim right of initializing (nop if already claimed)
LKINI2:	CAMN A,LSWREQ		;did we get it?
	JRST LKINI5		;no, check 2nd word to see if other guy fulfilled duty

	; Got access, we must initialize! LKINI2 to LKINI3-1 is critical code

	SETOM LOCK1		; Clear the lock(s)
	SETZM LOCK2		; Not really a lock... count of dead comsats.
LKINI3:	MOVEM B,LSWDON		; Indicate init done
LKINI9:	POPAE P,[B,A]
	RET

LKINI5:	CAMN B,LSWDON		;didn't get init rights, see if other finished it.
	 JRST LKINI9		;yes, nothing left to do.
	MOVEI A,30.		;no, he's still at it...hang around, he might die.
	.SLEEP A,
	MOVE A,B
	JRST LKINI1		;try to claim again.

; LKGRAB - takes A as addr of switch to swipe at; skips if
;	successfully grabbed switch for very own, doesn't skip if
;	it was locked.  tries only once!!!

LKGRAB:	AOSE (A)		;try to get it
	 RET		;lost
LKGRB1:	PUSH P,B		;got it! now put it on
	MOVE B,43		;locked switch list
	HRLI B,(SETOM)
	MOVEM B,1(A)
	MOVEM A,43
LKGRB2:	POP P,B			; LKGRB1 to LKGRB2-1 is critical code
	AOS (P)			;skip, we got it
	RET

; LKFREE - takes A as addr of switch to free.
;	(assumes that same switch is first item on locked switch list)

LKFREE:	PUSH P,B
	HRRZ B,1(A)
	MOVEM B,43	;remove from lsw list
LKFRE1:	SETOM (A)	;and unlock (LKFRE1 is critical instr.)
	POP P,B
	RET


SUBTTL EQV - File description (NAMES >)
IFN 0,[
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;	Format of ASCII EQV file is as follows:
;;;	(see actual file for more general details...)

	A recipient spec or RSPEC in standard form is:
		(<rtyp> <rnam>  X  <attr>  <attr>  ... <attr> )
		If <rspec> is an atom, it is read as (<rspec>), which
		(see below) turns into (NAME <rspec>).
Where:
 <rtyp> - Recipient type, such as NAME, FILE, RUN, BUG.
		If <rtyp> is not a recognized type, it is interpreted
		as being the <rnam>, of type NAME.
 <rnam> - Recipient string, such as KLH, INFO-ITS, [SYS;TS FOO].
 <attr> - Attributes, each of which is parsed as:
		(ATTR AVAL)
		 Where	ATTR - Attribute name.
			AVAL - Attribute value.
		If A is an atom rather than a list, it is read as
		(ATTR).  I.e. it exists but has no value.
  X  -   Also an Attribute, but interpreted crockishly.  If
		X is a list, it is a normal Attribute;
		Otherwise if an atom, 
		1) If "@" is the first character, interpretation is (SITE X)
		2) If a number or site name, interpretation is as above.
		3) Else as for atomic <attr>.

		Equivalence entries are in the format
		(<name> <attr> ... <attr> (EQV-LIST <rspec> ... <rspec>))

 <name> - A recipient name for which the following information applies.
		Only recipients of type NAME will be matched up in
		the EQV file.
 <attr> - Attribute, exactly as described above, which applies for this name.
 <rspec> - Recipient spec to which the message should be sent, in lieu of
		the <name> itself.  To actually send to <name> there should
		either be no (EQV-LIST ... ), or <name> should be one of
		the <rspec>s within it.


THE FOLLOWING IS OUT OF DATE, AND NOT AT ALL CORRECT!!!!
		Current Internal Format:

	Each equivalence is parsed into a LLN and all the LLNs strung
together into the main list, with initial LP in $LLLST(L).  Each LLN
(of attrib A$ELST) points to a list, the first LN of which must have
attrib A$ENAM and which is followed by normal recipient-attribute LN's.
However, within these following LN's can occur those of type A$RCP, which
are LLN's pointing to a recipient list of their own.  These A$RCP's
represent the EQV-LIST for the given A$ENAM.
	Later, there will be no main list nor A$ELST LLN's.  Rather,
a sorted table will exist in the HDR area, each word representing one
equivalence and containing <LP to A$ENAM LN>,,<LP to "following LN's">.
]
SUBTTL EQVCHK - Equivalence file loading

LVAR EQVTIM:	0	;has creation time of last ascii file for which compilation attempted.

;Note that this can close all areas except SCHDAR
EQVCHK:	PUSHAE P,[A,B]
	MOVEI A,EQAFN1		; See if a new ascii file was written.
	CALL OPNMFI		; Try to open it.
	 JRST [	STAT (,("No ascii EQV file exists??"))
		JSR AUTPSY]
	SYSCAL RFDATE,[CIMM DKIC ? CRET A]	; Get creation time.
	 JSR SYSLOS
	CAMN A,EQVTIM		; Compare with time of last file gobbled
	 JRST EQVCH9		; OK, still same
	EXCH A,EQVTIM		; Not same!!! Store new, get old time.
	JUMPN A,EQVCH3		; Jump if cause is new ASCII file.
	;; Initialization, see if really need to re-compile.
	;; Get creation time for existing bin file and compare.
	.IOPUSH DKIC,		; Save chan.
	MOVEI A,EQVFN1
	CALL OPNMFI		; Open eqv bin file
	 JRST [	.IOPOP DKIC,	; If no EQV file, definitely must recompile!
		JRST EQVCH3]
	SYSCAL RFDATE,[CIMM DKIC ? CRET A]	; Get creation time.
	 JSR SYSLOS
	.IOPOP DKIC,
	CAML A,EQVTIM		; Bin created before ascii file?
	 JRST EQVCH9		; Nope, created after, win!  Needn't compile.
	;; New ASCII file has appeared, must try to compile it.
EQVCH3:	CALL EQVGOB		; Attempt compile
	 CAIA			;   Compilation lost?
	  JRST EQVCH7		; Won...
	STAT (,("Note: LOST on compilation of ascii EQV file, trying backup binary..."))
	CALL EQVBIN		; Try to get bin.
	 CAIA			; Binary lost too???
	  JRST [STAT (,("Note: Backup won."))
		JRST EQVCH7]
	STAT (,("Note: Backup failed... dying."))
	JSR AUTPSY		; Refuse to run without beloved EQV file!
EQVCH7:	STAT (,("Note: EQV file compiled."))
EQVCH9:	POPAE P,[B,A]
	.CLOSE DKIC,
	RET

;;; EQVBIN/EQVGET - Slurp binary up into core.

EQVBIN:	PUSH P,A
	MOVE A,[EQVAR,,EQVFN1]
	CALL DFGET		; Get compiled EQV data file.
	 JRST POPAJ		; Not there?
	JRST POPAJ1

EQVGET:	PUSH P,A		; Same but no erret provision
	MOVE A,[EQVAR,,EQVFN1]
	CALL DFGET
	 JSR AUTPSY
	POP P,A
	RET


SUBTTL EQVGOB - Compilation of NAMES > ascii equivalence file

	; Compile ASCII EQV file, produce "binary" LSE file.
	; Note that this can close all areas except SCHDAR
EQVGOB:	PUSHAE P,[A,B,C,D,E,L]
	CALL MAPIN		; Ensure latest HOSTS3 and LSR1 databases.
	STAT (,("Note: Compiling EQV file "))
	MOVSI C,-NAREAS		; Flush any and all areas in core.
EQVGO1:	MOVE B,ARPTBL(C)	; Get ARPT to each area in turn.
	CAIE B,SCHDAR		; Sparing only the Schedule list!
	 UARCLS (B)		;  Closeum!
	AOBJN C,EQVGO1		; Loop for all areas to flush when coring down.
	MOVE A,[IMSAR,,EQAFN1]
	CALL OPNMFI
	 JRST [	CSTAT (,("!! Couldn't open !!"))
		JRST EQVG99]	; Fail.
	SYSCAL RFNAME,[CIMM DKIC ? CRET JUNK ? CRET EQAFR1 ? CRET EQAFR2]
	 JSR SYSLOS
	SYSCAL FILLEN,[CIMM DKIC ? CRET B]
	 JSR AUTPSY
	CSTAT (,6F(EQAFR1),(" "),6F(EQAFR2),(" "),LPAR,N10(B),(" wds"),RPAR,("..."))
	CALL TXFIN		; Get ascii file

	; Do heuristics on file length to set EQV LSE initial size.
	; This minimizes thrashing caused by infinite expansions.
	MOVE C,B
	LSH C,1		; Double file length
	IDIVI C,3	; Div by 3, and
	MOVE D,C	; Use (2*len)/3 as length of SA
	LSH C,1		; and remaining 2/3 as length of LA.
	MOVEI B,[0 ? C ? D]	; Furnish these values to LSE open routine.
	MOVEI A,EQVAR
	CALL LSEOPX		; Open a fresh EQV LSE with given sizes.

	MOVE L,$ARLOC+EQVAR	; Set current LSE to EQV.
	MOVEI A,IMSAR		; Indicate input area.
	CALL RCPGOB		; Gobble them up!

	; Done compiling, now write out report file
	MOVEM A,$LLLST(L)	; store returned LP to list.
	CAIN B,0
	 UAROPN [%ARTCH+%ARTZM,,ETXTAR ? [40]]	; Open very small area for error text collection.

	OUT(TMPC,OPEN(UC$UAR,ETXTAR))
	JUMPE B,EQVG95		; Jump if no errors.
	FWRITE TMPC,[N9,B,[ Errors detected, file ],6F,EQAFR1,[ ],6F,EQAFR2,[ Rejected.
]]
	CSTAT (,N9(B),(" Errors, rejected. "))
	CALL EQRPUT
	JRST EQVG99

	; No errors.
EQVG95:	FWRITE TMPC,[[No errors detected for file ],6F,EQAFR1,[ ],6F,EQAFR2,[
]]
	CSTAT (,(" No errors. "))
	CALL EQRPUT
	MOVE A,[EQVAR,,EQVFN1]
	CALL DFPUT		;write out the LSE
	AOS -6(P)		;won, skip on return

EQVG99:	POPAE P,[L,E,D,C,B,A]	;return
	UARCLS ETXTAR		; Flush report-text area.
	UARCLS IMSAR		; Flush input-text area.
	RET

	; Write out report file.  Main hair is producing elegant FN2.
EQRPUT:	PUSH P,A
	SKIPA A,EQAFR2		; Get true FN2 of input
EQRPT2:	 LSH A,-6
	TRNN A,77		; Anything in last char position?
	 TLNN A,-1		; Or is LH gone?
	  CAIA			; Aha, ready to smash.
	   JRST EQRPT2		; Nope, go shift it.
	HRLI A,'ERR		; Make ERR the first 3 chars.
	MOVEM A,EQRFN2
	CSTAT (,("Writing report file "),6F(EQRFN1),(" "),6F(EQRFN2))
;	MOVE A,[ETXTAR,,EQRFN1]
	MOVE A,SATDEV		; Make sure we use the correct
	MOVEM A,EQRDEV		; device and directory.
	MOVE A,SATDIR
	MOVEM A,EQRDIR
	MOVEI A,EQRFIL
	MOVEI B,ETXTAR
	CALL ARDSKO
	 JSR AUTPSY
	 JSR AUTPSY
	POP P,A
	RET

SUBTTL *------------------- Mainline -----------------------*
SUBTTL Scheduler main loop

	; Come here to execute a request, so that control drops
	; back through to MAIN when done.  A has LP to entry, B has time requested.
MAINX:	MOVE C,A		; Save LP for later deletion.
	MOVE A,LISTAR(A)+1	; Get LP to list for entry.
	MOVE A,LISTAR(A)	; Skip A$STIM LN.
	MOVE B,LISTAR(A)+1	; Get A$SRTN value (addr of rtn to XCT)
	MOVE A,LISTAR(A)	; Get LP to A$SARG, and
	MOVE A,LISTAR(A)+1	; Finally have it in A!
	LNDEL C,$LLLST(L)	; Now have all info, flush request from schedule list!
	CALL (B)		; Execute request!
	TLNE F,%SCOPN		; At end of each request,
	 OUTCAL(SC,FRC)		; make sure that buffered stats are forced out.
				; Fall through when done.

MAIN:	;; First make sure the locks haven't been mysteriously unlocked
	;; so we don't get two Comsats running at the same time
	SKIPGE LOCK1
	 JRST [	STAT (,("*** LOCK1 not locked! ***"))
		JSR AUTPSY ]

	;; Next, truncate the STATS file so that Penny and Alan can
	;; get some sleep.
	TLNE F,%SCOPN		; Is it open?
	 CALL [ SYSCAL FILLEN,[CIMM SC ? CRET A]
		 JSR SYSLOS	; Yeah, get its length
		CAMG A,SCMAXL	; Current file too long?
		 RET		; It's ok, out of here
		JRST SCROPN ]	; Too long, handle it and return from there.

	;; Now see if anything immediately urgent to do.
	CALL SCHGT		; Get first request.
	JUMPL B,MAINX		; Requests with negative time are urgent!

	;; Nothing urgent to do, see if need to GC or update NAMES, etc.
	;; These things are sometimes done now to defeat infinite input
	;; requests that otherwise would never let COMSAT reach idle state.

	SOSGE EQVQCT
	 JRST [	CALL EQVCHK	; Ensure currency of NAMES equivalences.
		MOVE U1,EQVQLM	; Checked for NAMES, reset count.
		MOVEM U1,EQVQCT
		PUSH P,A
		CALL MAPIN
		POP P,A
		JRST .+1 ]

	SOSGE GCRQCT		; Bump count, and
	 JRST MAIN42		; Go directly to GC-test if must.

	;; See if there are any left-over MAILIN files around.
	;; Process these before new input requests to preserve FIFO ordering.
	CALL IRQGOT		; Try getting one, process if found.
	 CAIA			; Nope, nothing.  Just continue.
	  JRST MAIN		; Whoops, hacked one!  Return to sched loop!

	;; Are there any input requests (MAIL files)?
	CALL IRQGET		; Gobble and process if one exists.
	 CAIA			; No request files, continue.
	  JRST MAIN		; Else when done, back to schedule loop!

	;; Nothing urgent and no input requests.  Is first
	;; schedule request semi-immediate?  (Criteria is whether
	;; time specified is present or past)
	.RDTIME C,		; Get current system time
	CAMG B,C		; How does request time compare?
	 JRST MAINX		; Less than or eq to!  Must do!

	;; Nothing that scheduler has to do NOW.
	;; This is a good place to do nebbish little things,
	;; like writing cruft to the STATS file...
	SKIPG SCLCNT		; Have we written enough lines yet?
	 JRST [	MOVEI A,STTLIM			; Yep, reset the counter,
		MOVEM A,SCLCNT
		STAT (,("Date is now: "),TIM(MDYT)) ;  and put out the date.
		JRST .+1]

	;; Here, there's really nothing to do but go to sleep.
	;; B, having time of first schedule request, defines time
	;; of next necessary action.  See how far off it is...
	.RDTIME C,		; Get system time again
	MOVE A,B
	SUB A,C			; Find 30'ths til next necessary action.
	CAMG A,NAPLIM		; Sooner than limit for napping?
	 JRST MAIN60		; Yes, go directly to take short nap.

	;; Next request too far off to take nap.  So, strip down stuff,
	;; free core, close channels, GC and whatnot to prepare for
	;; long hibernation.

	;; Check to see if MSGS ought to be GC'd.
MAIN42:	PUSH P,L
	SKIPN $AROPN+MASTER	; Make sure in core
	 CALL MSTGET
	MOVE L,$ARLOC+MASTER
	MOVE A,$LHMFG(L)	; Get # wds garbage
	MOVE B,$LHMFL(L)	; And length of MSGS thus far.
	CAIG B,2000		; If garbage is 1K or less,
	 JRST MAIN46		; Don't bother.
	CAML A,B		; If garbage is .GE. length,
	 JRST MAIN45		; Then certainly GC!
	IDIV B,A		; Well, see what Length/Garbage ratio is.
	CAIGE B,4		; If smaller than 4:1, then
MAIN45:	 CALL MSGSGC		; GC the MSGS file!
MAIN46:	MOVE A,GCRQLM		; GC check done. Reset count, so that
	MOVEM A,GCRQCT		; another check guaranteed to happen somtime.
	AOSN GCQFLG		; Garbage collect queue?
	 CALL GCQ0
	POP P,L			; SCHDAR

	;; Flush all areas in core.
	MOVSI C,-NAREAS
MAIN51:	MOVE B,ARPTBL(C)	; Get ARPT to each area in turn.
	CAIE B,SCHDAR		; Sparing only the Schedule list!
	 UARCLS (B)		; Closeum!
	AOBJN C,MAIN51

	;; Cleanup done, now should check schedlist again
	;; in case something happened.
	CALL SCHGT		; Get first on list.
	.RDTIME C,
	CAMG B,C		; Compare request time with current,
	 JRST MAINX		; and jump if necessary, else fall through...

	;; Final checks before sleeping.  B has time to wake up.
MAIN60:	MOVE C,@NQMFWR		; Get current NQMFWR var.
	CALL IRQCHK
	 CAIA			; Ah, OK to sleep!
	  JRST MAIN		; Uh-oh.  Must go process input request.
	.RDTIME A,		; OK to .HANG - Now get current time,
	SUB B,A			; find # of 30'ths to sleep, and drop thru
	JUMPLE B,MAIN		; to sleep, unless must execute NOW.

	;; OK, go to sleep.  
	;; No. of 30'ths to sleep is in B, NQMFWR to test in C.
MAIN70:	TLZE F,%SCOPN		; Now close stats channel....
	 JRST [	OUT(SC,CLS)	;  If twas open
		AOJA C,.+1]	;   must AOS C since closing bumps NQMFWR.
	SETZM LSRTNS"LSRADR	; Close down LSR1, which doesn't affect NQMFWR.
	.CLOSE LSR1C,
	CAMLE B,SLPLIM		; Don't sleep longer than maximum allowed.
	 MOVE B,SLPLIM
	LSH B,1			; Turn 30'ths into 60'ths for .REALT
	.SUSET [.SAMASK,,[%PIRLT]]	; Turn off .REALT enable
	.SUSET [.SAPIRQC,,[%PIRLT]]	; And any pending ints.
	MOVE A,[600000,,B]
	.REALT A,			; Start real-time clock interrupt
	MOVEI A,WAKEUP			; Set dispatch vector for
	MOVEM A,RLTVEC			; waking up...
	.SUSET [.SIMASK,,[%PIRLT]]	; and now enable it.
MAIN80:	.SUSET [.SADF1,,[%PIRLT]]	; Clear defer bit.
	.SUSET [.SPICLR,,[-1]]		; I think something zeros this
	CALL SUNAME			; Try to keep name up to date
	CAMN C,@NQMFWR			; Until clock or file-write wakes us,
	 .HANG				; hang!

	;; If here, something was written to .MAIL. - check to see if
	;; it was a request, and if not go back to sleep.
	;; Important that this be doable without opening stats file, since
	;; otherwise parallel comsats can activate each other forever!
	.SUSET [.SIDF1,,[%PIRLT]]	; Defer alarm clock...
	MOVE C,@NQMFWR			; Get new NQMFWR value.
	CALL IRQCHK			; Anything there?
	 JRST MAIN80			; Nope, resume our peaceful snooze.

WAKEUP:	MOVE L,$ARLOC+SCHDAR		; SCHDAR is always MAIN's current LSE.
	.SUSET [.SAMASK,,[%PIRLT]]	; Stop realt's, and for PEEK's sake
	.SUSET [.SAPIRQC,,[%PIRLT]]	; also remove any pending one.
	.SUSET [.SADF1,,[%PIRLT]]	; and clear defer bit in case was set.
	MOVSI A,400000
	.REALT A,			; Clear and release the REALT clock.
	SETZM RLTVEC			; Clear RLT dispatch vector.
	SETZM SDFCNT			; Clear times-called-SDFULL.
	TLNN F,%SCOPN
	 CALL SCOPN			; Ensure sure STATS channel is open,
	CALL MAPIN		   	; Ensure latest LSR1 and HOSTS3.
	CALL EQVCHK			; Get latest NAMES too.
	MOVE U1,EQVQLM			; Checked for NAMES, reset count.
	MOVEM U1,EQVQCT
	JRST MAIN			; and go back to beg of scheduler loop.


NAPLIM:	30.*60.		; Max # of 30'ths allowed to "nap", without cleanup
SLPLIM:	30.*60.*20.	; Max # of 30'ths scheduler allowed to sleep
GCRQLM:	50.		; Max # of sched rqsts allowed between forced GC checks
LVAR	GCRQCT:	0	; Countdown for above, reset with each GC check
EQVQLM:	7.		; <ax # of sched rqsts before checking for new NAMES.
LVAR	EQVQCT:	0
SUBTTL Scheduler routines

IFN 0,[
		Scheduler description

	Entries on the Schedule List are sublists of 3 items:
A$STIM, A$SRTN, and A$SARG.  The first specifies time at which
the request is scheduled, the second specifies routine to
execute at that time, and the third provides an argument for
the routine.  Time is specified in 30'ths of a second since
system startup (what .RDTIME returns), and the routine
is specified by address (called via PUSHJ).  The argument can
be anything that fits into a word.
	Notes:
1) The items must occur on the sublist in the exact
	order specified, eg. A$STIM is always first.  This
	allows better efficiency.
2) The list is always assumed non-empty.  To achieve
	this, initialization inserts a "sink" request that
	claims to desire execution 36 years from now.
];IFN 0

; SCHINI - Initializes Schedule List.  Should be called before
;	any other schedule operations are done.

SCHINI:	PUSHAE P,[A,L]
	MOVEI A,SCHDAR
	CALL LSEOPN		; Open up a fresh LSE.
	MOVE L,$ARLOC+SCHDAR	; Make it current.
	MAKELN A,[A$SARG,,0 ? 0]
	MAKELN A,[A$SRTN,,[A]
		%LTVAL,,[[SCHBUG]]]	; In unlikely event this is ever XCT'd!
	MAKELN A,[A$STIM,,[A]		; Set requested time to max. pos #.
		%LTVAL,,[[SETZ-1]]]
	MAKELN A,[0 ? %LTLST,,[A]]	; Now put all under LLN
	MOVEM A,$LLLST(L)		; And make it first thing on schedule list.
	POPAE P,[L,A]
	RET
SCHBUG:	JSR AUTPSY		; Comes here if initial request ever XCT'd!


; SCHDIT - Takes in A, B, and C the A$STIM, A$SRTN, AND A$SARG of
;	an item to be put on the schedule list.  All 3 are taken
;	to be values.
; SCHDI - Similar but uses time c(A) from now, rather than AT c(A).
;	Schedule time units are 30'ths of sec, and 0 is time system
;	started, just as returned by .RDTIME.

SCHDIT:	PUSH P,A
	JRST SCHDI2
SCHDI:	PUSH P,A
	.RDTIME A,		; Get system time in 30'ths
	ADD A,(P)		; Get time to schedule at.
SCHDI2:	PUSHAE P,[B,C,L]
	MOVE L,$ARLOC+SCHDAR	; Set up current LSE to right one.
	MAKELN C,[A$SARG,,0	; Gobble routine argument to start list.
		%LTVAL,,[C]]
	MAKELN C,[A$SRTN,,[C]	; Push routine onto list
		%LTVAL,,[B]]
	MAKELN C,[A$STIM,,[C]	; and conclude with A$STIM as FIRST thing on list.
		%LTVAL,,[A]]
	MAKELN A,[A$S,,0 ? %LTLST,,[C]] ; And put all under a LLN.
	CALL SRTINS		; And stick entry into sorted schedule list.
	POPAE P,[L,C,B,A]
	RET


; SCHGET - Get highest priority schedule entry.  Simply gets first
;	thing on list, since it's sorted.  Returns in A the LP to entry's
;	LLN, and its A$STIM value in B.
; SCHGT - Similar but assumes current LSE is already SCHDAR.

SCHGET:	PUSH P,L
	MOVE L,$ARLOC+SCHDAR	; Make schedule list current LSE.
	CALL SCHGT
	POP P,L
	RET

SCHGT:	MOVE A,$LLLST(L)	; Get LP to first LLN.
	MOVE B,LISTAR(A)+1	; Get LP to list for entry.
	MOVE B,LISTAR(B)+1	; Get A$STIM (val of first thing on list).
	RET



; SRTINS - Insert entry on sorted list.  Auxiliary for SCHDIT, REMIND.
;	Given an LLP in A and current LSE, assumes that the LSE's list is
;	composed of entries of the format:
;		--> entry LLN --> entry LLN --> ...
;			\		\		
;			Value ->...	Value ->...
;	The LLN pointed to by A is a similar "entry".  The list must
;	be sorted by the value of the first element on the entry sublists;
;	lowest and oldest first.  This is accomplished by inserting
;	the new item immediately in front of the first greater value -
;	those of equal value are skipped over.

SRTINS:	PUSHAE P,[B,C,D]
	SKIPN B,$LLLST(L)	; Get LP to sorted list.  
	 JRST SRTIN7		; If nothing there, do special case.

	PUSH P,A
	MOVE A,LISTAR(A)+1	; Get LP to LN holding value to sort on
	MOVE A,LISTAR(A)+1	; and get value.
	SETZ C,			; Initialize "LP to LLN to insert after"
SRTIN3:	MOVE D,LISTAR(B)+1	; Loop - Get LP to VLN for this entry on list.
	CAMGE A,LISTAR(D)+1	; Compare values.
	 JRST SRTIN5		; Aha, this entry is greater - insert!
	MOVE C,B		; Not yet. Save LP to this LLN.
	HRRZ B,LISTAR(C)	; and get CDR to next.
	JUMPN B,SRTIN3		; Go process unless nothing there.

	; Found place to insert new schedule entry.  C has LP to previous LLN,
	; B has LP to current LLN, which new entry must precede.
SRTIN5:	POP P,A			; Restore LP to new entry.
	HRRM B,LISTAR(A)	; Make its CDR-> current entry.
	JUMPE C,SRTIN7		; If at beg of list, do special store.
	HRRM A,LISTAR(C)	; and make previous entry's CDR-> new entry.
	JRST SRTIN9		; Done!

SRTIN7:	HRRZM A,$LLLST(L)	; Come here if entry must be inserted as 1st item.
SRTIN9:	POPAE P,[D,C,B]
	RET


; SCHREM - Schedule Reminder.  Looks at first (earliest) reminder
;	on RMLAR and generates a schedule request to send that
;	reminder.  Does nothing if no reminders exist.

SCHREM:	PUSHAE P,[A,B,C,L]
	SKIPN $AROPN+RMLAR	; Ensure reminder list exists.
	 CALL RMLGET
	MOVE L,$ARLOC+RMLAR	; Make reminder list current.
	HRRZ A,$LLLST(L)	; Get LP to first entry.
	JUMPE A,SCHRM9		; If none, return.
	MOVE A,LISTAR(A)+1	; Get LP to entry's list.
	MOVE A,LISTAR(A)+1	; Get A$STIM for entry - internal time wd.
	CALL DATIME"UPTIME		; Convert to system time.
	MOVEI B,REMSND		; Execute REMSND rtn then.
	SETZ C,			; No argument at moment.
	CALL SCHDIT		; Schedule the request.
SCHRM9:	POPAE P,[L,C,B,A]
	RET

; SCHNTA - Schedule Network, All of.  Searches through QML list
;	and generates a schedule request for each site found.
;	Since only called on initialization, schedules to run immediately.

SCHNTA:	PUSHAE P,[A,B,L]
	SKIPN $AROPN+QMLAR	; Ensure QML there.
	 CALL QMLGET
	MOVE L,$ARLOC+QMLAR	; Make it current LSE.

	; Pluck each site # off QML and generate schreq for it.
	MOVE A,$LLLST(L)	; Get LP to first thing on QML list.
	MOVEI B,SCHNTX		; Execute this rtn for each site LLN...
	SETZ C,			; Arg for SCHNTX - schedule for NOW.
	CALL MAPC		; Munch list!
	 JSR AUTPSY		; Will never happen.
	POPAE P,[L,B,A]
	RET

; SCHNTX - Given LP in A to a QML LLN, generates schedule request for
;	connecting to that site.  B should contain time in 30'ths from now
;	to execute request.  Always skips.

SCHNTX:	PUSHAE P,[A,B,C]
	MOVE A,LISTAR(A)+1	; Get LP to site-# LN (first thing on list!)
	MOVE C,LISTAR(A)+1	; Get site # as A$SARG to sched.
	MOVE A,B		; Use given time for relative A$STIM
	MOVEI B,QUESND		; And specify standard un-queuer rtn.
	CALL SCHDI		; Schedule, using A as relative time-from-now.
	POPAE P,[C,B,A]
	AOS (P)
	RET

SUBTTL IRQCHK, IRQMRK, IRQGET - Process Input Requests.

LVAR	DIDONE:	0	;-1 iff we have processed first input request.

; IRQCHK - Checks for existence of a MAIL < input request.  If
;	one found, skips on return, otherwise no skip.  No
;	other function...

IRQCHK:	.CALL IRQOPN	; At moment, fail for any reason.
	 RET
	AOS (P)		; Win, skip return.
IRQNOP:	.CLOSE DKIC,	; Tidily flush channel.
	RET

IRQOPN:	SETZ ? SIXBIT /OPEN/ ? [.BII,,DKIC] ? SATDEV
		IRQFN1 ? IRQFN2 ? SETZ SATDIR

; IRQGET - Looks for a MAIL < input request, and processes it if one found.
; IRQGOT - Similar, but looks for an existing MAILIN > request instead.
;   Both skip unless no request existed.
;	The rename of MAIL < to MAILIN > is done for two
;	reasons: to reduce access conflicts and improve robustness.
;	Once renamed to MAILIN, it is COMSAT's property
;	and nobody can touch it;  this indicates pretty obviously
;	what it's working on at the moment.  The use of MAIL < processes
;	messages in reasonably chronological order, since they are
;	written as MAIL >.
;		However, > is used for MAILIN because
;	this helps ensure that if anything was wrong with previous MAILIN
;	(such as causing a crash), it will be processed only
;	after all existing MAIL requests are done.  Thus, a bug so horrible
;	that it doesn't even let COMSAT rename the file to BADREQ will
;	still be bypassed to some extent.

; IRQGET - Tries to gobble and process a request.
;	Tries hard to open input file, but returns if none found or
;	it gives up, without skipping.  If read in, will always
;	skip on return, regardless of processing results.
;	This is the routine that vectors off such requests.

BVAR
IRTLFG:	0		; Length of file if too long, else zero.
SPRQFG:	0		; -1 if hacking special request.
IROFIL:	BLOCK 4		; Filblk for original request filename.
EVAR

	IRQMIN==10.	;Minimum length of messages we handle (6).
IRQMLN:	IRQMIN*PG$SIZ	;This is about 50KB long. (30KB)

IF1,{
ORADIX==10
RADIX 10.
INFORM IRQMIN Input request size = ,\IRQMIN
RADIX ORADIX
};IF1

LVAR	IRQRST:	0	; -1 when this COMSAT reset for a big input request

IRQGET:	.CALL IRQOPN		; Open MAIL <
	 RET
	SYSCAL RFNAME,[CIMM DKIC ? CRET IROFIL ? CRET IROFIL+2
		CRET IROFIL+3 ? CRET IROFIL+1]
	 JRST IRQNOP
	SYSCAL RENMWO,[CIMM DKIC ? IRIFN1 ? IRIFN2]
	 JRST IRQNOP
	SETZM IRQRST		; Not looping on "too big" on a new request
	STAT (,("InReq: "),6F(IROFIL+3),(" "),RABR,(" "))
	PUSH P,A
	JRST IRQG05		; Go process the file.

IRQGOT:	PUSH P,A
	MOVEI A,IRIFN1		; Point to filblk to try opening.
	CALL OPNMFI		; Open, trying hard.
	 JRST POPAJ		; Couldn't open it?  Failure return.
	SETOM IRQRST		; Don't allow "too big" oldreq (don't loop)
	STAT (,("OldReq: "))	; Open!  Drop thru to do rest.

	; Common entry for IRQGET and IRQGOT; DKIC is open for
	; file to be processed.
IRQG05:	AOS -1(P)		; Now committed to skipping...
	PUSHAE P,[B,C,D,L]
	SETZM IRTLFG		; Clear flags
	SETZM SPRQFG
	MOVEI A,IRRFIL		; Point to "real" filename block.
	SYSCAL RFNAME,[CIMM DKIC	; Find true filename.
		CRET (A) ? CRET 2(A)
		CRET 3(A) ? CRET 1(A)]
	 JSR SYSLOS
	CSTAT (,6F(3(A)),("; "))		; Say which MAILIN it is.
	SYSCAL RFDATE,[CIMM DKIC ? CRET C]	; Get creation time of request
	 JSR SYSLOS
	SYSCAL FILLEN,[CIMM DKIC ? CRET B]
	 JSR SYSLOS		; Now check its length...
	SKIPG B			; Zero length file?
	 JRST [	STAT (,("Note: InReq file of zero length - ignoring."))
		JRST IRQG85 ]	; Just flush it quietly.
	MOVEI A,MSGAR		; OK, will need LSE for sure...
	CALL LSEOPN		; Open a fresh one as the current MSG-LSE.
	MOVE L,$ARLOC+MSGAR	; Make it current LSE.
	CAIGE B,PG$SIZ*2.	; Always attempt to process messages
	 JRST IRQG10		; which are around 10K characters long.
	MOVE U1,B		; Let's see if this sucker might fit.
	CALL EQVLEN		; EQV file may be read into core soon.
	ADD U1,U2		; Add it in it's approx len.
	CALL PSRP		; Is this frob going to fit?
	 CAIA
	  JRST IRQG10
	MOVEM B,IRTLFG		;  Nope, indicate too-long request.
	MOVE A,B		;  Get # blocks in A, # remaining wds in B.
	IDIVI A,PG$SIZ
	STAT (,("Note: Inreq file too big! Length="),N9(A),("+"),N9(B))
	MOVE B,IRTLFG
	CAMGE B,IRQMLN		; Is this a reasonable request?
	 JRST [	AOSN IRQRST	;  Yes, but if we already lost once
		 JRST IRQG06	;   don't keep trying forever.
		SETOM IRQRST
		JSR RESET ]	; Try REAL hard for this request!!
	;; Input request seems unreasonable...flush it.
IRQG06:	SETZM IRQRST		; Reset the reset flag.
	HRLZI A,IMSAR		; Specify area to read into.
	MOVEI B,PG$SIZ		; Just a page's worth.
	CALL TXFINL		; Read in the file, using limit in B.
	MOVEI A,IMSAR		; Indicate which area holds input.
	CALL SPCFND		; Parse request into current LSE!
	 NOP			;  Ignore any errors.
	MOVE A,[ASCNT [Request too large.]]
	JRST IRQG49 		; Now go send error receipt.


	;; Message will fit, so let's get it into the database.
IRQG10:	.RYEAR U1,
	TRZ C,DATIME"TM%DST	; Assume Standard Time in effect.
	TLNN U1,100000		; Now if Daylight Savings in effect,
	 TRO C,DATIME"TM%DST	;   set flag.
	MAKELN A,[A$TIM,,0 ? %LTVAL,,[C]]	
	MOVEM A,$LLLST(L)	; Time message file created is first LN!
	HRLZI A,IMSAR		; Specify area to read into.
	CALL TXFIN		; Read in the file, using limit in B.
	MOVEI A,IMSAR		; Indicate which area holds input.
	CALL SPCFND		; Parse request from there into current LSE!
	 JRST IRQG50		; Error in specs?  go handle
	FINDA C,[A$SNM,,[$LLLST(L)]]	; Should have found sender name.
	 CAIA
	  JRST IRQG25
	FINDA C,[A$CSN,,[$LLLST(L)]]	; Else had better be claimed-from.
	 JRST [	STAT (,("Note: No sender for request!"))
		JRST IRQG80 ]		;    Else cant report errors!
	MOVEI A,A$SNM			; Only claimed-from exists.
	DPB A,[$LAFLD,,LISTAR(C)]	; Turn it into A$SNM.

	; Now have msg-attrib list in MSG-LSE.  Check to ensure that
	; it's a message, rather than a special request.
IRQG25:	FINDA A,[A$PREQ,,[$LLLST(L)]]	; Does list have special-req attrib?
	 JRST IRQG30			; Nope, treat as normal message.
	SETOM SPRQFG		; Yes!  Set flag, drop through.

	; Input request is not a MAIL-style message, but rather a special
	; processing request!
	CALL SPRXCT		; Execute the request.
	 JRST [	JUMPN A,IRQG50		; non-skip signals error, save request.
		JRST IRQG80]		; Just rename if no err msg.
	JUMPE A,IRQG85		; Successful return, jump if no message.
	JRST IRQG35		; Message, go process it!
				; Guaranteed to have rcpt, txt, sender.

	; Not a Special Request; just plain vanilla mail.
IRQG30:	FINDA A,[A$RCP,,[$LLLST(L)]]	; Were any rcpts found?
	 JRST [	MOVE A,[ASCNT [At least one recipient must be specified!  None were seen.]]
		JRST IRQG49]
	FINDA A,[A$MTXT,,[$LLLST(L)]]	; Message text?
	 JRST [	MOVE A,[ASCNT [Message text must be specified! None was seen.]]
		JRST IRQG49]

	;; Normal message has been read into MSG-LSE.  Now assign it an ID...
IRQG35:	CALL IDSET		; Cons up an ID and stick onto list.
	;; Now all ready to mail or queue!
	FINDA B,[A$TLST,,[$LLLST(L)]]
	 CAIA			; Is it a reminder?
IFN 0,	  JRST [CALL REMIND	;  Yep! Go queue in RML.
IFN 0,		JRST IRQG85]	;  and flush disk request.
	  JRST [MOVE A,[ASCNT [Reminders do not work, please don't try them.]]
		JRST IRQG49]	; In fact they blow COMSAT away.
	;; Mail it!
	PUSH P,MF
	SETZ MF,		; Completely normal message.
	CALL MAIL		; This does any queueing & updating necessary
	 NOP
	SETOM DIDONE
	POP P,MF
	JRST IRQG85		; Through with request, flush from disk.


	;; Rename bad input file.
IRQG80:	STAT (,("Note: Renaming bad input file to "))
	SYSCAL RENAME,[SATDEV ? IRRFN1 ? IRRFN2 ? SATDIR
		IREFN1 ? IREFN2]
	 JRST [	CSTAT (,("<Couldn't???>"))
		JRST IRQG90]
	MOVEI A,IREFN1
	CALL OPNMFI
	 JRST [	CSTAT (,("<Can't re-open??>"))
		JRST IRQG90]
	SYSCAL RFNAME,[CIMM DKIC ? CRET A ? CRET A ? CRET B]
	 JSR SYSLOS
	CSTAT (,6F(A),(" "),6F(B))
	.CLOSE DKIC,
	JRST IRQG90

	;; Through with input request, delete it from dsk!
IRQG85:	MOVEI A,IRRFIL
	SYSCAL DELETE,[(A) ? 2(A) ? 3(A) ? 1(A) ? CERR B]
	 JRST [	CAIE B,4	; If lost, make sure it's because 
		 JSR AUTPSY	; file was already gone.
		JRST .+1]
	JRST IRQG90		; Return.

IRQG90:	UARCLS IMSAR
	POPAE P,[L,D,C,B,A]
	RET

	; Here when request error discovered.  Try to return msg to sender
	; if any exists, as well as to comsat wizard for inspection.
	; A contains SLP to error string.
	; Unresolved question: when to report BADREQ file name?  How?
	; Should file (esp too-large) be moved to sender's dir?

IRQG49:	MAKELN A,[0 ? %LTSTR,,[A]]	; Entry pt to use ASCNT err string.
IRQG50:	STAT (,("Note: Error during parse of input request."))
	MOVE C,L		; Save current LSE
	MOVE B,A		; and save err-msg SLP.
	MOVEI A,EMSGAR
	CALL LSEOPN		; Open error-message LSE
	MOVE L,$ARLOC+EMSGAR	; Make it current LSE.
	OUT(TMPC,OPEN(UC$SAO),("Error in input request file."),EOL)
	OUT(TMPC,SL(B,C))	; Note SLN is in previous LSE!
	OUT(TMPC,EOL,("Message not sent and not queued;"))
	SKIPN IRTLFG		; Include failed msg in receipt if possible.
	 JRST [	OUT(TMPC,("text of bad file follows:"),EOL,("--------"))
		OUT(TMPC,EOL,TA(IMSAR))
		JRST IRQG56 ]
	PUSHAE P,[D,E]		; Failed msg was too big.
	MOVE D,IRTLFG		; Get msg length in words.
	IMULI D,5		; Cvt to chars.
	IDIVI D,PG$SIZ		; Cvt approximate KB.
	OUT(TMPC,SP,D(D),(" Kbytes is far too large for me."),EOL,("Please use the FTP program to transfer huge files around the network."),EOL)
	POPAE P,[E,D]
IRQG56:	MAKELN B,[A$MTXT,,0 ? %LTSAO,,0] ; Hack up msg-txt LN in B
	EXCH L,C		; Restore previous L
	CALL SNMRCP		; Cons up a rcpt from sender-name, return in A.
	 JRST IRQG59		; Couldn't find sender?  sigh, just rename.
	EXCH L,C
	LNCOPY A,[C ? A]	; Copy it into this LSE.
	HRRM B,LISTAR(A)	; Make existing list the CDR of new LN.
	MOVEM A,$LLLST(L)	; Store ptr to consed-up list.
	STAT (,("	Input-request error, msg sent:"))
	PUSH P,MF
	MOVEI MF,M%CORG+M%EMSG+M%ERR	; Say comsat-originated, EMSGAR, CC.
	CALL MAIL			; Send it!
	 NOP
	POP P,MF
	MOVE L,C		; Restore original LSE.
IRQG59:	UARCLS EMSGAR
	JRST IRQG80		; Return, renaming file.

; IDSET - Obtains an ID string and puts it on list of current LSE.
;	$LLLST(L) will thus contain LP pointing at it.

IDSET:	PUSH P,A
	CALL IDGET
	HRL A,$LLLST(L)		; Get LP to current list
	HLRM A,LISTAR(A)	; Make it CDR of the A$ID LN
	HRRZM A,$LLLST(L)	; and make A$ID LN the start of list.
	POP P,A
	RET

; IDGET - Obtains an ID string for message, guaranteed to be unique
;	for that particular message.  Returns LP to the SLN in A.
;	Expects current message LSE in L.

IDGET:	PUSH P,B
	MOVEI A,IDFN1
	CALL OPNMFI		; Open highest ID file.
	 JSR AUTPSY
	SYSCAL RFNAME,[CIMM DKIC ? CRET IDRFIL ; Find real name of ID file.
		CRET IDRFIL+2 ? CRET IDRFIL+3 ? CRET IDRFIL+1]
	 JSR SYSLOS
	.CLOSE DKIC,
	MOVEI B,IDRFIL		; Rename file B (real) to file A ( > )
	SYSCAL RENAME,[(B) ? 2(B) ? 3(B) ? 1(B) ; Existing file
			(A) ? 1(A)		; To next higher file
			CERR A]		; With provision for non-existence.
	 JRST [	CAIN A,4		; Error, File not found?
		 JRST IDGET		; Yes, repeat process.
		JSR AUTPSY]		; Foo.
	MOVE A,IDRFIL+3			; Aha, rename won.  Get old real FN2.

	; Now have unique ID number for this system.  Add site name string
	; and a few other things to ensure net-wide uniqueness.

	OUT(TMPC,OPEN(UC$SAO))
	OUT(TMPC,LABR,6F(A),("."),TIM(YMD))
	SKIPE XVERS			; If not normal COMSAT, add more junk.
	 OUTCAL(TMPC,("*"),6F(JNMPRF),("*"))
	FINDA B,[A$NMH,,[$LLLST(L)]]	; If message is not from outside
	 JRST [	FINDA B,[A$SNM,,[$LLLST(L)]]	;  Even hairier ID string
		 CAIA				;  since we may output it.
		  OUTCAL(TMPC,("."),TLS(B))	;  ID includes sender name.
		JRST .+1 ]
	OUT(TMPC,C(100),TZ(OWNNAM),RABR)
	MAKELN A,[A$ID,,0 ? %LTSAO,,]
	CSTAT (,(" ID="),TLS(A),(" "))
	POP P,B
	RET	


LVAR	IDRFIL:	BLOCK 4

	CONSTANTS	; Someplace in the middle, to avoid midas gronkage.



SUBTTL *------------ Parsing -------------*
SUBTTL Parser for input request files (MAIL <'s)

; SPCFND - This is the top level parser for input request files.
;	It takes in A the ARPT of an area which contains the text of the
;	file in standard IRQ format, and it conses up the results
;	onto the front of the current LSE.
;		It skips if the parsing worked.  At the moment, no error
;	message is returned; any errors are sent to the STATS file.

BVAR
ATRLEN:	0	;holds length of attrib last found
MXATRL==24.	;max. attribute-name length in chars

ATRBUF:	BLOCK <MXATRL+4>/5	;holds attrib name last found

IPLNPT:	0	;holds ptr (temp) for IPLING
IPLNCH:	0	;holds count of chars left in ims area (also for IPLING)
IPLAST:	0	;holds ptr to start of last line parsed
IPLTCH:	0	;holds char count for ditto
EVAR
.SCALAR	 OFRMFG	; Crock to help track down obsolete FROM's.

SPCFND:	PUSHAE P,[B,C,D,E]
	CSTAT (,(" SPECS:"))
	MOVE E,A		; Set up index to input area
	SETZM OFRMFG		; Crock to help remove other crocks.
	SETZM CNFALL		; Zero crocks to decide which confirm option
	SETZM CNFERR		; (if any) to use.
	SETZM RCPNUM		; Initialize rcp vars
	SETZM RRCPS
	HRLZI A,440700		; Set up initial relative ptr into input area
	MOVEM A,IPLNPT		; Initialize for IPLING.
	HRR A,$ARLOC(E)		; Make abs.
	PTRDIF A,$ARWPT(E)	; Get total chars in IMS area
	MOVEM A,IPLNCH		; and initialize for IPLING.

SPCF10:	CALL IPATTR		; Get an attrib and its argument
	 JRST [	JUMPG A,SPCF60	;  If err message, handle error.
		JRST SPCF20]	;  Else simply EOF...

	; Found attrib;
	; A = # chars in text of argument,
	; B = index to attrib in table,
	; $ARRPT(E) = BP to start of argument text.
	CAIGE B,ATRTAS-ATRTAB	; Remove leading spaces for this item?
	 JRST SPCF15		; No, forget it.
	JUMPE A,SPCF15		; If no chars, all done.
SPCF14:	ILDB C,$ARRPT(E)	; Get character.
	CAIN C,40		; Is it a space?
	 SOJG A,SPCF14		; Yes, count it and loop.
	D7BPT $ARRPT(E)		; Either it isn't or we ran out.  Put it back.
SPCF15:	HRRZ B,ATRTAB(B)
	CALL (B)		; Do attribute's routine.
	 JRST SPCF60		; Ugh, error in attrib routine.
	JUMPE A,SPCF10		; If rtn returned nothing, that's ok.
	LNAPP [A ? $LLLST(L)]	; Add to main list with append,
	MOVEM A,$LLLST(L)	; because rtn may have returned a list!
	JRST SPCF10		; Attrib done, get another.

	; All done, do a few checks before returning.
SPCF20:	SETZ A,			; Stick in appropriate confirm option
	SKIPE CNFERR
	 MOVE A,[LITSTR [FAIL]]
	SKIPE CNFALL		; if any is indicated.
	 MOVE A,[LITSTR [ALL]]
	JUMPE A,SPCF21		; Win return.
	MAKELN A,[A$CNF,,[$LLLST(L)]
		%LTSTR,,A]
	MOVEM A,$LLLST(L)
SPCF21:	CALL SPCF66		;Figure out who it's from
	JRST SPCF80		;Take win return

SPCF60:	CAIN A,
	 MAKELN A,[0 ? %LTSTR,,[LITSTR [Random lossage.]]]
	STAT (,("Parsing aborted: "),C(42),TLS(A),C(42))	; Error in attrib processing.
	MOVE B,IPLAST
	ADD B,$ARLOC+IMSAR	; Get abs. ptr to bad line.
	OUT(TMPC,OPEN(UC$SAO),("Parsing error: "),SL(A),EOL)
	OUT(TMPC,("Line stopped at is:"),EOL,S(IPLTCH,B))
	MAKELN A,[0 ? %LTSAO,,]
	PUSH P,A
	CALL SPCF66		;Try to figure out who it's from, if possible
	POP P,A
	CAIA
SPCF80:	 AOS -4(P)
	POPAE P,[E,D,C,B]
	RET

SPCF66:	FINDA C,[A$NMH,,[$LLLST(L)]]	;Network mail?
	 RET
	CALL HPARSE		;Yes, try to parse header
	 JRST SPCF67		;Can't, pretend came from "NET-ORIGIN"
	FINDA C,[A$SNM,,[$LLLST(L)]]
	 JSR AUTPSY
	FINDA B,[A$SNH,,[$LLLST(L)]]
	 JSR AUTPSY
	CSTAT (,LBRC,("HDR-FROM:"),TLS(C),("@"),HST(LISTAR(B)+1),RBRC)
	RET

SPCF67:	MAKELN A,[A$SNM,,[$LLLST(L)]	;Cannot parse header, fake sender name.
		  %LTSTR,,[LITSTR [NET-ORIGIN]]]
	MOVEM A,$LLLST(L)
	RET

;  IPATID - Input attribute identification... uses ATRLEN and
;	ATRBUF, returns index in B to matching entry in attribute table.

.SCALAR COMPT1,COMPT2

IPATID:	PUSHAE P,[A,C,D]
	MOVEI B,NIATTR-1	;Get index to last entry
IPAID1:	HLRZ C,ATRTAB(B)	;Get addr--> cnt,, [ptr]
	HLRZ D,(C)		;Get char cnt of this entry
	CAMN D,ATRLEN		;If matches length of input attrib
	 JRST IPAID3		; found entry of same length!
IPAID2:	SOJG B,IPAID1		;Serial loop thru table
	STAT (,("UNKNOWN ATTR FOUND="),RABR,LBRC,TZ(ATRBUF),RBRC)
	JRST IPAID9

	;; Found same-length entry, now check text equality
IPAID3:	MOVE A,[440700,,ATRBUF]
	MOVEM A,COMPT1
	MOVE A,(C)		;Addr of text string pointed to by our table.
	HRLI A,440700
	MOVEM A,COMPT2		;Now have two ptrs set up.
IPAID4:	ILDB A,COMPT1
	CAIL A,"a		;Convert to uppercase.
	 CAILE A,"z
	  CAIA
	   SUBI A,40
	ILDB C,COMPT2		;Assume strings in ref table all uppercase.
	CAIE A,(C)		;Match?
	 JRST IPAID2		; Nope.
	SOJG D,IPAID4		;Yes, keep going.
IPAID9:	POPAE P,[D,C,A]
	;; Return # of attrib.  (always returns something, even if nil)
	RET



; LIPNUM - Takes SLP in A, tries to parse as oct or dec # & skips with
;	value in A if successful.

LIPNUM:	PUSH P,B
	MOVE B,LISTAR(A)+1	; Get SPT
	ADD B,$LSLOC(L)		; Make abs
	HLRZ A,B		; Get cnt in A, and
	HRLI B,440700		; make BP in B.
	CALL IPNUM		; Try to parse.
	 JRST POPBJ		; Foo, lost.
	JRST POPBJ1		; Won!

;  IPNUM - takes BP in B, # chars to gobble in A, and
;	tries to parse as a number (oct or dec). returns value in A,
;	doesn't skip if bad parse.  Updates BP.

IPNUM:	PUSHAE P,[C,D,E]
	SETZB C,D		;zero octal and decimal accumulators
	JUMPLE A,IPNU20		;if nothing to parse, return zero
IPNU05:	ILDB E,B		;Now flush leading whitespace.
	CAIE E,40
	 CAIN E,^I
	  CAIA
	JRST [	CAIE E,"-	; Is non-whiespace a minus sign?
		 TRZA F,%TMP	;   No, it's a digit or something.
		  TROA F,%TMP	; Yes, remember to negate.
		   JRST IPNU11	; Either way we're done with whitespace.
		JRST IPNU40]
	SOJG A,IPNU05
	JRST IPNU20		;Return zero if nothing there but whitespace.

IPNU10:	ILDB E,B		;Get ascii digit.
IPNU11:	CAIL E,"0		;Check to be sure it's a digit.
	 CAILE E,"9
	  JRST IPNU30		;Foo! Non-numeric char.
	LSH C,3			; Octal*8
	IMULI D,10.		; Decimal*10
	ADDI C,-"0(E)
	ADDI D,-"0(E)
IPNU40:	SOJG A,IPNU10
IPNU20:	MOVE A,C		;If no decimal pt, number is octal.
	JRST IPNU90		;Return.

IPNU30:	CAIE E,".		;Is non-numeric a decimal pt?
	 SOJA A,IPNU61		; No, go flush blanks/tabs
	MOVE C,D		;Ah yes, use decimal accumulator.
	SOJLE A,IPNU80		;If this was last char, we're done.
IPNU60:	ILDB E,B		;Else flush blanks/tabs.
IPNU61:	CAIE E,40
	 CAIN E,^I
	  CAIA
	   JRST IPNU70		;Foo? Can't do fractions.
	SOJG A,IPNU60
IPNU80:	MOVE A,C		;Put number into A.

IPNU90:	TRNE F,%TMP
	MOVN A,A		;Negate if minus-sign seen.
	AOS -3(P)
IPNU70:	POPAE P,[E,D,C]
	RET


; IPNUMA - Variant of IPNUM that uses $ARRPT(E).

IPNUMA:	PUSH P,B
	MOVE B,$ARRPT(E)
	CALL IPNUM
	 CAIA
	  AOS -1(P)
	MOVEM B,$ARRPT(E)	; Restore updated BP.
	POP P,B
	RET

; NUMCVT - Takes SP in A to a numerical string, returns value in A.

NUMCVT:	PUSH P,B
	MOVE A,LISTAR(A)+1	;get SPT
	HRRZ B,A		; Get SA address, and
	ADD B,$LSLOC(L)		; make absolute
	HRLI B,440700		; BP.
	HLRZ A,A		;cnt
	CALL IPNUM
	 SKIPA		
	  AOS -1(P)
	POP P,B
	RET


;  IPATTR - looks for attribute name and argument, using IPLING for input.
; Win return skips:
;	A = # chars in argument
;	B = index to attribute in table (0 if couldn't identify)
;	$ARRPT(E)= ptr to beg of argument text
; Failure (nonskip):
;	A - if zero, means normal EOF
;	   else SLP to error message.

IPATTR:	PUSHAE P,[C,D]
	CALL IPLING		;get a line... returns in a= # chars in line
	 JRST IPAEOF		;eof, none left

	; Copy attr name into atrbuf...
	MOVEI C,MXATRL		; Max length of attrib name
	MOVE D,[440700,,ATRBUF]
IPATR1:	ILDB B,$ARRPT(E)	; Get char
	CAIE B,":
	 CAIN B,";		; Attrib names are ended by colon or semicolon.
	  JRST IPATR2		; Aha!
	SOJL C,[MOVE A,[ASCNT [Attribute too long.]]
		JRST IPAERR]
	IDPB B,D		;deposit in ATRBUF
	SOJG A,IPATR1
	MOVE A,[ASCNT [EOF before attribute finished.]]
	JRST IPAERR

	; Found an attribute name.
IPATR2:	SUBI C,MXATRL		; Find -#chars in name
	MOVNM C,ATRLEN		; Store #
	PUSH P,B
	SETZ B,
	IDPB B,D		; Make atrbuf name ASCIZ.
	POP P,B
	SOJ A,			; Decrement # chars left on line.
	CAIE B,";		; Was terminator a semicolon?
	 JRST IPATR3		; No, a colon...rest of line is argument.

	; Semicolon arg... rest of line is char cnt, text starts on next line.
	CALL IPNUMA		; Parse number
IPATR4:	 JRST [	MOVE A,[ASCNT [Semicolon arg not numerical or otherwise bad.]]
		JRST IPAERR]
	JUMPL A,[CAME A,[-1]
		  JRST IPATR4
		MOVE A,IPLNCH	; -1 means gobble rest of world...
		JRST .+1]
	MOVE B,IPLNCH
	SUB B,A			; Subtract that many chars from rest of input
	JUMPL B,[MOVE A,[ASCNT [Semicolon arg truncated by EOF.]]
		JRST IPAERR]	; Sigh, ain't that many chars to read!
	MOVEM B,IPLNCH		; Else store decremented count
	MOVE B,IPLNPT		; Find ptr to next line
	ADD B,$ARLOC(E)		; Make abs.
	MOVEM B,$ARRPT(E)	; Stick in.
	PTSKIP A,IPLNPT		; Update ptr to first char after arg.

IPATR3:	CALL IPATID		; Identify the attribute in table.
	POPAE P,[D,C]		; Win even if not really identified.
	AOS (P)
	RET

IPAERR:	MAKELN A,[0 ? %LTSTR,,[A]]	; Error - return err message.
	CAIA
IPAEOF:	 SETZB A,IPLNCH		; EOF - indicate no more chars
	POPAE P,[D,C]
	RET



; IPLING - sets $ARRPT(E) to beg of line, returns in A = # chars in line.
;	sets IPLNPT and IPLNCH for next call; former is ptr to next line, latter is
;	total chars left in input.  to initialize, set IPLNPT=beg of input
;	(relative), IPLNCH= # chars.

IPLING: PUSHAE P,[C,D]
	MOVE C,IPLNPT		;get rel ptr to next start
	MOVE D,IPLNCH		;get # chars left from this point onward
	ADD C,$ARLOC(E)		;make ptr abs.
	MOVEM C,$ARRPT(E)	;set rartab. guaranteed to be on start of line, or end of one.
	JUMPLE D,IPLNG7		;leave here if no more input.

	;see if at end of a line; flush blank lines.
IPLNG1:	ILDB A,$ARRPT(E)	;search for next line
	CAIE A,^M
	 JRST IPLNG2
	SOJLE D,[STAT (,("FOO? NO ^J (EOF) BUT CONTINUING"))
		 JRST IPLNG7]
IPLNG5:	ILDB A,$ARRPT(E)
	CAIE A,^J		;check for next char=lf
	 JRST [STAT (,("FOO? NO ^J AFTER ^M, ASSUMED TO EXIST."))
	       JRST IPLNG2]
	SOJG D,IPLNG1		;search til find non-nil line.
	JRST IPLNG7		;eof, no more.

	;found non-nil line.  find length.
IPLNG2:	D7BPT $ARRPT(E)		;decrement ptr back to beg of line
	MOVE C,$ARRPT(E)	;put in acc.
	MOVE B,D		;save count at beg of line
IPLNG3:	ILDB A,C
	SOJLE D,[STAT (,("FOO? EOF IN MIDDLE OF LINE"))
		 JRST IPLNG7]
	CAIE A,40		;blank character?
	 CAIN A,11
	  JRST IPLNG3
	CAIE A,^M
	 JRST IPLNG4		;non-blank line, proceed
	MOVEM C,$ARRPT(E)	;Turned out to be blank line, skip it
	JRST IPLNG5

IPLNG4:	ILDB A,C
	CAIE A,^M
	 SOJG D,IPLNG4
	JUMPLE D,[STAT (,("FOO? EOF BAD, BUT CONTINUING"))
		  JRST IPLNG8]
	SUB B,D			;found EOL. get # chars in line
	SOJLE D,[STAT (,("FOO? EOF BAD, BUT CONTINUING"))
		 JRST IPLNG8]
	ILDB A,C
	CAIE A,^J
	 JRST [	STAT (,("FOO? NO ^J AFTER ^M, ASSUMED TO EXIST"))
		D7BPT C
		JRST .+2 ]
	  SOJ D,

	SUB C,$ARLOC(E)		;get relative ptr
	MOVEM C,IPLNPT		;store back for next call
IPLNG6:	MOVEM D,IPLNCH		;ditto for char cnt
	MOVE A,B		;return in a= # chars on line
	MOVEM A,IPLTCH		;store for error typeout also
	MOVE C,$ARRPT(E)
	SUB C,$ARLOC(E)
	MOVEM C,IPLAST		;ptr to last line processed, and # chars on it.
	POPAE P,[D,C]
	AOS (P)
	RET

	;graceful exit when eof found before any material.
IPLNG7:	POPAE P,[D,C]
	RET

	;exit when eof found before crlf, but material exists.
IPLNG8:	SETZ D,
	JRST IPLNG6

DEFINE INPATR *NAME*,ROUT
	[ASCNT [NAME]],,ROUT
TERMIN

	; Input attrib dispatch table.  Attrib names must be less than
	; MXATRL chars!
ATRTAB:
	INPATR ||,PARNIL		;default for unknown names. must be 1st entry.
	INPATR |SUBJECT|,PARSBJ
	INPATR |TEXT|,PARTXT
	INPATR |RCPT-LIST-NAME|,PARRLN
	INPATR |FAKE-TO|,PARRLN		; New form of RCPT-LIST-NAME.
	INPATR |FAKE-FROM|,PARFFM
	INPATR |ERRORS-TO|,PARMRP	; Another SMTP hack.
	INPATR |USER-HEADER|,PARUHD
ATRTAS:   ;;; Below here, leading spaces are NOT ignored.
	INPATR |CLAIMED-FROM|,PARCSN	;sender claims to be this instead
	INPATR |FROM|,PARSND		;message sent from this uname
	INPATR |SENT-BY|,PARSBY		; "         "    "  "  "
	INPATR |FROM-JOB|,PARFJB	; Program that sent message.
	INPATR |TO|,PARRTO
	INPATR |AUTHOR|,PARCSN
	INPATR |FROM-UNAME|,PARSBY	; New form of sent-by.
	INPATR |FROM-XUNAME|,PARNIL	; XUNAME of sending process
	INPATR |FROM-PROGRAM|,PARFJB	; Program that sent message.
	INPATR |RCPT|,PARRCP
	INPATR |NET-MAIL-FROM-HOST|,PARNTS
	INPATR |HEADER-FORCE|,PARHDF	;header force
	INPATR |REGISTERED|,PARREG	;confirmation options
	INPATR |EXPIRES|,PAREXP
	INPATR |MSG-FN1|,PARMFN
	INPATR |MSG-FN2|,PARMF2
	INPATR |ATTRIBUTE|,PARATR	; Arbitrary attribute/value pair.
	INPATR |RETURN-PATH|,PARRTP	; SMTP hack.
NIATTR==.-ATRTAB

; PARATR - Implements arbitrary attribute/value input.
;	argument must be a list, (ATTRIB ATTRIBVAL), or in ordered pairs.
PARATR:	SETOM LRDLEV		; Set reading at top-level.
	SETZM LRDLIN		; Initialize LREADR to top line.
	CALL LREADR		; Read in the structure.
	 JRST [	MOVE A,B	; Foo. return error SLN.
		RET]
	JUMPG A,APOPJ		; Later add err msgs for these too.
	JUMPE B,APOPJ
	MOVE A,B
	CALL EVALA		; "Evaluate" it...
	 JRST APOPJ		; Blew up.
	AOS (P)
	RET

; PARSTR - Common subroutine of following rtns.  Pops string into
;	a SLN with attrib type as specified in B, returns SLP in A.
PARSTR:	HRLZ A,A
	HRRI A,$ARRPT(E)	;set up ptr to string
	MAKELN A,[0 ? %LTBPT,,[A]]
	DPB B,[$LAFLD,,LISTAR(A)]
	AOS (P)			; Always wins.
	RET

; PARNIL - This routine gives unknown attribs a home.

PARNIL:	SETZ A,
	PJRST POPJ1	; Make them win for time being


; SUBJECT - parse subject line.

PARSBJ:	MOVEI B,A$SBJ
	PJRST PARSTR

; TEXT - stores text of message.

PARTXT:	CSTAT (,LBRC,("TL="),DEC(A),RBRC)
	MOVEI B,A$MTXT
	PJRST PARSTR

; Recipient List Name - String to use in header of message as substitute for
;	list of recipients.

PARRLN:	MOVEI B,A$RLN
	CALL PARSTR
	 JSR AUTPSY
	CSTAT (,LBRC,("RLN="),TLS(A),RBRC)
	AOS (P)
	RET

; Fake From - String to use in header of message as "From" line.

PARFFM:	MOVEI B,A$FFM
	CALL PARSTR
	 JSR AUTPSY
	CSTAT (,LBRC,("FAKE-FROM="),TLS(A),RBRC)
	AOS (P)
	RET


;PARHDF - parses "HEADER-FORCE:<string>"

PARHDF:	MOVEI B,A$HFRC
	CALL PARSTR
	 JSR AUTPSY
	AOS (P)
	PJRST SLNUPR	; Force to uppercase and return.

; USER-HEADER - parse user-specified header line.

PARUHD:	MOVEI B,A$UHDR
	PJRST PARSTR

; PAREXP - parses expiration date for a MSG

PAREXP:	CALL PARNUM
	JUMPG A,PARNER		; If still chars left, failed.
	CAILE B,365.
	 MOVEI B,365.		;no more than 1 year.
	ADDI B,1		;plus 1 for good measure (consider sending at 11:59 pm)
	MAKELN A,[A$XPIR,,0
		%LTVAL,,[B]]
	AOS (P)
	RET

PARNER:	MAKELN A,[0 ? %LTSTR,,[LITSTR [Bad parse of number.]]]
	RET
PARFER:	MAKELN A,[0 ? %LTSTR,,[LITSTR [Bad format.]]]
	RET

; PARMFN - parses FN1 or FN2 for a MSG

PARMFN:	SKIPA B,[A$MFN1]	; FN1 attrib
PARMF2:	 MOVEI B,A$MFN2		; FN2 attrib
	HRLZ A,A
	HRRI A,$ARRPT(E)	;set up <cnt>,,[<bp>]
	CALL CVT6PC		;cvt to 6bit
	CAIN A,0
	 JRST POPJ1		;bad arg, but will just add default later.
	MAKELN A,[0 ? %LTVAL,,[A]]
	DPB B,[$LAFLD,,LISTAR(A)]	;deposit attrib type.
	AOS (P)
	RET


; NET-MAIL-FROM-HOST - finds where net msg came from.

PARNTS:	CALL IPNUMA			; Crunch host number
	 JRST PARNER			; Blah, go return err msg.
	CALL RESOLV"STDHST		; Standardize the number.
	CSTAT (,LBRC,("NET-MAIL-FROM: "),HST(A),RBRC)
	MAKELN A,[A$NMH,,0		; Form LN to store host # in
		%LTVAL,,[A]]
	SETOM CNFERR		;confirm errors only
	AOS (P)
	RET

; PARREG - parses "REGISTERED:<char>"

PARREG:	JUMPLE A,[SETZ A, ? JRST POPJ1]		;if nothing, win anyway.
	PUSHAE P,[B,C]
PARRG2:	ILDB B,$ARRPT(E)
	CAIL B,141		; This uppercasify will suffice.
	 TRZ B,40
	CAIN B,"A
	 SETOM CNFALL		;set switch indicating "ALL" seen
	CAIN B,"F
	 SETOM CNFERR		;set switch indicating "only errors" seen
	SOJG A,PARRG2
	SETZ A,
	POPAE P,[C,B]
	AOS (P)
	RET
	
BVAR
CNFALL:	0		;when set, means "ALL" seen
CNFERR:	0		;when set, means "only errors" seen
EVAR


; PARFJB - Parses "FROM-JOB:<jname>"
;	Only function at moment is to report in stats.

PARFJB:	SETZ B,
	CALL PARSTR
	 JSR AUTPSY
	CSTAT (,LBRC,("J:"),TLS(A),RBRC)
	LNDEL A,
	SETZ A,
	PJRST POPJ1

; PARSBY - parses "SENT-BY:<uname>"

PARSBY:	MOVEI B,A$SNM
	CALL PARSTR
	 JSR AUTPSY
	CSTAT (,LBRC,TLS(A),RBRC)
	AOS (P)
	RET

; PARMRP, PARRTP - SMTP "Return-Path" values.
; A$SRTPs are real SMTP return paths from foreign hosts.
; A$SMRP is a hack to allow users to specify them for outgoing mail.

PARMRP:	SKIPA B,[A$SMRP]
PARRTP:	 MOVEI B,A$SRTP
	CALL PARSTR
	 JSR AUTPSY
	CSTAT (,LBRC,("RTP:"),TLS(A),RBRC)
	JRST POPJ1

; PARCSN - parses "CLAIMED-FROM:<name>"
; "Claimed-From" - sender claims to really be this name, rather than
;	that which "From" gives.

PARCSN:	MOVEI B,A$CSN
	CALL PARSTR
	 JSR AUTPSY
	CSTAT (,LBRC,("CLAIMS-TO-BE:"),TLS(A),RBRC)
	AOS (P)
	RET


; PARSND - parse FROM:<chars>"<sender-name>  (OBSOLETE)

PARSND:	CAIG A,1		; Must be at least 1 char (quote mark)
	 JRST PARFER		; Bad format, complain.

	CSTAT (,("<Obsolete FROM>"))	; Crock to help track down.
	SETOM OFRMFG		; Indicate obsolete FROM seen.

	PUSHAE P,[B,C,D]
	MOVE B,A		;put count into b
	MOVEM B,PRSNCT		; and save it for quote-less kludge.
	SETZ A,			;clear (holds accum. ptr)

	SETZM CNFQ
	SETZM CNFX
PARSN5:	SOJL B,PARSN3		;If never find quotemark, use whole str as name
	ILDB C,$ARRPT(E)	;get char
	CAIL C,141		; This uppercasify will suffice
	 TRZ C,40
	CAIN C,""		;quote mark=terminates options, begins name
	 JRST PARSN2		;exit option loop
	CAIE C,"N		; Requesting NET-style header?
	 CAIN C,"T		; (old way used T)
	  MAKELN A,[A$HFRC,,[A]
		%LTSTR,,[LITSTR [NET]]]
	CAIN C,"I		;requesting ITS-style header?
	 MAKELN A,[A$HFRC,,[A]
		%LTSTR,,[LITSTR [ITS]]]
	CAIN C,"R		;requesting quote of text (null header)?
	 MAKELN A,[A$HFRC,,[A]
		%LTSTR,,[LITSTR [NULL]]]

	CAIN C,"Q
	 SETOM CNFQ		;indicate Q seen
	CAIN C,"X
	 SETOM CNFX		;indicate X seen
	JRST PARSN5

	; Fond no quote-mark in arg.  Assume whole string intended.
PARSN3:	LNDEL A			; Flush any randomness mistakenly done.
	SETZM CNFQ
	SETZM CNFX
	MOVN B,PRSNCT		; get back original count (negative)
	PTSKIP B,$ARRPT(E)	; and original bp
	MOVMS B			; Now set up psitive cnt & drop thru.

	; Cons up SLN for name string.
PARSN2:	HRLZ B,B		;put char cnt in lh
	HRRI B,$ARRPT(E)	;and addr of ptr in rh
	MAKELN A,[A$SNM,,[A]	;store sender-name string
		%LTBPT,,[B]]	;note arg is byte-ptr type
	CSTAT (,LBRC,TLS(A),RBRC)

	MOVE B,CNFQ
	ADD B,CNFX
	JUMPE B,[SETOM CNFALL	;if neither option seen, confirm all.
		JRST PARSN7]
	SKIPE CNFX		;if it was X that got set,
	 SETOM CNFERR		;confirm errors only.
PARSN7:	POPAE P,[D,C,B]
	AOS (P)
	RET

.SCALAR CNFQ,CNFX,PRSNCT

.SCALAR PARFSW
.SCALAR PRCPH	; # of site
	;PRCPH is also looked at by EQRCPL due to bad modularity
	;PRCPH is also looked at by RCPHST due to bad modularity

; PARRTO - parses "TO" attribute.
; PARRCP - parses "RCPT" attribute.

PARRCP:	SETZM PRCPH
	CAIA
PARRTO:	 SETOM PRCPH
	JUMPLE A,PARFER		; Make sure something there.
	PUSHAE P,[B,C,D]
	SETZ C,
	SKIPN PRCPH		; TO, or RCPT?
	 JRST PARR40		; RCPT attrib.
	SETZM PRCPH		; TO. Clear # of site to send to

	; See what syntax we're crunching.  Can assume that leading
	; spaces already flushed.
	MOVE B,$ARRPT(E)	; Get scratch copy of BP.
	ILDB B,B		; Get 1st char.
	CAIL B,"0
	 CAILE B,"9		; See if numeric char...
	  JRST [CAIE B,"(	; If not, reading structured rcpt?
		 JRST PARR12	; No, use local host and enter mode-switch loop
		SETZ C,		; Yes, indicate no mode-switch LN's, and
		JRST PARR40]	; go hack structured rcpt.

	; Assume old style - get host #
	CALL PARNUM		; Read a number, return # in B.
	EXCH A,B
	CALL RESOLV"STDHST	; Standardize the host #
	EXCH A,B
	CAMN B,OWNHST
PARR12:	 SETZ B,		; Use 0 if local.
	MOVEM B,PRCPH

	; Enter kludgy mode-switch loop.
	SETZ C,
	SETZM PARFSW
PARR20:	ILDB B,$ARRPT(E)
	CAIN B,""
	 SOJA A,PARR3
	CAIN B,"C		;cc?
	 JRST [MAKELN C,[A$ROPT,,[C]
			%LTSTR,,[LITSTR [CC]]]
		JRST PARR25]
	CAIN B,"+
	 JRST [	MOVEI B,1
		MOVEM B,PARFSW
		JRST PARR26]
	CAIN B,"-
	 JRST [	SETOM PARFSW
		JRST PARR26]
	CAIN B,"S
	 JRST [	MAKELN C,[A$RMDS,,[C]
			%LTVAL,,[PARFSW]]
		JRST PARR25]
	CAIN B,"M
	 JRST [	MAKELN C,[A$RMDM,,[C]
			%LTVAL,,[PARFSW]]
		JRST PARR25]
	JRST PARRLS		; Error, report bad format.
PARR25:	SETZM PARFSW
PARR26:	SOJG A,PARR20

PARR3:	JUMPLE A,PARRLS		; Ran out?  Also error, bad format.

	; C must be set up with LP to any mode-switch LN's read.
PARR40:	SETOM LRDLEV		; Read at top level...
	SETZM LRDLIN		; Initialize LREADR to top line.
	CALL LREADR		; Read in a structure.
	 JRST [	MOVE A,B	; Ugh?! Return err-msg SLN.
		JRST PARR90]
	JUMPE B,PARRLS		; Error if no structure was read!
	JUMPG A,[CALL PARR50	; If anything left, look for host
		  JRST PARRLS
		 JRST .+1]
	MOVE A,B
	PUSH P,C
	MOVEI C,A$RCP
	CALL APRCPT		; Process rcpt structure.
	 JRST [POP P,C ? JRST PARR90]		; Bleah.
	POP P,C
	FINDA B,[A$RHST,,[LISTAR(A)+1]]	; Specified a host?
	 JRST [	SKIPN PRCPH		; No, was any old-style site specified?
		 JRST .+1
		MAKELN B,[A$RHST,,[LISTAR(A)+1]
			%LTVAL,,[PRCPH]]
		MOVEM B,LISTAR(A)+1
		JRST .+1]
	LNAPP [LISTAR(A)+1 ? C]
	AOS -3(P)
PARR90:	POPAE P,[D,C,B]
	RET
PARRLS:	POPAE P,[D,C,B]
	PJRST PARFER		; Return "bad format" err msg.

;Read list in B, have number of characters left in A, presuambly
;after a close parenthesis.
;Normally this is an error, but allow "@HOST" here and drop host
;into PRCPH if so.
PARR50:	PUSHAE P,[B,C,D]
	MOVE B,$ARRPT(E)
	ILDB D,B
	CAIE D,"@
	 JRST PARR59
	SUBI A,1		; A has number of chars in SITE name.
				; And B has the BP to the site name.
	CALL HMATC		; Call host-name analyze rtn
	 JRST PARR59		; Foo?  Fail.
	MOVEM A,PRCPH		; Win, save site number
	AOS -3(P)
PARR59:	POPAE P,[D,C,B]
	RET

	; Maybe someday make IPNUM general enough to 
	; flush this routine.  Could simply test for succ/fail by
	; seeing if any chars left, etc.
PARNUM:	PUSHAE P,[C,D]
	SETZB B,C
PARNM0:	JUMPLE A,PARNM9
	ILDB D,$ARRPT(E)
	CAIL D,"0
	 CAILE D,"9
	  JRST PARNM3
	LSH B,3
	IMULI C,10.
	ADDI B,-"0(D)
	ADDI C,-"0(D)
	SOJA A,PARNM0
PARNM3:	CAIN D,".	; Must cvt to decimal?
	 SKIPA B,C	; use other sum if so, and skip BP restoration.
	  D7BPT $ARRPT(E)
PARNM9:	POPAE P,[D,C]
	RET
SUBTTL RCPGOB - Ascii Rcpt-List Parser

; RCPGOB - Takes an ARPT in A, which is taken as an ascii input.
;	The input is as in the NAMES > file or inside "@FILE" files.
;	An LP to a list of A$RCP LNs is returned in A, and the number
;	of errors encountered is returned in B.  If there were any errors,
;	they were written to the ETXTAR area.
;		This calls LREADR to read the ascii, and APRCPT (which calls
;	EQRCPL to make sense out of what LREADR returns.
; RCPGBA - The same, except it takes a byte pointer in B and a character count
;	in A.  (Special entrypoint for RNETAD).

LVAR RPGERR:	0	; The number of errors so far.
LVAR RPGIBP:	0	; BP storage for special non-area reading.

RCPGBA:	PUSHAE P,[C,D,E]
	MOVEM B,RPGIBP
	MOVEI E,RPGIBP-$ARRPT
	JRST RCPGB0
RCPGOB:	PUSHAE P,[C,D,E]
	MOVE E,A		; E = ARPT to input area.
	MOVE A,$ARRPT(E)
	PTRDIF A,$ARWPT(E)	;get # chars in area.
RCPGB0:	SETZ D,			; D = LP to collected rctps
	MOVEM A,IPLNCH
	SETZM LRDLIN		; zero # lines read
	SETZM RPGERR		; and # errors.
	
	; Parse lists.

RCPG10:	SETOM LRDLEV		; Set input level to top-level.
	MOVE A,IPLNCH
	CALL LREADR		; Read in a list-structure or atom.  A has # chs left.
	 JRST RCPG50		; Failed due to some lossage. B has SP to errmsg.
	JUMPE B,RCPG90		; Done when returned LP is 0.
	MOVEM A,IPLNCH		; Save current char cntdown.
	SETZM PRCPH		; Always local host!
	MOVE A,B		; Put LP in A.
	MOVEI C,A$RCP		; Attrib type to use in C.
	CALL APRCPT		; Crunch as if rcpt spec. (returns LP in A)
	 JRST RCPG60		; Foo?
	HRRM D,LISTAR(A)	; Point its CDR at current list of rcpts
	MOVE D,A		; and now current list points to it!
	JRST RCPG10		;loop

	; Error during parse of a list.
RCPG50:	MOVEM A,IPLNCH
	AOS C,RPGERR		; Increment count of errs.
	CAIG C,1		; If first one,
	 UAROPN [%ARTCH+%ARTZM,,ETXTAR ? [40]]	; Open small area for error text collection.
	OUT(TMPC,OPEN(UC$UAR,ETXTAR),("  --L"),D(LRDLIN),(" Parse error: "),SL(B),EOL)
	JRST RCPG10		; Continue gobbling.

	; Error during evaluation of a list
RCPG60:	AOS C,RPGERR		; Bump cnt of errors
	CAIG C,1		; If first one,
	 UAROPN [%ARTCH+%ARTZM,,ETXTAR ? [40]]	; Open very small area for error text collection.
	OUT(TMPC,OPEN(UC$UAR,ETXTAR),("  --L"),D(LRDLIN),(" EVAL error: "),SL(A),("
    In list: "))
	MOVE B,IPLNCH		; Find # chars left
	SUB B,LRDBGL		; Subtract # chars left at time list began
	SUBI B,1		; Plus 1 to include left paren
	MOVE C,$ARRPT(E)	; to get -<length of list read>.  Then get read ptr
	PTSKIP B,C		; And adjust BP to point at beg of string,
	MOVMS B
	OUT(TMPC,S(B,C),EOL)	; Output the bad list text
	JRST RCPG10		; Now continue...

RCPG90:	MOVE A,D		; Return LP to collected list in A
	MOVE B,RPGERR		; and cnt of errors in B
	POPAE P,[E,D,C]
	RET
SUBTTL LREADR - LISP-style reader
; LREADR - LISP-style reader.  A has char count, E has ARPT to
;	area holding input text ($ARRPT is used).  S-exp is
;	read into a list structure of LLN's and SLN's; a String
;	corresponds to an atom, and a LLN IS a list.
;	Returns LP in B to resulting structure, but B = 0 if
;	input terminated and nothing was read.
;		If an error occurs, it will fail to skip, but
;	will try to set up recovery so that the next call will
;	proceed normally.  A failure message will be returned
;	in B as a LP to SLN holding message.

BVAR
LRDLEV:	0		; Holds paren level reading at.  -1 = top level.
LRDLIN:	0		; Holds line # reading at.
LRDBGL:	0		; Holds current char cnt at point list begins.
EVAR

LREADR:	JUMPLE A,LRDRZW
	TRZA F,%BRKT		;clear bracket-literal flg and skip into loop.
LRDR05:	 SOJLE A,LRDRZW		; decrement char cnt and jump if all gone.
	ILDB B,$ARRPT(E)
	CAIE B,40		; Ignore spaces
	 CAIN B,^I		; and tabs.
	  JRST LRDR05
	CAIE B,^J		; Also ignore LF's
	 CAIN B,",		; and commas.
	  JRST LRDR05
	CAIE B,^L	    	; And ignore form feeds
	 CAIN B,^K		; and vertical tabs!
	  JRST LRDR05

	CAIN B,^M		; CR's must be treated specially..
	 JRST LRDR60		; off to hack CR.
	CAIN B,";		; Also hack ";" specially, as comment.
	 JRST LRDR50
	CAIN B,"(
	 SOJA A,LRDR70		; Beginning of list!
	CAIN B,")
	 SOJA A,LRDR80		; End of list!
	CAIE B,^Q
	 CAIN B,"/		; Either ^Q or / quotes next char...
	  JRST [SOJLE A,LRDRZW
		ILDB B,$ARRPT(E)
		TRO B,200	; Quote next char, whatever it is
		JRST LRDR30]

	; Have something substantial - now process atom!  By special
	; hack, brackets will enclose an atom, including themselves!
	TRZ F,%BRKT+%LRLIT	; Clear flags...
	CAIN B,""
	 JRST [	TRO F,%LRLIT	; If beginning a literal string,
		SAOBEG TMPC,	; must start up chan this way
		JRST LRDR32]	; to avoid deposit of the ".
	CAIE B,"{		; See whether to set.
	 CAIN B,"[
	  TRO F,%BRKT+%LRLIT	; Set flag if bracket-literal.


LRDR30:	SAOBEG TMPC,		; Start up SA output channel.
LRDR31:	OUT(TMPC,C((B)))	;store char
LRDR32:	SOJLE A,LRDR38		;ran out?
	ILDB B,$ARRPT(E)	;get another
	TRNN F,%LRLIT		; Collecting literal string?
	 JRST LRDR35		; Nope...
	TRNE F,%BRKT		; Collecting a bracketed literal?
	 JRST [	CAIE B,"]	;end of bracketed literal?
		 CAIN B,"}
		  CAIA
		   JRST LRDR31	;no, gobble til find end.
		OUT(TMPC,C((B)))
		MOVEI B,40
		SOJA A,LRDR38]	;store and break. (pretend broke on 40)
	CAIE B,""
	 JRST LRDR31		; If not terminator, just gobble.
	SOJLE A,LRDR38
	ILDB B,$ARRPT(E)	; Get next char to test for another "
	CAIE B,""		; Well, is it?
	 JRST LRDR37		; Nope.  Break on this.
	JRST LRDR31		; Hmm, so it is!  Gobble that & continue.
	
LRDR35:	CAIE B,^Q
	 CAIN B,"/		;quoter?
	  JRST [MOVEI B,^Q	;yes, quoting. replace with ^Q
		TRNE F,%BRKT	;in case hacking brackets.
		 OUTCAL(TMPC,C((B)))	;since if so, store the ^Q.
		SOJLE A,LRDR38	;get quoted char.
		ILDB B,$ARRPT(E)
		JRST LRDR31]	;store it with no questions
	CAIE B,40
	 CAIN B,^I
	  JRST LRDR37		;string break.
	CAIE B,")
	 CAIN B,";
	  JRST LRDR37		;also break.
	JUMPE B,LRDR37		;Null must be a break for NETAD fields.
	CAIE B,^M
	 CAIN B,^J
	  JRST LRDR37		;and break.
	CAIE B,"(
	 CAIN B,",
	  JRST LRDR37		;and break again.
	JRST LRDR31		; Didn't break, get more chars.

	; Hit break char, atom finished! Break char in B.
LRDR37:	D7BPT $ARRPT(E)		;back up over brk char, so it gets read again
LRDR38:	MAKELN B,[0,,0
		%LTSAO,,0]	;and make string LN for it
	JRST LRDRW		;and win.


	; ";" found, flush to CR.
LRDR50:	SOJLE A,LRDRZW
	ILDB B,$ARRPT(E)
	CAIE B,^M		; Skip and drop through when CR found.
	 JRST LRDR50		; Until then, flush chars.

	; Here when find CR and no atom string yet
LRDR60:	AOS LRDLIN		;increment # lines seen
	SKIPGE LRDLEV		;are we in toplevel?
	 JRST LRDR05		;yes, ignore CR.
LRDR62:	SOJLE A,LRDRZW		;no, must check beginning of next line.
	ILDB B,$ARRPT(E)
	CAIN B,^J
	 JRST LRDR62		;check again for LF with new char
	CAIE B,40		;now check first char on new line.
	 CAIN B,^I
	  JRST LRDR05		;space and tab win.
	CAIN B,";
	 JRST LRDR50		;so does a comment line
	CAIN B,^M
	 JRST LRDR60		;another CR needs another check.
	; If none of above, lose!! Flush up to next line to be safe.
	SAOBEG TMPC,
	FWRITE TMPC,[[Illegal continuation, Line ],N9,LRDLIN,[ - perhaps missing ")"]]
LRDR64:	AOS LRDLIN
LRDR65:	SOJLE A,LRDR95		; Now flush to first line with something on it.
	ILDB B,$ARRPT(E)
	CAIE B,^M
	 JRST LRDR65		; Flush til CR seen.
LRDR66:	SOJLE A,LRDR95
	ILDB B,$ARRPT(E)
	CAIN B,^J		; and if necessary til all LF's gone.
	 JRST LRDR66
	CAIE B,40		;now check first char on new line.
	 CAIN B,^I
	  JRST LRDR65		;space and tab lose.
	CAIN B,";		;so does a comment line,
	 JRST LRDR65
	CAIN B,^M		; and another CR.
	 JRST LRDR64
	D7BPT $ARRPT(E)		; Aha, flush done. Now point to first char of new line,
	FWRITE TMPC,[[
	Resuming at line ],N9,LRDLIN]
	JRST LRDR95		; and bomb out gracefully.

	; Here when "(" seen, must start collecting list.
LRDR70:	PUSHAE P,[C,D]
	AOSG LRDLEV		; Increment level we're at.
	 MOVEM A,LRDBGL		; If reader was at top level, store char cnt at beg.
	SETZB C,D
LRDR71:	CALL LREADR		;get whatever is next in list
	 JRST POPDCJ		; Error. Pass it on.
	JUMPL B,LRDR75		;if ")", end list.
	JUMPE B,[MOVE B,[ASCNT [EOF and no ")"]]
		POPAE P,[D,C]
		JRST LRDR90]	;out of chars?? lose, need ")" to terminate.
	JUMPE C,[HRRZ C,B	; If no LP to whole list yet, store first as such.
		HRRZ D,B
		JRST LRDR71]
	HRRM B,LISTAR(D)	;store ptr to returned LN in CDR of last in list.
	HRRZ D,B		;save ptr to it.
	JRST LRDR71		;get next item.
LRDR75:	MOVE B,C		;ended with ")".  return ptr to start of.
	SOS LRDLEV		; and bump level one up.
	POPAE P,[D,C]
	MAKELN B,[0 ? %LTLST,,[B]] ;make a List-LN for it.
	JRST LRDRW		;and go win.

	; Here when ")" seen - end of list.
LRDR80:	SKIPL LRDLEV		; at top level?
	 JRST [	SETO B,		; No, ")" is legal... set B to -1 and return to self!
		JRST LRDRW]
	SAOBEG TMPC,
	FWRITE TMPC,[[Line ],N9,LRDLIN,[: Stray ")" seen at top level!]]
	JRST LRDR64		; Go flush to first line with something on.

	; Failure return with error message in B.
LRDR90:	SAOBEG TMPC,
	FWRITE TMPC,[[Line ],N9,LRDLIN,[: ],TC,B]
LRDR95:	MAKELN B,[0 ? %LTSAO,,0]
	RET

LRDRZW: SETZ B,			; Return here when A counted out and nothing read yet.
LRDRW:	AOS (P)
	RET

SUBTTL EQRCPL - Single rcpt evaluation

; EQRCPL - Recipient parsing routine.  
; 	   See ASCII EQV file description for syntax it parses.
;
;  The idea here is that EQRCPL is a prelude to EVALA.  EVALA wants to
;  see ((ATTRIB VAL) (ATTRIB VAL) ...).  EQRCPL deals with the special
;  hacks at the front of such a list, to save lots of typeing, and then
;  goes to ERCP40 which calls EVALA on the rest of the list.  Returns
;  result in A, or else an SLP to an error message in A (like EVALA).
;
;  Due to bad modularity this also has to look at PRCPH which if non-zero
;  is an implicit host that has not yet been put into the data structure.

EQRCPL:	PUSHAE P,[B,C,D,E,EQRGUB]
	SETOM EQRGUB'			;No magic for special rcpt type yet
	MOVE C,LISTAR(A)		; Make sure given LLN is chopped
	HLLZS LISTAR(A)			; off from its CDR.
	TLNE C,%LTLST			; Is input a list?
	 JRST ERCP10			; Yup, go handle major case.

	;; We simply have an atom.
	MAKELN D,[0 ? %LTLST,,0]	; Make a recipient list.
	MOVEI B,A$RNAM			; Assume it will be a NAME.
	DPB B,[$LAFLD,,LISTAR(A)]
	CALL RCPHST			; Try parsing as Foo@Site.
	 SETZ B,			;  If fail, just ignore.
	SKIPE B				; Got a host?
	 JRST [	HRLM C,LISTAR(A)+1	; Yes, zap string to given length.
		MAKELN B,[A$RHST,,[LISTAR(D)+1] ? %LTVAL,,[B]]
		MOVEM B,LISTAR(D)+1	; Store rcpt!
		JRST .+1]
	CALL RCPTYP			; Check for magical names.
	JUMPN B,[ HRL B,A		;  Save old node,,new node
		  MOVEM B,EQRGUB	;  for later when we know what host.
		  JRST .+1 ]
	LNAPP [LISTAR(D)+1 ? A]
	JRST ERCP90

	;; We have a list.
ERCP10:	MOVE D,A			; Save LP to LLN in D. 
	HRRZ A,LISTAR(D)+1		; Get LP to its list for munching.
	SETZM LISTAR(D)+1		; And zap the LLN to start anew.
	JUMPE A,[SAOBEG TMPC,		; Make sure something in list
		FWRITE TMPC,[[Nil recipient spec!]]
		JRST ERCP95]
	MOVE C,LISTAR(A)		; Something there. Look at 1st LN
	TLNN C,%LTSTR			; must be atomic.
	 JRST ERCP40			; Else start hacking attribs already!

	; Come here when the first item is atomic - see if it
	; matches any known recipient type.
	MOVSI C,-NRTYPS
	USLNEA A,RTYPTB(C)	; Try matching.
	 AOBJN C,.-1
	JUMPGE C,ERCP14		; Jump if NOT a type...
	CALL SLNUPR		; It's a type! Make it uppercase.
	MOVEI B,A$RTYP
	DPB B,[$LAFLD,,LISTAR(A)]	; and insert proper attribute.
	HRRZ B,LISTAR(A)		; Now get next LN in list.
	HLLZS LISTAR(A)
	LNAPP [LISTAR(D)+1 ? A]
	MOVE A,B
	JUMPE A,[SAOBEG TMPC,
		FWRITE TMPC,[[Type specified without recipient name!]]
		JRST ERCP95]
	MOVE B,LISTAR(A)
	TLNN B,%LTSTR		; Must be atomic also (recipient name).
	 JRST ERCP40		; If not, start attrib collection.

	; Come here if the first item is atomic and not a type, or if the first
	; item was a type and the second item is atomic.
ERCP14:	MOVEI B,A$RNAM			; Item must be interpreted as a NAME,
	DPB B,[$LAFLD,,LISTAR(A)]	;   but we try parsing it too:
	CALL RCPHST			; See if can parse as Foo@Site
	 SETZ B,			; If fail, just ignore.
	JUMPN B,[HRLM C,LISTAR(A)+1	; Can! Zap string to given length.
		MAKELN B,[A$RHST,,[LISTAR(D)+1]
			%LTVAL,,[B]]
		MOVEM B,LISTAR(D)+1
		JRST .+1]
	CALL RCPTYP			; And see if name has type-indicator...
	JUMPN B,[ HRL B,A	;Save old node,,new node
		  MOVEM B,EQRGUB	;for later when we know what host
		  JRST .+1 ]
	MOVE B,A		;Assuming this is R-name put SLP in B
	HRRZ A,LISTAR(B)	; Save CDR
	HLLZS LISTAR(B)		; Zap within LN.
	LNAPP [LISTAR(D)+1 ? B]

	; Here we have finished up with the first atomic item (or first two 
	; if the first was a type and the second was atomic.
	; Are there more atomic special cases?
ERCP25:	JUMPE A,ERCP90		; End of list is OK now.
	MOVE B,LISTAR(A)	; Find type.
	TLNN B,%LTSTR		; Atomic?
	 JRST ERCP40		; No, list... it's an attribute!

	; Yes, more special case stuff.  There are three cases:
	; 1) It is "@", which means the next thing had better be an 
	;    atom parsable as a site name.
	; 2) It starts with an @, so it had better be parsable as a site name.
	; 3) It is an atom parseable as a site name.
	; If it is just a random atom, it is passed to EVALA.
	SLNEA A,[ASCNT [@]]
	 JRST ERCP30		; Not "@", continue.
	HRRZ B,LISTAR(A)	; Save the CDR
	LNDEL A,		; Flush the "@".
	SKIPN A,B		; Get LP into A.  Is it NIL?
	 JRST ERCPBA		; Yes, error
	MOVE B,LISTAR(A)	; If next LN is non-atomic, error.
	TLNN B,%LTSTR
	 JRST ERCPBA
ERCP30:	PUSH P,A		; Save the LP
	MOVE B,LISTAR(A)+1	; Get SPT.
	HRLI B,440700		; set up BP in B.
	ADD B,$LSLOC(L)		; Make absolute.
	ILDB C,B		; Get first char.
	CALL HMATCH		; And go analyze the string.
	 JRST [	POP P,A		; Foo, lost.  Restore LP
		CAIE C,"@	; and see if this was SUPPOSED to win.
		 JRST ERCP40	; No, go parse merrily as atomic attribute.
		SAOBEG TMPC,	; Ugh! definitely an error.
		FWRITE TMPC,[[Bad SITE spec - "],TLS,A,["]]
		JRST ERCP95]
	POP P,B			; Aha, winning site spec.  Restore LP, into B.
	MAKELN A,[A$RHST,,0	; Now create new LN...
		%LTVAL,,[A]]
	LNAPP [LISTAR(D)+1 ? A]
	HRRZ A,LISTAR(B)	; Now get CDR to next thing on input list,
	LNDEL B,		; Before flushing the site-string LN.

	; Here, ready to evaluate rest of list as attributes... A has LP to,
	; D has LLP to a LLN containing sublist collected thus far.
ERCP40:	JUMPE A,ERCP90		; If NIL, all done.
ERCP45:	CALL EVALA		; Evaluate rest!
	 JRST ERCP99		; Bleah, failed.  Err SLP in A.
	CAIE A,0
	 LNAPP [LISTAR(D)+1 ? A]

	; Here, recipient-spec processing all done! LP in D to completed list.
ERCP90:	SKIPGE EQRGUB		; Did RCPTYP find something interesting?
	 JRST ERCP94
	FINDA A,[A$RHST,,[LISTAR(D)+1]]	;Yes, is host an ITS?
	 JRST [	SKIPN A,PRCPH	; Implicit host?
		 JRST ERCP91	; Local host is always ITS
		JRST .+2 ]
	  MOVE A,LISTAR(A)+1
	CALL NHITS
	 JRST [	HRRZ A,EQRGUB	; Not ITS, discard RCPTYP result
		LNDEL A,
		JRST ERCP94 ]
ERCP91:	HLRZ A,EQRGUB		; ITS, discard original name
	LNDEL A,LISTAR(D)+1
	HRRZ B,EQRGUB		; and insert new name and attributes
	LNAPP [LISTAR(D)+1 ? B]
ERCP94:	FINDA A,[A$RNAM,,[LISTAR(D)+1]]	; Make sure name exists!
	 JRST [	MAKELN A,[0 ? %LTSTR,,[LITSTR [No recipient name was specified? ]]]
		JRST ERCP99]
	HRRZ A,D
	AOSA -5(P)		; Skip on return, and skip over nastiness.

	; Returns here when error seen.
ERCP95:	 MAKELN A,[0 ? %LTSAO,,]	; Put error message into SLN.
ERCP99:	POPAE P,[EQRGUB,E,D,C,B]	; Returns here if error message furnished.
	RET

ERCPBA:	SAOBEG TMPC,		; (ERCP-Bad-Atsign) Shared error routine.
	FWRITE TMPC,[["@" not followed by host name!]]
	JRST ERCP95
SUBTTL EVALA - Input Attrib "evaluation" processing routines

; EVALA - Attribute "Evaluation".  Takes LP in A to a list which it
;	crunches and returns result as LP in A.  For syntax of
;	list, see description of NAMES > format (general version,
;	not specifically rcpt type)

EVALA:	PUSHAE P,[B,C,D]
	SETZ D,			; Initialize LP to accumulated result list.
	TLZA A,-1		; Enter loop, clearing LH.
EVAL40:	 HRRZ A,LISTAR(A)	; Get next LN.
EVAL41:	JUMPE A,EVAL90		; done?
EVAL42:	MOVE B,LISTAR(A)	; get type
	TLNN B,%LTSTR
	 JRST EVAL50		; List! must be (ATTR AVAL).

	; Atomic attribute!  Merely a ATTRIB has been seen.  It implicitly
	; has no arguments.
EVAL45:	CALL SLNUPR		; Make uppercase.
	CALL IARCOD		; Try to match attrib name with internal code.
	 JRST [	MOVEI B,A$ATTR		; If failure, just use general-purpose code.
		DPB B,[$LAFLD,,LISTAR(A)] ; Set it up.
		HRRZ B,LISTAR(A)	; Save CDR
		HLLZS LISTAR(A)		; Before zapping it
		LNAPP [D ? A]		; For appending to result list.
		MOVE A,B		; Now the CDR is start of arg list...
		JRST EVAL41]		; Get another!
	PUSH P,A		; Save SLP
	MOVE C,B
	SETZ A,
	CALL @ATTRIR(C)	; Hack coded attribute, with null arg.
	 JRST [	SUB P,[1,,1]	; Error!
		SAOBEG TMPC,
		FWRITE TMPC,[[Error while processing attribute "],TC,ATTRTB(C),["
	],TLS,A]
		JRST EVAL95]
	JRST EVAL70		; Won, go add result to list.


	;;;;;;;;;;;;;;;;;;;;;;;;;
	;	List Attribute!
	; Comes in form of (ATTRIB VAL VAL VAL ... )
	; If attrib name is known, LP to rest of list is passed along to its
	; input routine and crunched thereby.  If not known, control
	; goes to general-purpose hacking (see A$ATTR part)
EVAL50:	PUSH P,A		; Save LP to LLN holding list attrib.
	HRRZ B,LISTAR(A)+1	; Get LP to list pointed to.
	JUMPE B,[ SETZ A,	; If "( )", ignore this LLN.
		  JRST EVAL70]
	SETZM LISTAR(A)+1	; Clear the sublist ptr.
	MOVE A,B		; A is now only trace to sublist.
	MOVE B,LISTAR(A)	; Find type of first thing on sublist.
	TLNN B,%LTSTR		; Must be atomic...
	 JRST [	SUB P,[1,,1]
		SAOBEG TMPC,
		FWRITE TMPC,[[Attrib name not atomic!]]
		JRST EVAL95]
	CALL SLNUPR		; Make uppercase
	CALL IARCOD		; See if code & input routine exists for this attrib.
	 JRST EVAL60		; Doesn't have one, must use A$ATTR construct.

	; Attrib has code & input routine! Dispatch to process list
	; as desired.  A should have LP to rest of list; processing
	; should return LP in A to crunched list.
	MOVE C,A
	HRRZ A,LISTAR(C)	; Get CDR to next LN as argument list for attrib.
	LNDEL C,		; And flush the SLP for attrib name.
	HRRZ C,B		; Get attrib type into C
	CALL @ATTRIR(C)	; Now process it! Should smash list and return LP in A.
	 JRST [	SUB P,[1,,1]		; Error! reset PDL & restore original LLN ptr.
		SAOBEG TMPC,
		FWRITE TMPC,[[Error while processing attribute "],TC,ATTRTB(C),["
	],TLS,A]
		JRST EVAL95]
	JRST EVAL70		; Done, jump to store returned list.

	; Attrib has no internal code! Must use A$ATTR general-purpose
	; construction. (ugh blech).  For each following item in the list,
	; 2 LN's are generated:
	;              A$ATTR --------> A$AVAL
	;        <name string>    <item string>
	; All item couplets are merely strung together, with the resulting LP in A
	; pointing to the whole slew.  The way it works is that a copy of the
	; attrib-name SLN is inserted in front of each item LN.
EVAL60:	MOVEI B,A$ATTR		; Set up general-purpose code.
	DPB B,[$LAFLD,,LISTAR(A)] ; Make it so.
	PUSH P,A		; Save LP to start of list (and to attrib name)
	HRRZ A,LISTAR(A)	; Get CDR
	JUMPE A,EVAL69		; If no more items, done.
	JRST EVAL67		; Else enter loop.

EVAL65:	MOVE B,(P)		; Aha, an item.  Copy original attrib-name.
	LNCOPY B,[0 ? B]	; (UUO shouldn't ref PDL)
	HRRM B,LISTAR(C)	; Plug onto previous LN,
	HRRM A,LISTAR(B)	; And stick item onto new attrib-name LN.
EVAL67:	MOVEI B,A$AVAL
	DPB B,[$LAFLD,,LISTAR(A)] ; Make item general-purpose attrib value.
	MOVE C,A		; Save old LP
	HRRZ A,LISTAR(C)	; Get CDR to next item (value)
	JUMPN A,EVAL65		; If more items, jump and getum.
EVAL69:	POP P,A			; All items gone!  Restore LP to whole slew.
				; And drop thru to store on accumulated list.

	; Finished attrib-list construction, LP in A to result list.  Must
	; append it to accumulated list, and flush the LLN that held the
	; whole attrib specification.
EVAL70:	POP P,B			; Restore LP to original LLN.
	CAIE A,0		; Skip if nothing returned.
	 LNAPP [D ? A]
	HRRZ A,LISTAR(B)	; Get CDR of LLN we just hacked,
	LNDEL B,		; And flush the LLN.  Bye, bye.
	JUMPN A,EVAL42		; Go munch next. (That is, unless nothing there).
				; In which case drop through to end.
EVAL90:	AOS -3(P)
	SKIPA A,D
EVAL95:	 MAKELN A,[0 ? %LTSAO,,0]
EVAL99:	POPAE P,[D,C,B]
	RET

; Routines are given LP in A to a (possibly nil) list of arguments,
; with internal attribute code in C.  B should be regarded as a
; "free" clobberable AC.

	; APNUL - for attribs that take no arguments.  Simply flushes
	; anything given.
APNUL:	JUMPE A,APNILV		; If nothing there, don't bother.
	LNDEL A			; Flush whole list, and drop thru.

APNILV:	MAKELN A,[0 ? %LTVAL,,0]	; Return nil Value
	CAIA
APNILS:	 MAKELN A,[0 ? %LTSTR,,0]	; Return nil String
	DPB C,[$LAFLD,,LISTAR(A)]	; with proper attrib code.
	AOS (P)
	RET

APOVFL:	MAKELN A,[0 ? %LTSTR,,[LITSTR [Too many arguments for attribute!]]]
	RET

	; APSTR - for attribs that take a single string as argument.
	; Sets SPT to 0 if none given.
APSTR:	JUMPE A,APNILS		; If nothing, return nil string.
	HRRZ B,LISTAR(A)	; Check for only 1 arg...
	JUMPN B,APOVFL		; Jump if too many!
	MOVE B,C		; Set up for routine ordinarily MAPC'd.
	PJRST APSTR5		; Hack it and return...

	; APSTRS - for attribs that lend their code to any number of
	; string arguments.
APSTRS:	JUMPE A,APNILS
	MOVEI B,APSTR5		; Set up addr of rtn to apply (arg in C = attrib code)
	PJRST MAPC		; Apply rtn to given list - sets attrib type to given.

	; Routine for MAPC utilization... to slap attrib type onto atoms.
	; Given LP in A and argument (attrib code) in B.
APSTR5:	HLL A,LISTAR(A)		; Get type.
	TLNN A,%LTSTR		; Check - must be atomic.
	 JRST [	MAKELN A,[0 ? %LTSTR,,[LITSTR [Non-atomic argument for attribute!]]]
		RET]	; If not, return immediately as loss.
	DPB B,[$LAFLD,,LISTAR(A)] ; Else force to given attrib type.
	AOS (P)			; And skip on return.
	RET

	; APEQVL - Equivalence list.  Takes any # of arguments,
	; each of which must be a list of recipient attribs.
APEQVL:	MOVEI B,EQRCPL		; apply this routine to each CAR
	PUSH P,C
	MOVEI C,A$RCP		; And insert this attrib type (not A$EQVL!)
	CALL MAPCA		; And return resulting list, with attrib type stuck in!
	 JRST POPCJ
	JRST POPCJ1

	; APLST - Single List.  Takes 1 arg which is a list,
	; evaluates, and puts attrib type onto single LLN returned.
APLST:	JUMPE A,APNONE
	MOVE B,A		; Save LP to LLN
	MOVE A,LISTAR(A)	; Get 1st wd
	TLNN A,%LTLST		; Make sure a list.
	 PJRST APNOTL
	MOVE A,LISTAR(B)+1	; Get 2nd wd, ptr to list.
	CALL EVALA		; Evaluate it...
	 RET		; Disaster...
	MOVEM A,LISTAR(B)+1	; Restore back
	DPB C,[$LAFLD,,LISTAR(B)]	; Won, make it proper type.
	MOVE A,B
	AOS (P)
	RET

	; APQTL - Quote following stuff and make it a list.
APQTL:	JUMPE A,APNONE		; Need an argument.
	MAKELN A,[0 ? %LTLST,,[A]]	; Make a LLN to rest of list.
	DPB C,[$LAFLD,,LISTAR(A)]
	PJRST POPJ1

	; APRCPT - Recipient.  Takes LP to either a string or list of
	; rcpt attribs and returns LP to a rcpt-LLN.
APRCPT:	JUMPE A,APNONE		; Must have an argument!
	HRRZ B,LISTAR(A)	; And must be only one...
	JUMPN B,APOVFL		; If non-Z, bah - too many.
	CALL EQRCPL		; Parse it...
	 RET			;  Blew it.
	DPB C,[$LAFLD,,LISTAR(A)]	; Won. deposit given attrib type.
	AOS (P)
	RET

	; APNUM - for attribs parsing single string as a number (oct or dec)
APNUM:	JUMPE A,APNILV		; If no arg, return 0.
	HRLM A,(P)		; Save arg on stack.
	MOVE B,LISTAR(A)
	TLNN B,%LTSTR
	 JRST APNOTA		; Make sure it's string.
	TRNE B,-1
	 JRST APOVFL		; If more than 1 arg, barf.
	CALL LIPNUM		; Try parsing...
	 JRST APBNUM		; Indicate bad number.
	MAKELN A,[0 ? %LTVAL,,[A]]
	DPB C,[$LAFLD,,LISTAR(A)]
	HLRZ B,(P)
	LNDEL B,
	AOS (P)
	RET

	; AP2NUM - Each item is a list of 2 numbers...
AP2NUM:	MOVEI B,AP2NM		; Routine to apply...
	PJRST MAPCA

AP2NM:	MOVE B,LISTAR(A)	; Make sure arg is list
	TLNN B,%LTLST
	 PJRST APNOTL
	HRLM A,(P)		; Save LP in LH on stack.
	MOVE A,LISTAR(A)+1	; Get LP to first
	MOVE B,LISTAR(A)
	TLNN B,%LTSTR
	 PJRST APNOTA		; Not a string...
	CALL LIPNUM		; Parse as #
	 PJRST APBNUM		; Ugh.
	EXCH B,A
	HRRZS A
	JUMPN A,[CALL LIPNUM		; Parse 2nd too.
		 PJRST APBNUM
		JRST .+1]
	MAKELN A,[0 ? %LTVAL,,[A]]	; LN-ify 2nd.
	MAKELN A,[0,,[A] ? %LTVAL,,[B]]	; and now 1st.
	MAKELN A,[0 ? %LTLST,,[A]]	; And return list.
	HLRZ B,(P)		; Recover original LP,
	LNDEL B,		; And flush the list of strings.
	AOS (P)
	RET

APNOTL:	MAKELN A,[0 ? %LTSTR,,[LITSTR [Argument not list!]]]
	RET
APBNUM:	MAKELN A,[0 ? %LTSTR,,[LITSTR [Bad numerical arg!]]]
	RET
APNOTA:	MAKELN A,[0 ? %LTSTR,,[LITSTR [Argument not atomic!]]]
	RET
APNONE:	MAKELN A,[0 ? %LTSTR,,[LITSTR [No argument furnished!]]]
	RET

	; AP6W - for attribs that want a single string arg converted to
	; one SIXBIT word value.
AP6W:	JUMPE A,APNILV		; Return 0 if no arg
	MOVE B,LISTAR(A)
	TLNN B,%LTSTR		; Make sure it's string.
	 JRST APNOTA		; Foo.
	TRNE B,-1
	 JRST APOVFL		; Make sure only 1 arg.
	MOVE B,A		; Save LP in B
	CALL CVLS6
	LNDEL B,		; Flush old SLN
	MAKELN A,[0 ? %LTVAL,,[A]]
	DPB C,[$LAFLD,,LISTAR(A)]
	AOS (P)
	RET

	; APHST - For attribs (i.e. SITE) that take arg as being
	; a site spec.  (can have "@" in front)
APHST:	JUMPE A,APNONE		; Error if nothing given.
	MOVE B,LISTAR(A)
	TLNN B,%LTSTR		; It must be an atom.
	 JRST APNOTA
	TRNE B,-1		; Only one argument is allowed.
	 JRST APOVFL
	MOVE B,A
	CALL HMATCH		; Try...
	 JRST [	MAKELN A,[0 ? %LTSTR,,[LITSTR [Bad SITE spec!]]]
		RET]
	LNDEL B,		; Win.  The host number is in A.
	MAKELN A,[0 ? %LTVAL,,[A]]
	DPB C,[$LAFLD,,LISTAR(A)]
	AOS (P)
	RET


; RCPTYP - special subroutine that, given SLP in A to
;	a recipient-name, tries to figure out if it should
;	be given a special TYPE.  If so, returns in B a
;	LP to a new R-NAME LN, linked to its proper R-TYPE LN. (ie 2 element list)
;	Does NOT mung original SLN!
;	Must check for these: 1) BUG-xxx 2) *name 3) [file]
RCPTYP:	PUSHAE P,[A,C,D]
	MOVE C,LISTAR(A)+1	; Get SPT
	ADD C,$LSLOC(L)		; Make abs.
	HLRZ D,C		; Get cnt
	HRLI C,440700		; Make BP
	JUMPE D,RCPTY8		; nil string?
	ILDB B,C		; Get 1st char
	CAIE B,"*		; *MSG ?
	 JRST RCPTY3		; Nope

	; Handle *-recipient.
	MAKELN B,[A$RTYP,,0	; Set appropriate type.
		%LTSTR,,[LITSTR [*MSG]]]
	JRST RCPTY7		; And go create LN copy of name.

	; Check for [<filename>] recipient.
RCPTY3:	CAIE B,"[		; A [file] ?
	 CAIN B,"{		; or {file}?
	  CAIA			; yep
	   JRST RCPTY5		; nope
	MAKELN B,[A$RTYP,,0	; Yep, set type to FILE.
		%LTSTR,,[LITSTR [FILE]]]
	JRST RCPTY7		; and finalize.

	; Check for BUG-X recipient.
RCPTY5:	CAIG D,4		; Enough chars to have a "BUG-X" in there?
	 JRST RCPTY8		; Nope, it's a plain regular name after all.
IRPC CH,,[BUG]
	CAIE B,"CH
	 CAIN B,40+"CH		; Add 40 bit for lowercase.
	  CAIA
	   JRST RCPTY8
	ILDB B,C
TERMIN
	CAIE B,"-
	 JRST RCPTY8

	; It's a BUG-X!!
	HRLZI D,-4(D)		; Put updated cnt in LH
	HRRI D,C		; And addr of BP in RH
	;; (Following instructions are in this order to avoid getting 
	;; screwed by area relocation.)
	MAKELN C,[A$RNAM,,0	; Slurp into name SLN
		%LTBPT,,[D]]
	MAKELN B,[A$RTYP,,0	; Now create TYPE for it
		%LTSTR,,[LITSTR [BUG]]]
	HRRM B,LISTAR(C)	; Cons type onto name SLN
	MOVE B,C		; And return LP in B to pair
	JRST RCPTY9

RCPTY7:	LNCOPY C,[0 ? A]	; Copy name
	HRRM B,LISTAR(C)	; Link TYPE to it
	SKIPA B,C		; And return LP in B.
RCPTY8:	 SETZ B,
RCPTY9:	POPAE P,[D,C,A]
	RET

; RCPHST - Parse Recipient and Host
;   A/ SLP to rcpt name 
;
; Tries to parse as "Foo@Site".
; If host turns out to be us, checks the recipient name
; for percent-sign (%) delimiter in case "Foo%Bar@ThisSite".
;
; Returns <# chars in name> in C, <site #> in B, and skips.  
; If it couldn't be parsed, skips but returns 0 in B.  
; Fails to skip if it parsed correctly and there was no such host.
; Garbage in C in latter two cases.
; Also have to strip our own name off the end, can be there sometimes.
;
;  Due to bad modularity this also has to look at PRCPH which if non-zero
;  is an implicit host that has not yet been put into the data structure.

LVAR	RCPHS%:	0		; Nonzero when parsing "local part".

RCPHST:	PUSHAE P,[A,D,E]
	SETZM RCPHS%		; Initially try parsing the "domain part".
RCPH10:	MOVE B,LISTAR(A)+1
	ADD B,$LSLOC(L)		; B has ASCNT to string to parse.
	HLRZ C,B
	JUMPE C,RCPH85		; If zero length, can't parse.
	SKIPE A,PRCPH		; See if host number already known another way
	 JRST RCPH80		; Don't look for % in address at foreign host
	MOVE E,C		; Original length
	HRLI B,440700
	PTSKIP C,B		; B is now a BP to the end of the string.
	CALL RCPH40		; See a host there?
	 JRST [	SKIPE RCPHS%	;  No, so there is only a "local part".
		 JRST RCPH85	;   If already did it, return 0.
		SETOM RCPHS%	;  Else reparse entire string as local part.
		MOVE A,-2(P)	;  Recover SLP and go reparse.
		JRST RCPH10 ]
	CAMN A,OWNHS2		; If this our alternate address
	 MOVE A,OWNHST		;  mutate it into our real address.
	CAME A,OWNHST		; Is this host us?
	 JRST RCPH70		;  No, all done.
	MOVE E,C		; We are the "domain part" host.
	SETOM RCPHS%		; Say we are parsing the "local part" now.
	CALL RCPH30
	 JRST [	MOVE C,E	;  Nothing else in local part, host is just us.
		MOVE A,OWNHST	;  Return our own host.
		JRST RCPH80 ]
RCPH70:	D7BPT B			; Strip possible blanks before host name
	LDB D,B
	CAIN D,40
	 SOJG C,RCPH70
RCPH80:	SKIPA B,A		; Return winning site number in B.
RCPH85:	 SETZ B,		;  Else return 0 as site if no site found.
	AOS -3(P)		; Skip.
	POPAE P,[E,D,A]		; Don't skip if parsed bad host name.
	RET

RCPH30:	D7BPT B			; Search backwards for @.
RCPH40:	LDB D,B
	CAIN D,40		; First skip backwards over spaces.
	 SOJG C,RCPH30
	JUMPLE C,APOPJ		; No non-blanks.
	SKIPA E,C		; Strip trailing blanks.
RCPH50:	 D7BPT B		; Now search for host delimiter.
	LDB D,B
	CAIN D,"@
	 JRST RCPH20
	SKIPE RCPHS%		; Parsing the local part yet?
	 CAIE D,"%		;  Yes, so can grok "%" here.
	  CAIA			; Don't parse them to left of the "@".
	   JRST RCPH20		;   Okay, kludgey relay feature.
	SOJG C,RCPH50
	RET			; No host present

RCPH20:	MOVE A,E
	SUBI A,(C)		; A has number of chars in SITE name.
	SUBI C,1		; C has number of chars in NAME.
				; And B has the BP to the site name.
RCPH60:	ILDB D,B		; Strip leading spaces
	CAIN D,40
	 SOJA A,RCPH60
	JUMPLE A,CPOPJ		; Fail if no host name present
	D7BPT B
	CALL HMATC		; Call host-name analyze rtn
	 JRST RPCH65		; Looks like a host name, but not one we know
	AOS (P)			; Success return
	RET

RPCH65:	SKIPN A,DOMGAT		; Send through domain-aware gateway
	 RET			; Just fail if no gateway
	MOVE C,E		; Length includes the host name
	JRST POPJ1		; Success

LVAR DOMGAT:	0		; Non zero to relay on unknown hostnames

SUBTTL HEADER PARSING

.VECTOR HPTOK(2),SVTOK(2)	;Current and previous token in %LTBPT form

;Parse header embedded in message text, if any, into A$SNM, A$SNH, and A$SBJ
;attributes.  This only parses the most common headers, not all headers.
;Skips if it succeeds in finding a sender
HPARSE:	PUSHAE P,[A,B,C,D,E]	;First set up $LSRPT(L) and $LSCHL(L)
	FINDA B,[A$MTXT,,[$LLLST(L)]]	; to the message text, if any.
	 JRST HPARS9		;No text
	MOVE B,LISTAR(B)+1
	HLRZ A,B		;Length
	ADD B,$LSLOC(L)
	HRLI B,440700		;Byte pointer
	MOVEM B,$LSRPT(L)	;Put where will be relocated by UABUMP
	MOVEM A,$LSCHL(L)
HPARS1:	CALL HPTOKN		;Get next token
	CAIN B,15		;Skipping leading blank lines
	 JUMPE A,HPARS1
	CAIN B,":		;Colon => NET or RFC733 header
	 JRST HPARS4
	CAIE B,"@		;Atsign => ITS header
	 JRST HPARS9		;Otherwise not parseable
	CALL SVTOKN		;Save sender name
	CALL HPHOST		;Host should be next
	 JRST HPARS9		;Garbage, cannot parse
	MAKELN B,[A$SNM,,[$LLLST(L)]	;Store sender
		  %LTBPT,,[SVTOK]]
	MAKELN A,[A$SNH,,[B]	;Put sender's host on
		  %LTVAL,,[A]]
	MOVEM A,$LLLST(L)
HPARS2:	CALL HPTOKN		;Look for subject
	CAIN B,15
	 JRST HPARS8		;End of line, no subject
	JUMPL B,HPARS8		;End of message, no subject
	CAMN A,[SIXBIT/RE/]
	 CAIE B,":
	  JRST HPARS2		;Some other token (part of date or sent-by)
	CALL HPARS7		;Gobble subject
HPARS8:	AOS -5(P)		;Success return
HPARS9:	POPAE P,[E,D,C,B,A]
	RET

;Network and RFC733 header processing
HPARS4:	CAMN A,[SIXBIT/SUBJEC/]
	 JRST [	CALL HPARS7	;Swallow subject
		JRST HPRS5A ]	;Check next line
	CAME A,[SIXBIT/FROM/]
	 JRST HPARS5		;Some other attribute, ignore it
	;; This is very simple-minded.  We only recognize token AT host
	;; and don't even look for angle brackets and parentheses.
	;; It works for all the commonly-sent header types other than
	;; screw cases such as people named "At".
	CALL HPTOKN		;Expecting user name
HPARS6:	CAIE B,15		;If end of line reached, die
	 CAIGE B,0		;Or end of message
	  JRST HPRS5B
	CALL SVTOKN		;Save putative sender name
	CALL HPSKSP		;Skip spaces, find real delimiter
	CAIN B,"@		;Some hosts may send @ rather than AT
	 JRST HPRS6B
	CALL HPTOKN		;Look for "at" as next word
	CAME A,[SIXBIT/AT/]
	 JRST HPARS6		;Well, maybe this was a sender name
HPRS6B:	CALL HPHOST		;Is next token a valid host name?
	 JRST HPARS6		;No, perhaps it is a sender name then
	CAMN A,OWNHST		;Is the host me?
	 CALL HPRS6C		;If so, recognize kludgey %
	MAKELN C,[A$SNM,,[$LLLST(L)]	;Store sender
		  %LTBPT,,[SVTOK]]
	MAKELN A,[A$SNH,,[C]	;Put sender's host on
		  %LTVAL,,[A]]
	MOVEM A,$LLLST(L)	;Ho, ho!  Got sender, keep looking for subject
	CAIN B,15
	 JRST HPRS5A
HPARS5:	SOSGE $LSCHL(L)		;Swallow remainder of line
	 JRST HPRS5B		;End of message
	ILDB A,$LSRPT(L)
	CAIE A,15
	 JRST HPARS5
HPRS5A:	CALL HPTOKN		;Get first token on next line
	CAIN B,":
	 JRST HPARS4		;Go analyze it if it is an attribute
HPRS5B:	FINDA A,[A$SNH,,[$LLLST(L)]]	;Done
	 JRST HPARS9		;Failed
	JRST HPARS8		;Succeeded

; Address in header turned out to be on my host.  Probably it is really
; some other host indirecting through me with the goddamn kludgey percent.
; SVTOK has the sender, A has the host (equal to OWNHST), B has delimiter
; This only allows a single level of %.  Tell it to the marines!
HPRS6C:	PUSH P,B
	HLRZ A,SVTOK		;Number of characters
	MOVE B,SVTOK+1		;Byte pointer
	ILDB C,B		;Search for %
	CAIE C,"%
	 SOJG A,.-2
	SOJLE A,HPRS6E		;Jump if not found
	HLRZ C,SVTOK		;Compute number of characters before %
	SUBI C,1(A)
	CALL HMATC		;Rest of string a host name?
HPRS6E:	 SKIPA A,OWNHST		;Nope, host is still me
	  HRLM C,SVTOK		;Yes, shorten token
	POP P,B
	RET

;Remainder of current line is subject
HPARS7:	MOVE B,$LSRPT(L)	;Byte pointer to start of subject
	MOVEI C,0		;Character count
HPRS7A:	SOSGE $LSCHL(L)
	 JRST HPRS7B
	ILDB A,$LSRPT(L)
	CAIE A,15
	 AOJA C,HPRS7A
HPRS7B:	MOVSS C
	HRRI C,B
	MAKELN A,[A$SBJ,,[$LLLST(L)] ? %LTBPT,,[C]]
	MOVEM A,$LLLST(L)
	RET

;Save the current token into SVTOK
SVTOKN:	PUSH P,A
	MOVE A,HPTOK
	HRRI A,SVTOK+1
	MOVEM A,SVTOK
	MOVE A,HPTOK+1
	MOVEM A,SVTOK+1
	JRST POPAJ

;Parse next token, if it is a host skip and return host number in A.
;Return delimiter in B.
HPHOST:	CALL HPTOKN
	PUSH P,B		;Delimiter
	HLRZ A,HPTOK		;Number of characters
	MOVE B,HPTOK+1		;Byte pointer
	CALL HMATC		;Is it a host name?
	 CAIA			;No
	  AOS -1(P)		;Yes, skip
	POP P,B			;Delimiter
	RET

;Parse next token, skipping spaces and line feeds.  Return sixbit in A,
;delimiter character in B (negative at end of text), and %LTBPT pointer
;(which becomes invalid if you cons) in HPTOK.  Valid characters in tokens
;are everything except space, colon, atsign, angle bracket, parenthesis,
;and controls (especially carriage return).
;Doesn't know about quoted strings, for instance.
HPTOKN:	PUSHAE P,[C,D]
	MOVEI A,0		;Blank sixbit
	MOVE C,[440600,,A]
	SETZM HPTOK		;No characters
HPTOK1:	SOSGE B,$LSCHL(L)	;First skip spaces and line feeds
	 JRST HPTOK9		;End of message, no token
	MOVE B,$LSRPT(L)	;In case token starts here
	MOVEM B,HPTOK+1
	ILDB B,$LSRPT(L)	;Next character
	CAIE B,40
	 CAIN B,12
	  JRST HPTOK1
HPTOK2:	CALL HPDELP
	 JRST HPTOK9		;Delimiter ends token
	AOS HPTOK		;Okay, this is part of token
	CAIL B,140		;This loop accumulates a token
	 SUBI B,40		;Upper case
	SUBI B,40		;Sixbit
	TLNE C,770000
	 IDPB B,C
	SOSGE B,$LSCHL(L)
	 JRST HPTOK9		;End of message, end of token
	ILDB B,$LSRPT(L)	;Next character
	JRST HPTOK2

HPTOK9:	HRLZ C,HPTOK		;Length of token
	HRRI C,HPTOK+1
	MOVEM C,HPTOK
	POPAE P,[D,C]
	RET

;Skip if character in B is not a delimiter
HPDELP:	CAIE B,"<
	 CAIN B,">
	  RET
	CAIE B,"(
	 CAIN B,")
	  RET
	CAIE B,"@
	 CAIN B,":
	  RET
	CAILE B,40
	 AOS (P)		;Not a delimiter
	RET

;Delimiter is in B.  If it's space, find real delimiter.
;For hosts that send space-atsign-space.
HPSKSP:	CAIN B,40		;Original delimiter a space?
	 SOSGE $LSCHL(L)	;More characters?
	  RET		;No, stop
	ILDB B,$LSRPT(L)	;Next character
	CAIN B,40
	 JRST HPSKSP		;Space, skip it
	CALL HPDELP		;Other delimiter?
	 RET		;Yes, return him
	AOS $LSCHL(L)		;Delimiter really space, back up
	MOVSI B,070000
	ADDM B,$LSRPT(L)
	MOVEI B,40
	RET

;Skip if this is net-mail-from-host with no discernible sender
;Returns host number sent from in A if it skips
SKPNMH:	FINDA A,[A$SNH,,[$LLLST(L)]]
	 FINDA A,[A$NMH,,[$LLLST(L)]]
	  RET		;Had parsed header or is local mail
	MOVE A,LISTAR(A)+1	;Get host number
	JRST POPJ1

SUBTTL MAPCAR and friends

; MAPCAR - Takes LP in A to a list, address in B of a routine to
;	apply to each CAR in succession, and returns in A an LP to
;	list of the results.  Skips on succesful completion and
;	returns immediately w/o skip if an apply fails.
; MAPCA - Like MAPCAR but C should contain an attrib code, which each
;	LN of the returned list is smashed to.
; MAPC - Like above, but returns nothing; merely executes given
;	routine for each thing on list.  C should contain an "argument"
;	which is given to the called routine in B.
;		Routines called are given LP in A to a single LN of
;	the list, and must return LP in A to resulting LN, and skip
;	on successful return.  B and C can be clobbered, but any
;	other ACs should be saved.  Routines for MAPC need not return anything.

MAPCAR:	TRZA F,%MAPC		; Set flag indicating which variety of MAPCAR.
MAPCA:	 TRO F,%MAPC
	PUSHAE P,[B,C,D,E]	; B and C args saved on stack.
	HRRZS A			; LP to input list given in D.
	JUMPE A,MAPCA8		; If nothing in input, done - return nil ptr.
	HRRZ D,LISTAR(A)	; Get CDR in prep for next pass.
	HLLZS LISTAR(A)		; Isolate arg.
	CALL @-3(P)		; Do first one.
	 JRST MAPCA7		; Error! Pass it on.
	MOVE E,A
	PUSH P,E		; Save as ptr to first LN of result list.
	JRST MAPCA4		; Jump into loop.

MAPCA2:	HRRZ D,LISTAR(D)	; Get CDR in prep for next loop.
	HLLZS LISTAR(A)		; Isolate argument.
	CALL @-4(P)		; Execute routine.
	 JRST MAPCA9		; If error, pass on.
	JUMPE A,MAPCA9		; Make sure something returned.
	HRRM A,LISTAR(E)	; Link result list to new LN.
	HRRZ E,A		; and make new LN last one.
MAPCA4:	TRNE F,%MAPC		; Should the LN's attrib-type get clobbered?
	 JRST [	MOVE B,-3(P)	; Yes, get attrib
		DPB B,[$LAFLD,,LISTAR(E)] ; And set it.
		JRST .+1]
	MOVE A,D		; Now get next LN
	JUMPN A,MAPCA2		; And process if it's there,
	POP P,A			; Else return LP to result and fall thru to end.

MAPCA8:	AOSA -4(P)
MAPCA9:	 SUB P,[1,,1]		; If error, flush LP to result from stack.
MAPCA7:	POPAE P,[E,D,C,B]
	RET

MAPC:	PUSHAE P,[A,B,C,D]
	HRRZ D,A		; LP to input list kept in D.
	JUMPE D,MAPC8		; If nothing in input, done.
MAPC2:	HRRZ D,LISTAR(D)	; Get CDR in preparation for next loop.
	MOVE B,-1(P)		; Set up arg to routine.
	CALL @-2(P)		; Execute routine...
	 JRST MAPC9		; If error, pass it on.
	SKIPE A,D		; Now get LP to next LN, skip if none.
	 JRST MAPC2		; crunch it.
MAPC8:	AOS -4(P)
MAPC9:	POPAE P,[D,C,B,A]
	RET


SUBTTL RLPAS, HMATCH,and RPARNM parsing routines.
IFN 0,[
; These routines are not used now because it turned out possible to use 
; EVALA and EQRCPL.  But the code should be kept around anyway...

; RLPARS - takes a BP in RLPARG, and # of chars in A, and
;	parses it as rcpt list in usual form name@site,name1@site1...
;	Returns in A the LN ptr to resulting rcpt list, and skips.
;	(no skip if parse error)
;	slightly odd in that <item> without a @<site> is interpreted
;	as <site> and not <name>@<local site> as QMAIL would do.
;	B contains LN ptr to name string when called from RCPP80.

RLPCNT:	0			;char cnt for the ptr
RLPARG:	0			;contains addr of ptr; indirected thru this.

RLPARS:	PUSHAE P,[B,C,D]
	SETZ D,			;D maintains ptr to whole list
RLPAR1:	CALL RPARNM		;read name up to "@" or "," or EOF
	 JRST RLPAR9		;error?
	JUMPE A,RLPAR8		;eof?
	MOVE C,A		;save ptr to name LN
	CAIN B,"@		;terminated by @?
	 JRST RLPAR2		;yes, go munch rest as site name
	MOVE C,-2(P)		;no rcpt name specified, use that given in B
	LNCOPY C,[0 ? C]	; (can't make ref to PDL)
	JRST RLPAR3		;and skip further gobbling of text

RLPAR2:	CALL RCOM		;read up to comma or EOF into a SLN (LP in A)
	 JRST RLPAR5		;nothing there, assume local site.
RLPAR3:	MOVE B,A		;save LP to sitename LN
	CALL HMATCH		;try to match it
	 JRST RLPAR9		;lost, couldn't parse as site name/number
	LNDEL B,		; A site! free up sitename SLN.
	CAMN A,OWNHST		; If site is local, jump
	 JRST RLPAR5		; and don't form
	MAKELN A,[A$RHST,,0	; a site-# LN.
		%LTVAL,,[A]]
	CAIA			; Now skip into rest of stuff

RLPAR5:	 SETZ A,		;clear LP to site-# LN if no site specified
	HRRM A,LISTAR(C)	;cons site-# with rcpt name
	JUMPE A,[EXCH A,C	;if local, must force name to uppercase.
		CALL SLNUPR	;smash string.
		EXCH A,C
		JRST .+1]
	MAKELN D,[A$RCP,,[D]
		%LTLST,,[C]]	;and put under a LLN, and cons onto whole list.
	JRST RLPAR1		;now go get another in list if any.

RLPAR8:	MOVE A,D		;return ptr to whole list
	AOS -3(P)
RLPAR9:	POPAE P,[D,C,B]
	RET

; RPARNM - takes and returns char cnt in RLPCNT, BP in RLPARG, reads up
;	to "@" or "," or eof into a SLN.  Flushes any spaces before
;	or after text.  Space within text, or odd chars, is error signaled by non-skip.
;	win return skips with ptr in A to the LN, break char (,@^c) in B.
; RCOM - as above, but doesn't break on @.

.SCALAR RPARNT,RPARSW

RCOM:	SETOM RPARSW	;switch to not break on @
	JRST .+2
RPARNM:	 SETZM RPARSW	;do break on @.
	PUSHAE P,[C,D]
	SKIPG B,RLPCNT
	 JRST RPARN4		;null string
RPARN1:	ILDB C,RLPARG
	CAIE C,40	;flush spaces
	 CAIN C,^I	;or tabs
	  SKIPA
	   JRST RPARN2	;not space or tab
	SOJG B,RPARN1
RPARN4:	SETZM RLPCNT
	SETZ A,
	JRST RPARN7	;null string
RPARN2:	D7BPT RLPARG
	MOVE D,RLPARG
	MOVEM D,RPARNT	;save--ptr to start.
	MOVE D,B	;save cnt
RPARN3:	ILDB C,RLPARG
	CAIN C,",	; break if separator
	 JRST [	SUBI D,(B)
		SOJA B,RPARN5]		;reached terminating char
	SKIPE RPARSW	;skip if checking @ too
	 JRST .+3	;nope
	CAIN C,"@
	 JRST [	SUBI D,(B)
		SOJA B,RPARN5]
	SOJG B,RPARN3
	IBP RLPARG	;so d7bpt will win with both types of endings
RPARN5:	MOVE A,RLPARG	;get for backwards look
	MOVEM B,RLPCNT	;store back cnt
	MOVE B,C	;save char broken on
RPARN6:	D7BPT A		;decrement ptr
	LDB C,A		;get char pted to
	CAIE C,40
	 CAIN C,^I
	  SKIPA
	   JRST RPARN8	;done, finalize.
	SOJG D,RPARN6	;else loop til all terminating blanks flushed
	JRST RPARN9	;null string, lose
RPARN8:	HRLZ C,D	;put cnt in lh
	HRRI C,RPARNT	;addr of ptr in rh
	MAKELN A,[A$RNAM,,0
		%LTBPT,,[C]]
RPARN7:	AOS -2(P)
RPARN9:	POPAE P,[D,C]
	RET
]; end of ifn 0,

; HMATCH - takes ptr to SLN in A, tries to match up as a network
;	host name or number. Skips and returns site # in A if successful,
;	else doesn't skip.  Is OK to have @ preceding name/number.
; HMATC same but takes cnt in A, BP in B directly. and skips @ test.

HMATC:	PUSHAE P,[B,C]
	JRST HMATC0
HMATCH:	PUSHAE P,[B,C]
	MOVE B,LISTAR(A)+1	;get SPT
	HLRZ A,B		; Put cnt in A
	ADD B,$LSLOC(L)		; Make addr absolute,
	HRLI B,440700		; set up BP in B,
	ILDB C,B		; Test first char
	CAIN C,"@
	 SOSA A			; If it's there, decrement cnt.
	  HRLI B,440700		; If not, restore BP.
HMATC0:	JUMPLE A,HMATC9		; Null host name is a loser (:BUG @)
	PUSHAE P,[A,B]		; Save in case IPNUM fails.
	CALL IPNUM
	 JRST HMATC5		; Failed.
	CALL RESOLV"STDHST	; Won, standardize host #.
	SUB P,[2,,2]		; If numerical parse won, flush stack & return.
	JRST HMATC8
HMATC5:	POPAE P,[B,A]
	CALL HANLYZ		; and go analyze.
	 CAIA			; Foo?
HMATC8:	  AOS -2(P)		; Hurray!
HMATC9:	POPAE P,[C,B]
	RET

SUBTTL *--------- Mailing -----------*
SUBTTL Special-Request processing routines

	; All stuff on this page is preliminary!!!
COMMENT |
*** NEW SCHEME ***
	(SPECIAL-PROC CMD ARG1 ARG2 ARG3 ... )
A$PREQ is LLN to a list,
	1st thing on list is a SLN specifying command.
	Succeeding things on list are arguments.

	Arguments to special-processing routines are furnished by
A$PARG.  This attribute may be either a string or a list containing
several arguments, as returned by EVALA - thus a fully parsed
A$RCP may be in this argument list.  Note that if it is desired to
have arguments that correspond to no existing attribute, these
must be distinguished by position in the list and QUOTE used to prevent
"evaluation". 
	For example:

((SPECIAL-PROC DELETE) (SPECIAL-ARG FOO))
			; A$PARG is the string "FOO".
((SPECIAL-PROC DELETE)
 (SPECIAL-ARG ((MESSAGE-ID 123456)
		(QUOTE (THIS IS QUOTED LIST)) )) )
		; A$PARG is a list containing one A$ID string and one A$QUOT
		; list which is the 2nd thing specified.
|

; SPCPRC - Special-Processing execution vector.
;	A - LP to A$PREQ type list
; Returns like routines it invokes.

SPRXCT:	PUSHAE P,[B,C]
	MOVE B,LISTAR(A)	; Get 1st word
	TLNN B,%LTLST		; Confirm that it's a list!
	 JSR AUTPSY		; No, Yeech!
	MOVE A,LISTAR(A)+1	; Okay, get list it points to.
	STAT (,("Note: Special req:"),C(42),TLS(A),C(42))
	MOVSI B,-NSPRQS
SPRXC1:	HLRZ C,SPRQTB(B)	; Get addr to a spec-req name
	USLNEA A,(C)		; Uppercase compare
	 AOBJN B,SPRXC1
	JUMPGE B,[STAT (,("Note: Above request is UNKNOWN; assumed bad."))
		MAKELN A,[0 ? %LTSTR,,[LITSTR [Unknown special request.]]]
		JRST SPRXC9]	; Error if can't identify.
	HRRZ B,SPRQTB(B)	; Aha, found it! Get routine addr
	HRRZ A,LISTAR(A)	; and set up ptr to arg list.
	CALL (B)		; and call it!
	 JRST SPRXC9		; Lossage, return whatever A contains.

	JUMPE A,SPRXC8		; Skip text mung if nothing to use.
	FINDA B,[A$MTXT,,[$LLLST(L)]]	; Any existing msg text?
	 CAIA
	  LNDEL B,$LLLST(L)	; Delete existing msg text.
	MOVEI B,A$MTXT
	DPB B,[$LAFLD,,LISTAR(A)]	; Use request results as new msg text.
	MOVE B,$LLLST(L)
	HRRM B,LISTAR(A)	; Cons it onto list.
	MOVEM A,$LLLST(L)
	FINDA A,[A$SBJ,,[$LLLST(L)]]	; Has any subject?
	 JRST [	MAKELN A,[A$SBJ,,[$LLLST(L)]	; If not, make one.
			%LTSTR,,[LITSTR [Request results]]]
		MOVEM A,$LLLST(L)
		JRST .+1]
	FINDA A,[A$RCP,,[$LLLST(L)]]	; Has any rcpts?
	 JRST [	CALL SNMRCP		; Nope, make rcpt out of sender name.
		 JSR AUTPSY		; Should have checked previously!
		MOVE B,$LLLST(L)	; Cons it onto msg list.
		HRRM B,LISTAR(A)
		MOVEM A,$LLLST(L)
		JRST .+1]

SPRXC8:	AOS -2(P)	; Win return, A non-zero if should send a msg.

SPRXC9:	POPAE P,[C,B]
	RET

	; Dispatch table identifying known special-processing routines
	; and their addresses.
SPRQTB:	[ASCNT [TEST]],,SPRTST
	[ASCNT [SHOW-Q]],,SPRSHQ
	[ASCNT [SHOW-MSG]],,SPRSHM
	[ASCNT [KILL-MSG]],,SPRKMG
NSPRQS==.-SPRQTB

; Special-processing routines.  All are given a single argument in A which is
; a LP to the 1st argument in a list of arguments,
; or zero if none exists.  All must skip if "successful";
; a failure return implies that an error message is waiting to be returned to
; originator of request. (actually, this may turn out not to be right thing)

; Possible outcomes:
; No skip:
;	Return error msg to sender (A if non-zero has LP to text)
; Skip:
;	If A zero, that's all.
;	Else return results to recipients
;		(include sender if no rcpts specified?)

SPRTST:	MAKELN A,[0 ? %LTSTR,,[LITSTR [This is result of A$PREQ "TEST".]]]
	PJRST POPJ1

	; "Show Queue" - Currently ignores arguments.
SPRSHQ:	MAKELN A,[0 ? %LTSAO,,[[OUTCAL(,CALL(PRTQML))]]]
	PJRST POPJ1	; Howja like them super instructions???!!

	; "Show Message" - takes message-ID argument.
SPRSHM:	JUMPLE A,POPJ1	; If no arg, just do nothing.
	OUT(,CH(TMPC),OPEN(UC$SAO))
	UARPUSH MSGAR
	CALL MSGGET
	 JRST [	OUT(,("Can't retrieve message"))
		JRST SPSHM7]
	; Output msg stuff here.
	PUSH P,L
	MOVE L,$ARLOC+MSGAR	;Use new LSE for FINDAs.
SPRKM1:	FINDA A,[A$MHDR,,[$LLLST(L)]]
	 JRST [	OUT(,("No message header for message!!"))
		JRST SPSHM7]
	OUT(,SL(A))
	FINDA A,[A$MTXT,,[$LLLST(L)]]
	 JRST [	OUT(,("No text for message!!"))
		JRST SPSHM7]
	OUT(,SL(A))
	POP P,L

	AOS (P)
SPSHM7:	UARPOP MSGAR
	MAKELN A,[0 ? %LTSAO,,]
	RET

	;Kill Message
SPRKMG:	JUMPLE A,POPJ1	; If no arg, just do nothing.
	OUT(,CH(TMPC),OPEN(UC$SAO))
	UARPUSH MSGAR
	CALL MSGGET
	 JRST [	OUT(,("Can't retrieve message"))
		JRST SPSHM7]
	PUSH P,L
	MOVE L,$ARLOC+MSGAR
	;Change A$RCP to A$RCPF
	MOVE A,$LLLST(L)
	MOVEI B,SPRKM2
	CALL MAPC
	 JSR AUTPSY
	MOVEI A,MF%SNT+MF%QUD+MF%FAI+MF%KIL
	CALL MSGFIN		;Send failure notification
	CALL MSGDEL		;Get rid of message
	CALL MGQDEL		;Remove from queue
	EXCH L,(P)
	OUT(,CH(TMPC),OPEN(UC$SAO))	; when it sees there are no rcpts left
	OUT(,("Message deleted:"),CRLF())
	EXCH L,(P)
	JRST SPRKM1		;Return copy of message

SPRKM2:	LDB B,[$LAFLD,,LISTAR(A)]	; Get attrib
	CAIE B,A$RCP
	 JRST POPJ1
	MOVEI B,A$RCPF
	DPB B,[$LAFLD,,LISTAR(A)]
	TLO F,%MSGMD
	PJRST POPJ1

;Delete current message from all queue entries it may be on
MGQDEL:	PUSHAE P,[A,B,C,D,E,L]
	SKIPN $AROPN+QMLAR		; Check, and
	 CALL QMLGET			; make sure that QML is loaded.
	MOVE L,$ARLOC+MSGAR		; Set to MSG-LSE
	FINDA A,[A$ID,,[$LLLST(L)]]	; Find MSG-ID
	 JSR AUTPSY
	MOVE E,LISTAR(A)+1		; Get SPT (add $LSLOC to make ASCNT ptr)
	MOVE L,$ARLOC+QMLAR		; Select QML, search all site lists
	HRRZ D,$LLLST(L)		; Get LP to first LLN
	JUMPE D,MGQDL9			; List exhausted
MGQDL1:	MOVE C,LISTAR(D)+1		; get LP to site's list.
MGQDL2:	HRRZ C,LISTAR(C)		; List of message IDs
	JUMPE C,MGQDL4			; End of list
MGQDL3:	LDB A,[$LAFLD,,LISTAR(C)]
	CAIE A,A$ID
	 JRST MGQDL2			; Not an ID, try next
	MOVE B,$ARLOC+MSGAR
	MOVE B,$LSLOC(B)
	ADD B,E				; ASCNT to ID of message being killed
	SLNEA C,B
	 JRST MGQDL2			; Not right ID, try next
	HRRZ B,LISTAR(C)
	TLO F,%QMLMD
	LNDEL C,LISTAR(D)+1		; Remove this from list
	MOVE C,B
	JUMPN C,MGQDL3
MGQDL4:	FINDA A,[A$ID,,[LISTAR(D)+1]]	; Any messages left?
	 SKIPA C,LISTAR(D)+1
	  JRST MGQDL6			; Yes
	MOVE A,LISTAR(C)+1		; Host
	HRRZ B,LISTAR(D)
	TLO F,%QMLMD
	LNDEL D,$LLLST(L)		; No, flush LLN from QML!
	STAT (,("All queued msgs gone for "),HST(A))
	SKIPA D,B
MGQDL6:	 HRRZ D,LISTAR(D)		; Get next site LLN.
	JUMPN D,MGQDL1			; Do next host in queue
MGQDL9:	TLNE F,%QMLMD
	 CALL QMLPUT
	POPAE P,[L,E,D,C,B,A]
	RET

; Routine to verify consistency of MASTER and QML/RML.
; For each message in MASTER, makes sure it exists on QML/RML;
; For each message in QML/RML, makes sure it exists on MASTER.
;	(this isn't too important though, since it can legitimately
;	happen that QML/RML contain deleted messages)
; Prints out 1-line summary for each offending message.
; Note carefully that output goes to the standard output!!

; VERLST - Verify Lists.
; VERMST - Verify MASTER
; VERRML - Verify RML

; VERQML - Verify QML

VERQML:	MOVE P,[-PDLLEN,,PDL]
	SETZB F,MF
	SETOM DEBUG
	MOVE A,[-6,,[
		.ROPTION ? TLO 0,%OPALL	; Use new mode ints & lock hacking.
		.RMASK ? IOR 0,[%BADPI]	; Specify interruptable bad conditions,
		.RPICLR ? MOVE 0,[-1]]]	; and enable them.
	SYSCAL USRVAR,[CIMM %JSELF ? A]
	 .LOSE %LSFIL
	CALL MYADDR		; Set our address(es)
	SKIPN PURESW		; And now, make sure
	 CALL IPURIF		; that pages are pure!
	CALL DATIME"UPINI
	 CALL MTMHNG		; What the hell, be consistant...
	CALL MLINIT		; Now ensure uniqueness!
	CALL MCINIT		
	TLNN F,%SCOPN
	 CALL SCOPN		
	.CALL [SETZ ? SIXBIT/OPEN/ ? [.UAO,,DBC] ? [SIXBIT/DSK/]
		[SIXBIT/GUBBLE/] ? SETZ [SIXBIT/>/] ]
	 .LOSE %LSSYS
	OUT(DBC,OPEN(UC$IOT))
	OUT(,CH(DBC))
	SETZ E,				; Count of LNs flushed.
	SKIPN $AROPN+QMLAR
	CALL QMLGET
	MOVE L,$ARLOC+QMLAR		; L points to QML.
	HRRZ B,$LLLST(L)		; B will point to QML nodes.
	CAIA
VERQM3:	 HRRZ B,LISTAR(B)
	JUMPE B,VERQM9
	LDB A,[$LAFLD,,LISTAR(B)]	; Check type of QML node.
	CAIE A,A$Q
	 JRST [	OUT(,("QML node bad??"),EOL)
		JRST VERQM3]
	MOVE A,LISTAR(B)+1		; Host sublist.
	LDB C,[$LAFLD,,LISTAR(A)]
	CAIE C,A$QHST
	 JRST [	OUT(,("Bad QML entry!!"),EOL)
		JRST VERQM3 ]
	OUT(,HST(LISTAR(A)+1),(":"))	; Output host-name for site.
	HRRZ A,LISTAR(A)		; CDR is to failure count.
	LDB C,[$LAFLD,,LISTAR(A)]
	CAIN C,A$QFCT			; Failure-count for host?
	 JRST [	OUT(,(" failure cnt "),D(LISTAR(A)+1))
		HRRZ A,LISTAR(A)
		JRST .+1]
	OUT(,EOL)
	CAIA
VERQE3:	 HRRZ A,LISTAR(A)		; CDR is to msg-ID list.
VERQE4:	SKIPE A
	 JRST [	LDB C,[$LAFLD,,LISTAR(A)]
		CAIN C,A$ID
		 JRST [	MOVE C,LISTAR(A)+1
			ADD C,$LSLOC(L)
			OUT(,("Message: "),TC(C),EOL)
			JRST VERQE3 ]
		OUT(,("Flushing bad LN in list, type is "),D(C))
		HRRZ C,LISTAR(A)	; Get CDR before flushing...
		OUT(,(" ...CDR is: "),O(C),EOL)
		AOS E
		LNDEL A,LISTAR(B)+1	; Flush from list.
		MOVE A,C
		TLO F,%QMLMD		; Indicate QML modified!
		JRST VERQE4 ]
	JRST VERQM3
VERQM9:	TLNE F,%QMLMD		; QML modified?
	 .VALUE [ASCIZ ":QML munged"]
	CALL QMLPUT
	TLZ F,%QMLMD		; And get rid of in-core copy
	UARCLS QMLAR
	OUT(,("Flushed "),D(E),(" nodes."))
	.VALUE [ASCIZ ": Done."]
	OUT(SC,CLS)
	OUT(DBC,CLS)
	MOVEI A,LOCK1
	CALL LKFREE		; First, make damn sure launch lock is free,
	JSR DEATHV		; then start swinging in the breeze


; PRTQML - Output QML, nicely formatted, on std output.

PRTQML:	PUSHAE P,[A,B,L]
	SKIPN $AROPN+QMLAR
	 CALL QMLGET
	MOVE L,$ARLOC+QMLAR
	HRRZ B,$LLLST(L)
	CAIA
PRTQM3:	 HRRZ B,LISTAR(B)
	JUMPE B,PRTQM9
	LDB A,[$LAFLD,,LISTAR(B)]
	CAIE A,A$Q
	 JRST [	OUT(,("QML node bad??"),EOL)
		JRST PRTQM3]
	MOVE A,LISTAR(B)+1
	CALL PRTQME		; Print a QML entry.
	JRST PRTQM3

PRTQM9:	POPAE P,[L,B,A]
	RET

; PRTQME - Output QML Entry, on standard output.
;	A - LP to the start of a site's queue-list.
;	L - QML LSE

PRTQME:	PUSHAE P,[B]
	LDB B,[$LAFLD,,LISTAR(A)]	; Get attrib of 1st LN
	CAIE B,A$QHST			; It better be this.
	 JRST [	OUT(,("Bad QML entry!!"),EOL)
		JRST PRTQE9]
	OUT(,HST(LISTAR(A)+1),(":"))	; Output host-name for site.
	HRRZ A,LISTAR(A)		; Get CDR, move up.
	LDB B,[$LAFLD,,LISTAR(A)]	; Get attrib for next LN.
	CAIN B,A$QFCT			; Failure-count for host?
	 JRST [	OUT(,(" failure cnt "),D(LISTAR(A)+1))
		HRRZ A,LISTAR(A)
		JRST .+1]
	OUT(,EOL)
	UARPUSH MSGAR			; Must save current msg!
	CAIA
PRTQE3:	 HRRZ A,LISTAR(A)
	JUMPE A,[UARPOP MSGAR		; Done, can restore MSGAR.
		JRST PRTQE9]
	LDB B,[$LAFLD,,LISTAR(A)]
	CAIE B,A$ID			; Should be message-ID
	 JRST [	OUT(,(" Bad LN in list!"),EOL)
		JRST PRTQE3]
	CALL MSGGET			; Try to get message, clobbers MSGAR.
	 JRST [	OUT(,(" Can't retrieve message "),TLS(A),EOL)
		JRST PRTQE3]
	PUSH P,L
	MOVE L,$ARLOC+MSGAR
	OUT(,("  "))			; Indent a couple spaces.
	CALL PRTSUM			; Output info about msg.
	POP P,L
	JRST PRTQE3

PRTQE9:	POPAE P,[B]
	RET

; PRTSUM - Show two-line message summary.  Outputs to std output.
;	L - LSE holding message list.

PRTSUM:	PUSHAE P,[A,B,C]
	FINDA A,[A$ID,,[$LLLST(L)]]	; Find message ID
	 JRST [	OUT(,(" <No msg ID!!> "))
		JRST PRTSM1]
	OUT(,TLS(A))		; Output ID
PRTSM1:	FINDA A,[A$TIM,,[$LLLST(L)]]	; Find message time
	 JRST [	OUT(,(" <no time!> "))
		JRST PRTSM2]
	OUT(,(" "),TIM(F1,LISTAR(A)+1),(" "))	; Output time.

	; Now print originator... this one is funny.
PRTSM2:	CALL SKPNMH		; Net-mail-from-host?
	 JRST PRTSM3
	OUT(,("@"),FMT(HST(A),-7.))
	JRST PRTSM4

PRTSM3:	FINDA A,[A$SNM,,[$LLLST(L)]]	; Find sender's name.
	 JRST [	OUT(,("   <>   "))
		JRST PRTSM4]
	OUT(,(" "),FMT(TLS(A),-7.))
	FINDA A,[A$SNH,,[$LLLST(L)]]	;Sender at foreign host?
	 JRST PRTSM4
	OUT(,("@"),FMT(HST(LISTAR(A)+1),-7.))

	; Now hack length.
PRTSM4:	FINDA A,[A$MTXT,,[$LLLST(L)]]
	 TDZA A,A
	  HLRZ A,LISTAR(A)+1	; Get length
	OUT(,(" "),D(A,5),(" "))

	; Now finalize first line by printing subject.
PRTSM5:	FINDA A,[A$SBJ,,[$LLLST(L)]]
	 JRST PRTSM6
	OUT(,("S:"),FMT(TLS(A),,20.))	; Output up to 20 chars.

;List queued recipients, one to a line
PRTSM6:	SKIPA C,$LLLST(L)
PRTSM7:	 HRRZ C,LISTAR(C)
	FINDA C,[A$RCP,,[C]]		; Find next queued rcpt
	 JRST PRTSM9			; No more
	FINDA A,[A$RPSN,,[LISTAR(C)+1]]	; Is it a pseudo?
	 CAIA
	  JRST PRTSM7			; Ignore it if so.
	OUT(,EOL,("    -> "))		; Start line for this rcpt
	FINDA A,[A$RTYP,,[LISTAR(C)+1]]	; Doesn't work to call NSRNAM
	 JRST PRTS7A			; Recursive %LTSAO I guess is bug
	OUT(,SL(A),(" "))
PRTS7A:	FINDA A,[A$RNAM,,[LISTAR(C)+1]]
	 JSR AUTPSY
	FINDA B,[A$RHST,,[LISTAR(C)+1]]
	 TDZA B,B			; Local host
	  MOVE B,LISTAR(B)+1
	OUT(,SL(A),(" at "),HST(B))
	FINDA A,[A$RFCT,,[LISTAR(C)+1]]	;Look for rcpt's failure count
	 JRST PRTSM8
	OUT(,("; Failed "),D(LISTAR(A)+1),(" times"))
PRTSM8:	FINDA A,[A$RRMG,,[LISTAR(C)+1]]	; See if err msg there
	 JRST PRTSM7
	OUT(,("; ERR="),SL(A))
	JRST PRTSM7

PRTSM9:	OUT(,EOL)
	POPAE P,[C,B,A]
	RET
SUBTTL Main mailing routine

;;; MAIL - Main mailing routine. 
;;;   MF/ control flags
;;;   A/ ptr to msg attrib list
;;;   B/ (optional) time of event this message is about
;;;
;;; Composes header, eqv's rcpts, sends msg and any error 
;;; msgs that arise, and queues if temporary error encountered.


M%CORG==1	;rh flag, set when msg originated by comsat.
M%SSBJ==2	;"  set when b has time to generate "msg of" subject about.
M%ERR==4	; Indicates error message, must CC to MAIL-MAINTAINERS.
M%EMSG==10	; Indicates message LSE is in EMSGAR.

.SCALAR MSBJTM	; May hold a time.
.SCALAR NRXPER	; # errors seen during rcpt expansion etc.

MAIL:	PUSHAE P,[A,B,C,D,L,N,LSTSUP,NRXPER]
	PUSHAE P,[NTSITE,NTHOST,NDHOST,NTCCON,NTRTSW,NTTYPE,XRSQQ,XRSQPS,XRSQRS]
	MOVEM B,MSBJTM		; Remember time of subject message.
	TRNN MF,M%EMSG		; Message in EMSGAR?
	  JRST MAIL04

	; Must make EMSGAR the MSGAR, by swapping ARBLKs!
	UARPUSH MSGAR		; Save MSGAR
	UARPUSH EMSGAR		; and transfer EMSGAR -> MSGAR via push/pop.
	UARPOP MSGAR

MAIL04:	MOVE L,$ARLOC+MSGAR	; Make current with right address.
	MOVE A,$LLLST(L)	
	TRNN MF,M%ERR		; Error message that must be CC'd to Comsat?
	 JRST MAIL06
	MAKELN B,[A$RNAM,,0
		%LTSTR,,[LITSTR [MAIL-MAINTAINERS]]]
	MAKELN B,[A$ROPT,,[B]
		%LTSTR,,[LITSTR [CC]]]
	MAKELN A,[A$RCP,,[A]
		%LTLST,,[B]]	; now have at least "MAIL-MAINTAINERS" as dest.

MAIL06:	TRNN MF,M%CORG		; Did COMSAT originate this message?
	 JRST MAIL30
	.IOPUSH NETI,		; Yes, we're recursing.  Save net chans.
	OUT(NETO,PUSH)
	OUT(NETD,PUSH)
	SETZB N,NTHOST			; And clear "current site".
	FINDA B,[A$ID,,[$LLLST(L)]]	; Make sure msg has an ID.
	 JRST [	MOVE B,A
		CALL IDGET		; Get one
		HRRM B,LISTAR(A)	; Tack rest of list onto it.
		JRST .+1]
	TRNE MF,M%SSBJ			; If so, form subject given time?
	 MAKELN A,[A$SBJ,,[A]		; Yes, make it.
		%LTSAO,,[[OUTCAL(,("Msg of "),TIM(DOW,MSBJTM),(", "),TIM(F2,MSBJTM))]]]
	MAKELN A,[A$SRTP,,[A] ? %LTSTR,,0]	; Prevent any SMTP looping.
	MAKELN A,[A$SNM,,[A] ? %LTSTR,,[LITSTR [COMSAT]]]
MAIL30:	MOVE B,A
	CALL DATIME"TIMGET
	MAKELN A,[A$TIM,,[B] ? %LTVAL,,[A]]
	MOVEM A,$LLLST(L)	; Now store ptr to consed-up list.
	SETZM NRXPER		; Zero error count
	CALL SNMEXP		; Hack sender name and options.
	CALL RCPEXP		; Crunch rcpt list into expanded form (can generate err)
	CALL HEADER		; Now form headers for message.
	CALL TXTCHK		; Check out text for ^_'s or CRLF.CRLF
	SKIPE NRXPER		; If any errors,
	 CALL RXPERR		;  generate preliminary err msg.
	CALL RCPSND		; now send!

	; Done, see if must restore MSGAR.
	TRNN MF,M%EMSG		; Was EMSGAR swapped?
	 JRST MAIL90		; Nope, OK.
	UARPOP MSGAR		; Restore old MSGAR.
MAIL90:	TRNN MF,M%CORG		; And now, if twere in recursion,
	 JRST MAIL95
	OUT(NETD,POP)		; Restore net channels.
	OUT(NETO,POP)
	.IOPOP NETI,
MAIL95:	POPAE P,[XRSQRS,XRSQPS,XRSQQ,NTTYPE,NTRTSW,NTCCON,NDHOST,NTHOST,NTSITE]
	POPAE P,[NRXPER,LSTSUP,N,L,D,C,B,A]
	RET
SUBTTL Sender name & option hacking.

SNMEXP:	PUSHAE P,[A,B]
	FINDA A,[A$SNH,,[$LLLST(L)]]
	 CAIA
	  JRST SNXP90		;Don't do if sender at foreign host
	FINDA A,[A$SNM,,[$LLLST(L)]]		; Find sender name
	 JRST [	FINDA A,[A$CSN,,[$LLLST(L)]]
		 JSR AUTPSY
		JRST .+1]
	MOVE A,LISTAR(A)+1
	ADD A,$LSLOC(L)		;make abs ascnt ptr to name
	SETZ B,			; Match to any type.
	CALL RCPEQV		;get ptr to option list if any
	 JRST SNXP90		;none
	PUSH P,L
	MOVE L,$ARLOC+EQVAR	; Temporarily set LSE to EQV for searching.
	FNDSTR A,A,A$ROPT,[NOQC]
	 TDZA B,B
	  SETO B,
	POP P,L
	JUMPE B,SNXP90		; Jump if no "NOQC" option.
	MAKELN A,[A$CNF,,[$LLLST(L)]
		%LTSTR,,[LITSTR [FAIL]]]
	MOVEM A,$LLLST(L)	; Found it, store confirm-option on list.

SNXP90:	POPAE P,[B,A]
	RET


; SNMRCP - Cons up RCPT for sender name.  Called by various things
;	that want to return some message or error to sender.
;	L - current LSE must have message attribs in it.
; Failure return .+1 if can't find sender name.
; Returns .+2
;	A - LP to an A$RCP LLN.
;
; Nowadays prefer an SMTP return path to a parsed FROM field!
;	We add ourselves to the virtual routing path at transmission time;
;	the first host in an SMTP reverse path is the host we received the
;	message from.  This means we can simply hand the entire path back
;	to the most recent relay host as the forward path for an error
;	receipt.  SU-SCORE appears to be trying to parse the path though!

SNMRCP:	PUSHAE P,[B,C]
	FINDA A,[A$SMRP,,[$LLLST(L)]]	; Explicit maintainer specified?
	 CAIA				;  Give them the error receipt.
	  JRST SNMRC6
	FINDA A,[A$CSN,,[$LLLST(L)]]	; Maybe claimed by someone?
	 CAIA				;  Give them the error receipt.
	  JRST SNMRC6
	FINDA A,[A$SRTP,,[$LLLST(L)]]	; Maybe SMTP path available?
	 JRST SNMRC5
	MOVE B,LISTAR(A)+1		; Get spt to return path.
	TLNN B,-1			; See if it's explicitly null
	 JRST SNMRC9			;  Yeah, punt this recipient.
	LNCOPY A,[0 ? A]	   	; Copy the reverse-path.
	MOVEI B,A$RNAM			; Becomes  forward-path.
	DPB B,[$LAFLD,,LISTAR(A)]  	; Mutate into rcpt name.
	FINDA B,[A$NMH,,[$LLLST(L)]]	; Find most recent netmail host.
	 JRST SNMRC5			;  Eh?
	JRST SNMRC7			; Source routed rcpt.

SNMRC5:	FINDA A,[A$SNM,,[$LLLST(L)]]	; Maybe parsed the header?
	 JRST SNMRC9			;   Ugh, no rcpt!
SNMRC6:	LNCOPY A,[0 ? A]		; Begin with copy of name LN.
	MOVEI B,A$RNAM			; Create rcpt name attribute.
	DPB B,[$LAFLD,,LISTAR(A)]	; Munge our copy.
	FINDA B,[A$SNH,,[$LLLST(L)]]	; If sender was at foreign host
	 CAIA				;  use that information too.
SNMRC7:	MAKELN A,[A$RHST,,[A] ? %LTVAL,,[LISTAR(B)+1]]	; Make copy of host LN.
	MAKELN A,[A$RCP,,0 ? %LTLST,,[A]] ; Cons host, recipient.
	AOS -2(P)			; Success!
SNMRC9:	POPAE P,[C,B]			; All done.
	RET


SUBTTL Message header formation

; HEADER -  Using current MSG-LSE, and any argument flags etc,
;	generates headers for each recipient, as well as a standard
;	default A$MHDR for the whole message. (If a rcpt has no
;	A$RHDR, the A$MHDR is used instead.)


HEADER:	PUSHAE P,[A,B,C,D]
	CALL HDRINI			; Initialize header vars...

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; This is a special kludge for net mail; when (if) network headers are
;;; parseable, the new scheme will be to extract the network header
;;; exactly and put it in HDRPN (to preserve comment fields) but 
;;; generate the ITS headers etc. as appropriate.  But since they can't
;;; be parsed, instead we put in the following "header force" which OVERRIDES
;;; R-HEADER-FORCEs, so that people who use R-HEADER-FORCE won't complain.
	FINDA B,[A$NMH,,[$LLLST(L)]]	; Is this net mail?
	 JRST HEAD05			;   No, avoid kludge.
	SETOM HDRNMH		; Make other code do different things
;;; Now, look at the first line of the message text.  If it looks like an
;;; ITS header, make up a network header from it and put it on as A$KHDR.
	FINDA B,[A$MTXT,,[$LLLST(L)]]	; Get message text
	 JRST HEAD05			;  None?
	MOVE B,LISTAR(B)+1
	HLRZ A,B			; Length
	ADD B,$LSLOC(L)
	HRLI B,440700			; Byte pointer
	MOVEM B,$LSRPT(L)		; Put where will be relocated by UABUMP
HDRK1:	ILDB C,$LSRPT(L)		; See if there's an @ before
	CAIN C,"@			; the first : or CR
	 JRST HDRK3
	CAIE C,":
	 CAIN C,^M
	  JRST HEAD05
	SOJG A,HDRK1
	JRST HEAD05

;;; Next space after the @ terminates the from-name, unless there is a parenthesized
;;; thingamajig after that.
HDRK3:	SOJLE A,HEAD05
	ILDB C,$LSRPT(L)
	CAIN C,^M
	 JRST HEAD05
	CAIE C,40
	 JRST HDRK3
	SOJLE A,HEAD05
	MOVE C,$LSRPT(L)		;LOOK AHEAD 1 CHARACTER
	ILDB C,C
	CAIE C,"(
	 JRST HDRK5
HDRK4:	ILDB C,$LSRPT(L)
	CAIN C,^M
	 JRST HEAD05
	SOJLE A,HEAD05
	CAIE C,")
	 JRST HDRK4
	ILDB C,$LSRPT(L)
	SOJLE A,HEAD05			;WE HAVE JUST READ THE SPACE
HDRK5:	SAOBEG TMPC,			;Make network header
	FWRITE TMPC,[[From: ]]		; Do any bumping before ASCNT formed.
	FINDA C,[A$MTXT,,[$LLLST(L)]]
	 JSR AUTPSY
	MOVE C,LISTAR(C)+1
	ADD C,$LSLOC(L)
	HLRZ D,C
	SUBI D,1(A)
	HRL C,D			;ASCNT to from-name not counting trailing sp.
	OUT(TMPC,TC(C),EOL)
	;Next 17. characters are the date
	FWRITE TMPC,[[Date: ]]
	MOVEI D,17.
HDRK6:	ILDB C,$LSRPT(L)
	OUT(TMPC,C((C)))
	SOJG D,HDRK6
	SUBI A,17.
	OUT(TMPC,EOL)
	;The rest of the line, if anything, is the subject.
	;In that case there is a Re: before it.
HDRK7:	SOJLE A,HDRK10
	ILDB C,$LSRPT(L)
	CAIN C,^M
	 JRST HDRK10
	CAIE C,":
	 JRST HDRK7
	FWRITE TMPC,[[Subject]]
HDRK8:	OUT(TMPC,C((C)))
	SOJLE A,HDRK9
	ILDB C,$LSRPT(L)
	CAIE C,^M
	 JRST HDRK8	
HDRK9:	OUT(TMPC,EOL)
HDRK10:	OUT(TMPC,EOL)			;Blank line separates header from text
	MAKELN A,[0 ? %LTSAO,,0]	; Return accumulated stuff.
	MOVEI B,A$KHDR
	DPB B,[$LAFLD,,LISTAR(A)]	; Set up attrib
	MOVE B,$LLLST(L)
	HRRM B,LISTAR(A)
	HRRZM A,$LLLST(L)		; Cons onto message-attrib list.
	;drop into HEAD05
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	; First, find out what sort of header the sender wants to use.
HEAD05:	MOVEI A,3			;Null header if NET-MAIL-FROM-HOST
	SKIPE HDRNMH
	 JRST HEAD20
	FINDA B,[A$HFRC,,[$LLLST(L)]]	;header force option exists?
	 JRST HEAD10			;no, hack default stuffs.
	MOVSI A,-NHDRFS
	USLNEA B,HDRFTB(A)		; Search thru header force table.
	 AOBJN A,.-1
	JUMPL A,HEAD20			; Jump if found!

	; Default header munching, decide if want ITS or Network style.
	; If completely intra-ITS, use ITS style; else must use Network style.
HEAD10:	SETZM RCSQCT		;initialize
HEAD12:	CALL RCPSEQ		;get one rcpt
IFN 0,[	 JRST [	SETZ A, ? JRST HEAD20]	;none left, ITS-style header is ok.

	FINDA A,[A$RHST,,[A]]	;see if foreign host
	 JRST HEAD12		; None, local...
	MOVE A,LISTAR(A)+1	;yes, get #
	CALL NHITS		; an ITS site?
	 CAIA
	  JRST HEAD12		;yes, continue
];IFN 0
	 NOP
	MOVEI A,4		; Non-ITS site found, default to RFC822 header.

HEAD20:	CALL @HDRTB(A)	; Form whatever style header is desired, return SLP in A
	MOVEM A,HDRPDF		; Store it as default and future A$MHDR.

	SKIPA D,$LLLST(L)	; Begin loop thru rcpts...
HEAD30:	 HRRZ D,LISTAR(D)	; Get next
	FINDA D,[A$RCP,,[D]]	; Find one.
	 JRST HEAD90		; When done, finalize & return.
	MOVE B,LISTAR(D)+1	; Get ptr to list
	FINDA A,[A$RPSN,,[B]]	; Make sure it's not a pseudo.
	 CAIA
	  JRST HEAD30		; It was, ignore it.

	; Here, can hack each rcpt's header separately.
	FINDA A,[A$HFRC,,[$LLLST(L)]]	; Ignore R-header-force if header-force
	 SKIPE HDRNMH		; ...or if NET-MAIL-FROM-HOST
	  JRST HEAD40
	FINDA A,[A$RHFC,,[B]]	; Does rcpt have a R-header-force?
	 JRST HEAD40		; Nope, so skip that.
	MOVSI C,-NHDRFS		; Has a R-header-force!  Find IDX for it.
	USLNEA A,HDRFTB(C)
	 AOBJN C,.-1
	JUMPGE C,HEAD30		; Jump if can't understand it.
	CALL @HDRTB(C)	; Else execute...
	JRST HEAD45		; and go put onto rcpt's list.

HEAD40:	FNDSTR A,B,A$RTYP,[*MSG]	; Is this a *MSG rcpt?
	 JRST HEAD30		; Nope, don't form any special header.
	CALL HDRMSG		; Uh-oh!  Form *MSG-style header!
HEAD45:	MOVEI C,A$RHDR
	DPB C,[$LAFLD,,LISTAR(A)] ; Set attrib
	LNAPP [B ? A]		; Put header on rcpt list.
	JRST HEAD30		; Back for more.

	; Return after header adventures.
HEAD90:	SKIPN A,HDRPDF			; Get default header.
	 JSR AUTPSY			; MUST ALWAYS BE ONE!
	LNCOPY A,[0 ? A]		; Copy it...
	MOVEI B,A$MHDR
	DPB B,[$LAFLD,,LISTAR(A)]	; Set up attrib
	MOVE B,$LLLST(L)
	HRRM B,LISTAR(A)
	HRRZM A,$LLLST(L)		; Cons onto message-attrib list.
HEAD99:	POPAE P,[D,C,B,A]
	RET


	; Header-Force table.
HDRFTB:	ASCNT [ITS]
	ASCNT [NET]
	ASCNT [*MSG]
	ASCNT [NULL]
	ASCNT [RFC733]
NHDRFS==.-HDRFTB

	; Header-routine table, holds rtns corresponding to HDRFTB above.
HDRTB:	HDRITS		; 0 - ITS header.
	HDRNET		; 1 - NET header.
	HDRMSG		; 2 - *-MSG header.
	HDRNUL		; 3 - Null header. (This number is known by HEAD05)
	HDRSTD		; 4 - ARPAnet standard header (RFC822)

BVAR	; Header-routine variables.
HDRTIM:	0		; Time of message
HDRFRM:	0		; SLP to "FROM" name to use.
HDRSBY:	0		; SLP to "SENT-BY" name to use, if any.
HDFFLG:	0		; -1 when "@" exists within FROM name.
HDRPI:	0		; SLP to ITS-style header if one formed yet.
HDRPN:	0		; SLP to NET-style header if one formed yet.
HDRPS:	0		; SLP to RFC733-style header if one formed yet.
HDRPMG:	0		; SLP to *MSG-style header if one formed yet.
HDRPDF:	0		; SLP to default header if one (used for A$MHDR).
HDRLCL:	0		; -1 if all recipients are local (i.e. on OWNHST)
HDRNMH:	0		; non-zero if NET-MAIL-FROM-HOST and header is in text
HDRFNB:	BLOCK 14	; Buffer for storing unscrambled full name.
EVAR

; HDRINI - Initialize Header routine variables.
;	MUST be called before any HDRITS, HDRNET, etc to produce valid
;	results for current message!

HDRINI:	PUSHAE P,[A,B,C]
	SETZM HDRFRM			; Clear various variables
	SETZM HDRSBY
	SETZM HDFFLG
	SETZM HDRPI
	SETZM HDRPN
	SETZM HDRPS
	SETZM HDRPMG
	SETZM HDRLCL
	SETZM HDRNMH
	FINDA A,[A$TIM,,[$LLLST(L)]]	; Find time of msg
	 JRST [	CALL DATIME"TIMGET 	; If doesn't exist, get current time.
		JRST HDRIN0]
	MOVE A,LISTAR(A)+1
HDRIN0:	MOVEM A,HDRTIM			; Save for later ref.

	; Now straighten out which sender name(s) to use
	SETZB B,C			;B holds claimed name ptr, C the sent-from name ptr
	FINDA C,[A$SNM,,[$LLLST(L)]]	;find the sent-from name if any
	 JFCL
	FINDA B,[A$CSN,,[$LLLST(L)]]	;find claimed-from name
	 CAIE C,			;if neither exists, skip and die
	  CAIA				;one exists, skip and live
	   JSR AUTPSY
	USLNE B,C			;compare strings
	 CAIA				;unequal
	  SETZ C,			;equal, so flush sent-from and use claimed-from.
	CAIN B,				;if claimed-from doesn't exist,
	 EXCH B,C			;use sent-from as claimed-from.
	MOVEM B,HDRFRM			; Store who it's from
	MOVEM C,HDRSBY			; And who actually sent it.

	SETZM RCSQCT		; initialize RCPSEQ coroutine
HDRIN1:	CALL RCPSEQ		; get a recipient
	 JRST [	SETOM HDRLCL	; none left, all recipients are local
		JRST HDRIN2
		]	
	FINDA A,[A$RHST,,[A]]	; get recipient's host
	 JRST HDRIN1		; none, i.e. local, go check next
	MOVE A,LISTAR(A)+1	; get host no.
	CAMN A,OWNHST		; is host us?
	 JRST HDRIN1		; yes, keep looking at recipients


	;minor check here ... if "@" exists within claimed-from name,
	;set flag saying not to add "@ <ownsite>" (but always add that to
	;sent-from name! Generate sent-from in this case if it didn't exist?)
	; Later should allow optional host spec for FROM: in input file.

HDRIN2:	SETZM HDFFLG
	JUMPE C,HDRIN5		; meaningless to check if no sent-from.
	MOVE A,LISTAR(B)+1	; get SPT to sender name.
	ADD A,$LSLOC(L)		; Make abs.
	HLRZ B,A
	HRLI A,440700
	JRST HDRIN4
	ILDB C,A
	CAIN C,"@
	 SOSA HDFFLG		;make -1 and skip out of loop
HDRIN4:	SOJGE B,.-3
HDRIN5:	POPAE P,[C,B,A]
	RET


SUBTTL Individual header routines.

; These routines are called to generate a specific type of header.
; They take no explicit arguments: they just assume that the current LSE
; is the message for which the header must be generated.  Also, to
; save time, certain decoding of the LSE which is common to several
; header types is prepared by HDRINI (i.e., HDRTIM, HDRSBY, etc).
; They are expected to return in A an SLN (no attribute) to the header
; text.

	; Form a null header
HDRNUL:	MAKELN A,[0 ? %LTSTR,,0]	; Fast...
	RET

	; Form an ITS-style header.
HDRITS:	SKIPE HDRPI			; If one already formed,
	 JRST [	LNCOPY A,[0 ? HDRPI]	; Copy and return that.
		RET]
	SAOBEG TMPC,
	FWRITE TMPC,[TLS,HDRFRM]		; Write FROM name.
	SKIPN HDFFLG				; If he has own ideas
	 JRST [	FWRITE TMPC,[[@],TZ,OWNNAM]	;  skip "@<localsite>".
		JRST .+1]	; Else (default) add it.
	SKIPN HDRSBY		; And skip to add Sent-by if different.
	 JRST HDRI20
	FWRITE TMPC,[[ (Sent by ],TLS,HDRSBY]	; Different, add "Sent-by"
	SKIPN HDFFLG
	 JRST [	FWRITE TMPC,[[@],TZ,OWNNAM]	; Add local-site if not given in FROM.
		JRST .+1]
	OUT(TMPC,RPAR)
HDRI20:	FWRITE TMPC,[[ ],WA,HDRTIM]		; Now, time of message.
	FINDA A,[A$SBJ,,[$LLLST(L)]]		;find subject if any
	 JRST HDRI30				;nope
	FWRITE TMPC,[[ Re: ],TLS,A]		;add the subject string
HDRI30:	OUT(TMPC,EOL)
	MOVE A,RCPNUM
	SKIPE HDRLCL			; if any recipients are foriegn then force a To: line
	 CAILE A,1			; only one recipient?
	  CALL TOPUT			; if so, skip 'to:' lines...else do.
	CALL UHDPUT			; Output user-specified header lines, if any.
	MAKELN A,[0 ? %LTSAO,,0]	; Return accumulated stuff.
	MOVEM A,HDRPI			; Save SLP in case called again.
	RET

	; NET style header
HDRNET:	SKIPE HDRPN			; If one already formed,
	 JRST [	LNCOPY A,[0 ? HDRPN]	; Copy & return that.
		RET]
	PUSH P,B
	SAOBEG TMPC,
	FWRITE TMPC,[[Date: ],WB,HDRTIM,[
From: ],TLS,HDRFRM]			; SIMPLE!!!
	SKIPE HDFFLG			; If FROM has a "@site" in it,
	 JRST HDRN22			; Skip "at <site>" & fullname.
	FWRITE TMPC,[[ at ],TZ,OWNNAM]

	; Now try to put the full name of the sender, in parentheses.
	MOVE B,HDRFRM
	MOVE A,LISTAR(B)+1
	ADD A,$LSLOC(L)		; Get abs ASCNT to sender's name.
	HLRZ B,A		; Get length of FROM field.
	CAILE B,6		; If longer than 6 letters, loser probably
	 JRST HDRN22		; put his fullname in From: field. 
	CALL CVT76C		; LSRTNS won't get the right thing, so skip it.
	MOVE B,A		; Get SIXBIT UNAME.
	MOVEI A,LSR1C
	CALL LSRTNS"LSRUNM
	 JRST HDRN22		; Not in LSR1, skip full-name hack.
	MOVEI A,LSRTNS"I$NAME
	CALL LSRTNS"LSRITM
	 JRST HDRN22		; No full name given, skip it.

	; A/ BP to the fullname in ASCIZ, last-name-first.
	MOVE B,A
	ILDB B,B		; (Get first char of full name)
	JUMPE B,HDRN22		; Full name is null, skip it.
	MOVE B,[440700,,HDRFNB]
	CALL LSRTNS"LSRNAM	; Unscramble the order.
	 JRST HDRN22		; This should not ever happen.
	MOVE A,[440700,,HDRFNB]
	FWRITE TMPC,[[ (],TPZ,A,[)]]	; SIMPLE!!!
HDRN22:	OUT(TMPC,EOL)
	SKIPE HDRSBY			; Sent-by exists?
	 JRST [	FWRITE TMPC,[[Sent-by: ],TLS,HDRSBY,[ at ],TZ,OWNNAM,[
]]
		JRST .+1]		;add sent-from name if necessary
	FINDA A,[A$SBJ,,[$LLLST(L)]]	;find subject if any
	 JRST HDRN30			;no
	FWRITE TMPC,[[Subject: ],TLS,A,[
]]					;output line
HDRN30:	CALL TOPUT			; Output recipients.
	CALL UHDPUT			; Output any user-specified hdr lines.
	OUT(TMPC,EOL)			; Extra CR separates header from text
	MAKELN A,[0 ? %LTSAO,,0]
	MOVEM A,HDRPN			; Save SLP in case called again.
	POP P,B
	RET

; DARPA Internet standard header (RFC822)

LVAR HDRQUP:	-1	; -1 if want names with periods are to be quoted.
LVAR HDRIDS:	-1	; -1 if want message IDs stuck in headers.
LVAR HDRGAT:	0	; Non-zero: addr of foo%bar@baz relay (headers only)

HDRSTD:	SKIPE HDRPS			; If one already formed
	 JRST [	LNCOPY A,[0 ? HDRPS]	;   then just copy and return it.
		RET]
	PUSHAE P,[B,C]		; Else accumulate header in string.
	OUT(TMPC,OPEN(UC$SAO))	; Begin string.
	OUT(TMPC,("Date: "),TIM(RFC2,HDRTIM),EOL,("From: "))
	FINDA A,[A$FFM,,[$LLLST(L)]]	; Maybe FAKE-FROM spec.
	 CAIA
	  JRST [ OUT(TMPC,TLS(A),EOL)
		 JRST HDRS30 ]
	SKIPE HDFFLG		; If FROM specification has a "@" in it
	 JRST [	OUT(TMPC,TLS(HDRFRM),EOL) ; then put it out verbatim.
		JRST HDRS30 ]
	MOVE B,HDRFRM		; Try to find author's fullname.
	MOVE A,LISTAR(B)+1
	ADD A,$LSLOC(L)		; Get abs ASCNT to sender's name.
	CALL CVT76C		; Convert it to sixbit.
	MOVE B,A
	MOVEI A,LSR1C
	CALL LSRTNS"LSRUNM	; Look up in LSR1 database.
	 JRST HDRS10		;  If missing or no core, don't bother.
	MOVEI A,LSRTNS"I$NAME
	CALL LSRTNS"LSRITM	; Found luser - get fullname.
	 JRST HDRS10		;  Oh, well, no fullname given.
	MOVE B,A		; Bp to last-name-first string.
	ILDB B,B		; Check first char of fullname.
	JUMPE B,HDRS10		; If fullname is null, skip it.
	MOVE B,[440700,,HDRFNB]
	CALL LSRTNS"LSRNAM	; Unscramble the order.
	 CAIA
	  JRST HDRS20
	; Here when fullname is unavailable.
HDRS10:	SKIPE HDRGAT
	 JRST [	OUT(TMPC,TLS(HDRFRM),("%"),TZ(OWNNAM),("@"),TZ(GATNAM),EOL)
		JRST HDRS30 ]
	OUT(TMPC,TLS(HDRFRM),("@"),TZ(OWNNAM),EOL)
	JRST HDRS30

	; Here when we have the fullname.
HDRS20:	MOVE A,[440700,,HDRFNB]	; Bp to fullname.
HDRS21:	ILDB C,A		; Must check to see if it's cool.
	JUMPE C,HDRS24		; If no weirdness, we can output it.
	CAIN C,".		; Periods may be special
	 SKIPN HDRQUP		;   depending on latest RFC (sigh).
	CALL SCHK4		; Is this a special char?
	 CAIA
	  JRST HDRS21		; If not special, get another.
HDRS22:	MOVE A,[440700,,HDRFNB]	; Uh-oh, name contains a losing char.
	OUT(TMPC,C(""))		; Must surround the name with quotes.
HDRS23:	ILDB B,A
	JUMPE B,[ OUT(TMPC,C(""))
		  JRST HDRS29 ]
	CAIE B,""		; If we run into a null
	 CAIN B,"\		;  or a backslash
	  OUTCAL(TMPC,C("\))	;   quote it with a backslash.
	OUT(TMPC,C((B)))	; Output char of name.
	JRST HDRS23		; Go back for more chars.
HDRS24:	OUT(TMPC,TZ(HDRFNB))	; Output luser's full name.
HDRS29:	OUT(TMPC,SP,LABR,TLS(HDRFRM))
	SKIPE HDRGAT
	 JRST [ OUT(TMPC,("%"),TZ(OWNNAM),("@"),TZ(GATNAM),RABR,EOL)
		JRST HDRS30 ]
	OUT(TMPC,("@"),TZ(OWNNAM),RABR,EOL)

HDRS30:	SKIPE HDRSBY		; Do SENDER if there is one.
	 OUTCAL(TMPC,("Sender: "),TLS(HDRSBY),("@"),TZ(OWNNAM),EOL)
HDRS40:	FINDA A,[A$SBJ,,[$LLLST(L)]]
	 CAIA			; Do SUBJECT if there is one.
	  OUTCAL(TMPC,("Subject: "),TLS(A),EOL)
HDRS50:	CALL STOPUT		; Add TO: to header
	CALL SCCPUT		; Add CC: to header
	CALL UHDPUT		; Do user-specified header lines, if any.
	SKIPE HDRIDS		; Maybe put MESSAGE-ID in header.
	 JRST [	FINDA A,[A$ID,,[$LLLST(L)]]
		 JSR AUTPSY
		OUTCAL(TMPC,("Message-ID: "),TLS(A),EOL)
		JRST .+1 ]
	OUT(TMPC,EOL)		; Extra CRLF separates header from text.
	MAKELN A,[0 ? %LTSAO,,0]
	MOVEM A,HDRPS		; Save SLP in case called again.
	POPAE P,[C,B]
	RET


; HDRMSG - forms header line for a MSG of format
;   DISTRIB: *<site1>,*<site2>...
;   EXPIRES: <date>
;   <FROM> <TIME> <SUBJECT>
; tricky part is putting only the right names in DISTRIB.

HDRMSG:	SKIPE HDRPMG			; Already have a *MSG header?
	 JRST [	LNCOPY A,[0 ? HDRPMG]	; Use it if so.
		RET]
	PUSH P,B
	SAOBEG TMPC,
	FWRITE TMPC,[[DISTRIB: ]]
	TRO F,%NOCOM
	SETZM RCSQCT			;initialize RCPSEQ
HDRM10:	CALL RCPSEQ
	 JRST HDRM20			;no more
	FNDSTR B,A,A$RTYP,[*MSG]	; Find all which are of type *MSG.
	 JRST HDRM10			; Not right type, ignore.
	FNDSTR B,A,A$ROPT,[NOTDIST]	; Make sure it's OK to include in DISTRIB.
	 CAIA
	  JRST HDRM10			; Nope, it doesn't want to be in there.
	FINDA B,[A$RNAM,,[A]]		;yep, get the name
	 JSR AUTPSY
	TRZN F,%NOCOM			; Output comma if anything precedes this.
	 OUTCAL(TMPC,(","))
	FWRITE TMPC,[[ ],TLS,B]		; Output name
	JRST HDRM10			; & get another.

HDRM20:	FWRITE TMPC,[[
EXPIRES: ]]
	FINDA A,[A$XPIR,,[$LLLST(L)]]	;get ptr to expiration time
	 SKIPA B,[7]			; Use 7 days if none specified.
	  MOVE B,LISTAR(A)+1		; Get # days since msg creation date.
	IMUL B,[24.*60.*60.]		; Produce # secs
	MOVE A,HDRTIM
	CALL DATIME"TIMSEC			; Convert creation time to # secs,
	ADD A,B				; Add in to get future time,
	CALL DATIME"SECTIM			; And convert back from secs.
	FWRITE TMPC,[WA,A,[
]]
	CALL SKPNMH			; Don't make bogus ITS header
	 CAIA
	  JRST HDRM90
	FWRITE TMPC,[TLS,HDRFRM]
	SKIPE HDFFLG			; If has "@" within name,
	 JRST HDRM29			; leave it.
	FINDA A,[A$SNH,,[$LLLST(L)]]	; Sender have host?
	 JRST [	FWRITE TMPC,[[@],TZ,OWNNAM]	; No, use local host
		JRST HDRM29 ]
	FWRITE TMPC,[[@],HST,LISTAR(A)+1]	; Yes, use sender's host
HDRM29:	FWRITE TMPC,[[ ],WA,HDRTIM]
	FINDA A,[A$SBJ,,[$LLLST(L)]]	; See if subject spec...
	 JRST HDRM30			; Nope
	FWRITE TMPC,[[ Re: ],TLS,A]
	CALL UHDPUT			; Output user-specified header lines, if any.
HDRM30:	OUT(TMPC,EOL)
HDRM90:	MAKELN A,[0 ? %LTSAO,,0]
	MOVEM A,HDRPMG
	POP P,B
	RET

; TOPUT - forms 'to:' lines from existing A$RCP's.
; ignores those rcpts with attribs of OPTION NOSHOW.

.SCALAR TOPASS	; Flag for TO or CC.

TOPUT:	PUSHAE P,[A,B,C,D,E]
	FINDA A,[A$RLN,,[$LLLST(L)]]	;see if rcpt list name exists
	 CAIA
	  JRST TOPUT7

	SETZM TOPASS		; Indicate pass 1 (to's)

TOPUT1:	SETZM RCSQCT		; Initialize rcpseq routine
	SETZ E,			; Zero cnt of chars on line
TOPUT2:	CALL RCPSEQ		; Get ptr to rcp attrib list
	 JRST TOPUT8		; No more rcpts to get

	FNDSTR B,A,A$ROPT,[NOSHOW]
	 CAIA			; Supposed to suppress?
	  JRST TOPUT2		;  Yes, don't do it.

	FNDSTR B,A,A$ROPT,[CC]	; Cc?
	 JRST [	SKIPE TOPASS	;  No, if "to" pass, do it.
		 JRST TOPUT2
		JRST .+2]
	JRST [	SKIPN TOPASS	; Yes, if "to" pass, don't do.
		 JRST TOPUT2
		JRST .+1]
	FINDA B,[A$RHST,,[A]]	; Get dest attrib
	 SKIPA C,OWNHST		; No A$RHST means local
	  MOVE C,LISTAR(B)+1	; Get host #
	EXCH A,B
	PUSH P,B		; HSTSRC clobbers
	MOVE A,[440700,,TMPNAM]	; Buffer in which to stick hostname
	MOVE B,C		; Host#
	CALL RESOLV"HSTSRC	; Get ASCIZ string in A (horribly inefficient
				;  with domains, oh well, fix it later)
	 SKIPA A,[6]		; Unknown, will use number, assume 6 chars long
	  CALL LASCIZ		;  Get length
	POP P,B
	EXCH A,B		; Now have # chars in hostname in B.
	FINDA D,[A$RNAM,,[A]]	; Get rcp name attrib
	 JSR AUTPSY
	MOVEM D,TPTMP1		; Save for printout
	HLRZ D,LISTAR(D)+1	; Get # chars in rcpt name
	ADD B,D			; Add into cnt.
	FINDA D,[A$RTYP,,[A]]	; See if type exists.
	 JRST TOPUT3		; Nope, no need to count that.
	SLNEA D,[ASCNT [NAME]]
	 CAIA
	  JRST TOPUT3
	HLRZ D,LISTAR(D)+1	; Count rcpt type string.  Get # chars
	ADDI B,3(D)		; Plus 3 for parens & separating space.
TOPUT3:	ADDI B,4		; Plus 4 for " at "
	JUMPLE E,TOPUT4		; If nothing on line, jump...
	ADDI E,2(B)		; Add 2 for ", " and add into total for line
	CAIGE E,72.		; Line too big now?
	 JRST TOPUT6		; Nope, is OK... go output.
	OUT(TMPC,EOL)		; Must start new line... give CRLF.
TOPUT4:	MOVEI E,4(B)		; Install new count.
	SKIPE TOPASS
	 OUTCAL(TMPC,("CC: "))
	SKIPN TOPASS
	 OUTCAL(TMPC,("To: "))
	CAIA
TOPUT6:	 OUTCAL(TMPC,(", "))
	CALL RTOS		; Output short form of rcpt.
	JRST TOPUT2		; get another item

	;; Items all gone.
TOPUT8:	CAILE E,0		; If anything was on line,
	 OUTCAL(TMPC,EOL)	;  CRLF it.
	SKIPN TOPASS
	 JRST [SETOM TOPASS ? JRST TOPUT1]
TOPUT9:	POPAE P,[E,D,C,B,A]
	RET

	;; RCPT LIST NAME exists.
TOPUT7:	FWRITE TMPC,[[To: ],TLS,A,[
]]
	SETOM TOPASS
	JRST TOPUT1

; RTOS - Recipient Type-Out, Short.  Takes LP in A to a rcpt's list
;	and outputs on TMPC its short representation:
;	If type NAME, outputs "NAME at SITE."
;	Otherwise, "(TYPE NAME) at SITE".
; RTOSS - RTOS, Shorter.  Similar, but uses "@" instead of " at ".

RTOS:	TRZA F,%TMP		; Use random flag for distinction.
RTOSS:	 TRO F,%TMP
	PUSHAE P,[B,C]
	FINDA C,[A$RNAM,,[A]]
	 JSR AUTPSY
	FINDA B,[A$RTYP,,[A]]
	 JRST RTOS10		; No type, don't need to parenthesize!
	SLNEA B,[ASCNT [NAME]]	; Has one, check it.
	 CAIA
	  JRST RTOS10		; If NAME, also don't need parens.
	FWRITE TMPC,[[(],TLS,B,[ ],TLS,C,[)]]
	JRST RTOS11
RTOS10:	FWRITE TMPC,[TLS,C]
RTOS11:	TRNE F,%TMP
	 OUTCAL(TMPC,C("@))
	TRNN F,%TMP
	 OUTCAL(TMPC,(" at "))
	FINDA B,[A$RHST,,[A]]
	 SKIPA B,OWNHST
	  MOVE B,LISTAR(B)+1
	FWRITE TMPC,[HST,B]
	POPAE P,[C,B]
	RET

; RTOL - Recipient Type-Out, Long form.  Given LP to rcpt's list in A,
;	outputs on TMPC its representation in long form as follows:
;	(TYPE NAME @SITE (ATTRIB X) (ATTRIB Y)...)
;	This may get used in an internal ITS sending scheme, but at the moment
;	it is not called from anywhere.

RTOL:	PUSHAE P,[B,C]
	OUT(TMPC,LPAR)
	FINDA B,[A$RTYP,,[A]]
	 JRST [	OUT(TMPC,("NAME"))
		JRST .+2]
	OUT(TMPC,TLS(B),SP)
	FINDA B,[A$RNAM,,[A]]
	 JSR AUTPSY
	OUT(TMPC,TLS(B),SP)	; Output name...
	FINDA B,[A$RHST,,[A]]
	 SKIPA B,OWNHST
	  MOVE B,LISTAR(B)+1
	FWRITE TMPC,[[@],HST,B,]

	; Now the messy part - must have rtn that slobbers through a list
	; outputting all input-able attributes in right format.  In this
	;case, must use a suppress-list including A$RHST, A$RNAM, and A$RTYP.


	OUT(TMPC,RPAR)		; For time being...
	POPAE P,[C,B]
	RET

; STOPUT - forms 'To:' lines from existing A$RCP's.
; Ignores those recipients with attributes of OPTION NOSHOW.

LVAR  STCC:	0		; nonzero if doing cc:'s

STOPUT:	PUSHAE P,[A,B,C,D,E]
	SETZM STCC		; Do To:'s
	FINDA A,[A$RLN,,[$LLLST(L)]]	;see if recipient list name exists
	 JRST STPUT1
	OUT(TMPC,("To: "),TLS(A),EOL)
	JRST STPUT9

	; SCCPUT - Like STOPUT but for cc:'s.
.SCALAR TPTMP1

SCCPUT:	PUSHAE P,[A,B,C,D,E]	; save ACs
	SETOM STCC		; do cc:'s

STPUT1:	SETZM RCSQCT		; initialize RCPSEQ routine
	MOVEI E,0		; zero count of characters on line
STPUT2:	CALL RCPSEQ		; get ptr to recipient attribute list
	 JRST STPUT8		; no more recipients to get

	FNDSTR B,A,A$ROPT,[NOSHOW]	; supposed to suppress?
	 CAIA
	  JRST STPUT2		; yes, skip this recipient

	FNDSTR B,A,A$ROPT,[CC]	; cc?
	 JRST [	SKIPE STCC	; no, if "To" pass then do it
		 JRST STPUT2
		JRST .+2]
	JRST [	SKIPN STCC	; yes, if "To" pass then ignore it
		 JRST STPUT2
		JRST .+1]
	FINDA B,[A$RHST,,[A]]	; Get destination attribute
	 SKIPA C,OWNHST		; No A$RHST means local
	  MOVE C,LISTAR(B)+1	; Get host no.
	EXCH A,B
	PUSH P,B		; HSTSRC clobbers
	MOVE A,[440700,,TMPNAM]	; Put ASCIZ string here
	MOVE B,C		; Host#
	CALL RESOLV"HSTSRC	; Get addr of ASCIZ string in A (ha, ha)
	 SKIPA A,[6]		; Unknown, will use number, assume 6 chars long
	  CALL LASCIZ		;  Get length
	POP P,B
	EXCH A,B		; Now have # chars in hostname in B.
	FINDA D,[A$RNAM,,[A]]	; Get recipient name attribute
	 JSR AUTPSY
	MOVEM D,TPTMP1		; Save for printout
	HLRZ D,LISTAR(D)+1	; Get no. characters in recipient name
	ADD B,D			; Add into count
	FINDA D,[A$RTYP,,[A]]	; See if type exists
	 JRST STPUT3		; Nope, no need to count that
	SLNEA D,[ASCNT [NAME]]
	 CAIA
	  JRST STPUT3
	SLNEA D,[ASCNT [BUG]]
	 CAIA
	  SKIPA D,[1]		; hacky way of getting BUG- to be length 4
	HLRZ D,LISTAR(D)+1	; Count recipient type string.  Get no. of characters
	ADDI B,3(D)		; Plus 3 for parens and separating space
STPUT3:	ADDI B,3		; Plus 3 for " at "
	JUMPN E,STPUT4		; If nothing on line, jump...
	SKIPN STCC
	 OUTCAL(TMPC,("To: "))
	SKIPE STCC
	 OUTCAL(TMPC,("cc: "))
	MOVEI E,4(B)		; Install new count
	JRST STPUT7
STPUT4:	ADDI E,2(B)		; Add 2 for ", " and add into total for line
	CAIL E,72.		; Line too big now?
	 JRST STPUT5		; Nope, is OK... go output.
	OUT(TMPC,(", "))
	JRST STPUT7
STPUT5:	OUT(TMPC,(",
    "))
	MOVEI E,4(B)		; Install new count
STPUT7:	CALL SRTO		; Output short form of recipient
	JRST STPUT2		; get another item

; Items all gone.
STPUT8:	CAILE E,0		; if anything was on line
	 OUTCAL(TMPC,EOL)	; then CRLF
STPUT9:	POPAE P,[E,D,C,B,A]
	RET


; SRTO - RFC822 recipient Type-Out.  Takes LP in A to a recipient's list
;	and outputs on TMPC its short representation:
;	If type NAME, outputs "NAME@SITE".
;	If type BUG, outputs "BUG-NAME at SITE".
;	Otherwise, "(TYPE NAME) at SITE".

SRTO:	PUSHAE P,[B,C]
	FINDA B,[A$RNAM,,[A]]
	 JSR AUTPSY
	CALL SCHECK
	TRNE F,%TMP		; need to quote string?
	 OUTCAL(TMPC,C(""))	; yes, output openning quote
	FINDA C,[A$RTYP,,[A]]
	 JRST SRTO3		; No type, don't need to parenthesize!
	SLNEA C,[ASCNT [NAME]]	; Has one, check it.
	 CAIA
	  JRST SRTO3		; If NAME, also don't need parens.
	SLNEA C,[ASCNT [BUG]]
	 CAIA
	  JRST SRTO2

;Putting quotes around complex (list form) recipients is a misfeature.
;It makes the syntax more standard but it makes it impossible to
;reply-parse it.  Presumably complex recipients don't normally go to
;random non-ITS hosts anyway.
;	TRON F,%TMP		; already output a quote?
;	 OUTI TMPC,""		; no, output a quote now
	FWRITE TMPC,[[(],TLS,C,[ ],TLS,B,[)]]
	JRST SRTO4

SRTO2:	OUT(TMPC,("BUG-"))
SRTO3:	TRNN F,%TMP
	 JRST [	OUT(TMPC,TLS(B))
		JRST SRTO4 ]
	  CALL STLS
SRTO4:	TRNE F,%TMP		; need to close quote?
	 OUTCAL(TMPC,C(""))	; yes, output closing quote
	FINDA B,[A$RHST,,[A]]
	 SKIPA B,OWNHST
	  MOVE B,LISTAR(B)+1	;Get host address in B.
	CAME B,OWNHST		;If this is some random host
	 JRST SRTO5		; output normally
	SKIPE HDRGAT		;Else may want to do the % hack.
	 JRST [	OUT(TMPC,("%"),HST(B),("@"),TZ(GATNAM))
		JRST SRTO9  ]
SRTO5:	OUT(TMPC,("@"),HST(B))
SRTO9:	POPAE P,[C,B]
	RET

; SCHECK - Set %TMP if the string in B contains special characters.
SCHECK:	PUSHAE P,[A,B,C]	; save ACs
	TRZ F,%TMP
	MOVE A,LISTAR(B)+1
	ADD A,$LSLOC(L)
	HLRZ B,A		; length
	HRLI A,440700		; B.P.
	JUMPE B,SCHK2		; the null string contains no specials
SCHK1:	ILDB C,A		; get next character
	CALL SCHK4		; test character
	 TROA F,%TMP		; character is special
	  SOJG B,SCHK1
	CAIN C,"@		; If the string ends in an atsign
	 TRO F,%TMP		; quote it (for BUG-@ to non-ITS hosts)
SCHK2:	POPAE P,[C,B,A]		; restore ACs
	RET

; SCHK4 - Skips if character in C is not special.
SCHK4:	CAIE C,"<
	 CAIN C,">
	  RET
	CAIE C,"(
	 CAIN C,")
	  RET
	CAIE C,":
	 CAIN C,";
	  RET
	CAIE C,"@
	 CAIN C,",
	  RET
	CAIE C,";
	 CAIN C,":
	  RET
	CAIE C,""
	 CAIN C,"\
	  RET
	CAIE C,"[
	 CAIN C,"]
	  RET
	AOS (P)
	RET


; STLS - Output string in B with quoting for \ and ".
STLS:	PUSHAE P,[A,B,C]	; save ACs
	MOVE A,LISTAR(B)+1
	ADD A,$LSLOC(L)
	HLRZ B,A		; length
	HRLI A,440700		; B.P.
	JUMPE B,STLS2
STLS1:	ILDB C,A
	CAIE C,""
	 CAIN C,"\
	  OUTCAL(TMPC,C("\))
	OUT(TMPC,C((C)))
	SOJG B,STLS1		
STLS2:	POPAE P,[C,B,A]
	RET

; TXTCHK - Routine to quickly munch through text looking for either ^_'s or
;	 CRLF.CRLF's, and set message-text flag (A$MTXF) appropriately. 

TXTCHK:	PUSHAE P,[A,B]
	MOVEM 16,AC16S
	MOVE 16,[1,,ACSAV]
	BLT 16,AC16S-1		;save accs
	FINDA A,[A$MTXT,,[$LLLST(L)]]
	 JSR AUTPSY
	MOVE A,LISTAR(A)+1	;cnt and ptr
	ADD A,$LSLOC(L)		;absolute addr
	HLRZ B,A
	ADDI B,4
	IDIVI B,5
	MOVN B,B
	HRL A,B			;aobjn complete.
	PUSH P,A		; Save it.
	MOVE C,[TXTCKR,,E]
	BLT C,E+TXTCKL-1
	SETZ C,
	JRST E

TXTCH3:	PUSH P,A
	PUSH P,B
	HRRZ A,A	;get addr
	ADD A,TXTCTB(B)	;get ptr to offending period
	MOVEI B,",	;get replacement char
	DPB B,A		;replace it
	POP P,B
	POP P,A
	JRST E+6	;continue

TXTCH2:	MOVE 16,[ACSAV,,1]
	BLT 16,16	;restore accs
	MOVE 16,AC16S
	POP P,A		; Restore AOBJN ptr to text.

	MOVE B,(A)	; Check for a message starting with  ".<CRLF>"
	AND B,[777777,,700000]
	CAME B,[ASCII/.
/]
	 JRST TXTC30
	MOVSI B,(ASCII/./#ASCII/,/)
	XORM B,(A)	; CLOBBER THE "." TO A "," VIA GROSS KLUDGE!


	; Loop to see if any ^_'s exist in text.
TXTC30:	MOVE B,(A)
	XOR B,[.BYTE 7 ? ^_ ? ^_ ? ^_ ? ^_ ? ^_ ]
	TLNN B,774000
	 JRST TXTC35	; Yup, found one.
	TLNN B,3760
	 JRST TXTC35	; etc
	TDNN B,[17,,700000]
	 JRST TXTC35
	TRNN B,77400
	 JRST TXTC35
	TRNN B,376
	 JRST TXTC35
	AOBJN A,TXTC30
	JRST TXTC40		; None found...
TXTC35:	MAKELN A,[A$MTXF,,[$LLLST(L)]
		%LTVAL,,0]
	MOVEM A,$LLLST(L)
TXTC40:	POPAE P,[B,A]
	RET

TXTCKR:
	OFFSET E-.	; Start following code at E.
TXTLUP::MOVEI B,5	;at e, set up char cnt
	MOVE D,(A)	;e+1  get a word from txt
TXTLP2::LSHC C,7	;e+2  shift a char into c
	TLZ C,400000	;e+3  bump off sign bit
	CAMN C,TXTCMS	;e+4  found nasty string yet? (crlf.crlf)
	 JRST TXTCH3	;e+5  arg! yes, go correct it
	SOJG B,TXTLP2	;e+6  no, shift all of new word in (eventually)
	AOBJN A,TXTLUP	;e+7  try all wds of msg txt
	JRST TXTCH2	;e+10 (15)  finally exit
TXTCMS::<ASCII /
.
/>_-1			;e+11 (16) comb. to search for, right adjusted=crlf.crlf
	OFFSET 0	; Restore normal Loc Counter.
TXTCKL==.-TXTCKR


TXTCTB:	0	;add-in ptrs to get ptr to period, period in 3rd char position
	170700,,0	;b=1, period was in 3rd pos. of d
	260700,,0	;b=2, . was in 2nd pos. of d
	350700,,0	;b=3, . in 1st pos. of d
	010677,,-1	;b=4, . in 5th pos. of c (this gives 10700,,.-1)
	100677,,-1	;b=5, . in 4th pos. of c (this gives 100700,,.-1)

BVAR
ACSAV:	BLOCK 15	;saves only acs 1-16
AC16S:	0		;need to save the blt-ptr acc. 'manually'
EVAR
SUBTTL Recipient list crunchers
; RCPSRT - sorts rcpts on a msg list by destination site, so
;	that local are 1st, and rcpts are bunched by site.  bubble sorts...

RCPSRT:	PUSHAE P,[A,B,C,D,E]

RSORT1:	SETZ A,				; Clear exch flag
	FINDA E,[A$RCP,,[$LLLST(L)]]
	 JRST RSORT7			; No rcpts at all?  Tolerate...
	FINDA C,[A$RHST,,[LISTAR(E)+1]]
	 SKIPA C,[0]
	  MOVE C,LISTAR(C)+1
	CAMN C,OWNHST
	 SETZ C,		; Make sure all local site refs are made zero

RSORT2:	MOVE B,C
	MOVE D,E			; Make previous n+1th now nth.
	HRRZ E,LISTAR(D)		; Get ptr to next
	FINDA E,[A$RCP,,[E]]		; And advance to next rcpt
	 JRST RSORT7			; Hmm, none left this pass
	FINDA C,[A$RHST,,[LISTAR(E)+1]]	; Now get ptr for n+1-th rcpt site
	 SKIPA C,[0]
	  MOVE C,LISTAR(C)+1		; Get site #
	CAMN C,OWNHST
	 SETZ C,

	; Now compare!
	CAMG B,C		; Skip to exchange if nth greater than n+1th
	 JRST RSORT2		; Else continue.

	; Aha, exchange.
	MOVE A,LISTAR(D)+1		; Get A$RCP list ptr
	EXCH A,LISTAR(E)+1		; Swap with n+1th
	MOVEM A,LISTAR(D)+1		; Ptrs now swapped!
	EXCH B,C			; And exch site #'s.
	JUMPN A,RSORT2			; Continue, unless
	JSR AUTPSY			; gross error in A$RCP LN!

RSORT7:	JUMPN A,RSORT1		; Do another pass, til nothing exchanged.
	POPAE P,[E,D,C,B,A]
	RET



; RCPSEQ - returns ptr to a rcp attrib list.  successive calls
;	after initializing will return rcps in sequence.  since list is
;	supposed to have been sorted, first are returned all rcpts with
;	lowest dest host numbers, then next lowest, etc.
;	skips on success, fails when none left

BVAR
RCSQCT:	0		; When this (current LP) zeroed, RCPSEQ initializes self.
RCSQLT:	0		;holds ptr to next LN (cdr of rcsqct)
EVAR

RCPSEQ:	SKIPN RCSQCT
	 JRST [	MOVE A,$LLLST(L) ;initialize
		MOVEM A,RCSQLT
		SETOM RCSQCT
		JRST .+1]
	SKIPG A,RCSQLT
	 RET			;0, none left.
	FINDA A,[A$RCP,,[A]]
	 RET			;none left
	PUSH P,B
	HRRZ B,LISTAR(A)	;get cdr for next call
	MOVEM B,RCSQLT
	MOVE A,LISTAR(A)+1	;return ptr to rcpt list
	POP P,B
	AOS (P)
	RET

; UHDPUT - Output user-specified header lines, verbatim. 
;	By including a USER-HEADER: line in the request file, the user program
;	sending the mail can have an arbitrary line inserted into the header.
;	This lets it send mail containing header fields of types that COMSAT
;	does not especially know about, such as In-Reply-To.  There can be any
;	number of USER-HEADER lines in a single mail request. (Here we output
;	them in reverse order so that they appear in the same order as the
;	user specified originally.)

UHDPUT:	PUSHAE P,[B,C]		;Return LP value in A.
	MOVE A,$LLLST(L)	;Get LP to message.
	SETZ B,			;Count of user-headers.
UHDP10:	FINDA A,[A$UHDR,,[A]]	;Look for a user-header SLP.
	 JRST UHDP20		; Not found.
	PUSH P,A		;Stack it up.
	AOS B			;Count it.
	HRRZ A,LISTAR(A)	;CDR ptr to next.
	JRST UHDP10		;Go after it.

UHDP20:	JUMPE B,UHDP90		;If there were any USER-HEADERs
	MOVE C,A		; stash ptr to rest of message.
UHDP21:	POP P,A			; Unwind stack of SLPs, outputting them.
	FWRITE TMPC,[TLS,A,[
]]
	SOJG B,UHDP21		; All the way.
	MOVE A,C		; Recover LP.
UHDP90:	POPAE P,[C,B]
	RET




SUBTTL Recipient Name Equivalencing

;;;   Equivalence-table hackery

; RCPEXP - Routine that attacks current LSE as a list of message attribs
;	and expands all rcpt LN's as specified by such things as EQV
;	and LSR file data.  

RCPEXP:	PUSHAE P,[A,B,C,D]
	MOVE D,$LLLST(L)	;put LP in D to step thru
	CSTAT (,("EXP-"),RABR)
	TRO F,%NOCOM		;set helping flag for stats
RCPXP1:	FINDA D,[A$RCP,,[D]]	;find rcpt from this pt
	 JRST RCPXP9		;no more, done
	MOVE C,D		; Found one.  Put LP in C, and
	HRRZ D,LISTAR(D)	; get CDR for next pass

	; C has LP to a rcpt LLN to be expanded.  Take it off attrib list,
	; and stuff resulting rcpts back on front of attrib list.

	MOVE A,LISTAR(C)+1	;get ptr to attrib list of the rcpt.
	SETZM LISTAR(C)+1	; and flush main list's pointer to it,
	LNDEL C,$LLLST(L)	; So that it's not flushed along with A$RCP LN!
	SETZM LSTSUP		;set flag to deafult to suppress listing of eqvlist
	SETZM RCPSLF		;clear forced-rcpt ptr
	SETZM RCPSLT		;and ptr to its type
	SETZM RCPSLP		;and ptr to its list qb
	SETZM RCPSLA		;and ptr to any list needing copying for descendants
	CALL RCPPUT		;crunch it onto msg attrib list!
	JRST RCPXP1		;continue...

RCPXP9:	CALL RCPSRT		;sort the resulting msg-attrib list.
	POPAE P,[D,C,B,A]
	RET

.SCALAR LSTSUP	; Flag to suppress listing of rcpts


; RCPPUT - Takes rcp attrib list, and stuffs it onto rcp-list
;	   if it checks out via equivalence table.  
;	   Has side effects such as checking for *<site> MSG 
;	   distribution specs and BUG-'s.  
;	   Also, if recipient is on ONWHS2, mutates A$RHST to OWNHST.
;	   Recursive, so must save everything on stack!

RCPPUT:	PUSHAE P,[A,B,C,D,RCPSLF,RCPSLT,RCPSLP,RCPSLA,RCPTRC]
	MOVEI B,(P)		; Saved RCPTRC
	HRL B,A
	MOVEM B,RCPTRC
	FINDA B,[A$RNAM,,[A]]
	 JSR AUTPSY
	FINDA C,[A$RHST,,[A]]	; Get host.
	 SKIPA C,OWNHST		;  If none, use OWNHST.
	  MOVE C,LISTAR(C)+1	; Else get host address.
	CAMN C,OWNHS2		; Identity crisis?
	 MOVE C,OWNHST		;  Yes, change host to connected net.
	EXCH A,B
	CAMN C,OWNHST		; Our own host?
	 CALL SLNUPR		; If rcpt on local host, make name uppercase.
	EXCH A,B
	TRZN F,%NOCOM		
	 OUTCAL(SC,(","))
	FINDA D,[A$RTYP,,[A]]	; If special rcpt type, so indicate in stats.
	 JRST RCPP20
	SLNEA D,[ASCNT [NAME]]
	 CAIA
	  JRST RCPP20
	CSTAT (,LPAR,TLS(D),(" "),TLS(B),RPAR,("@"),HN(C))
	JRST RCPP21

RCPP20:	CSTAT (,TLS(B),("@"),HN(C))	; Record name@host#
	SETZ D,			; Type is ordinary "NAME"
RCPP21:	CALL RCPE		; Rcpblk name already in rcplist?
	 CAIA			;  Yes.
	  JRST RCPP25
	SKIPE RCPSLF		; Found it. Must check parent?
	 CAME C,OWNHST		;  Must check, see if local first.
	  JRST RCPP99		;   No, ignore.
	SLNE B,RCPSLF		; Matching names?
	 JRST RCPP99		;  No, ignore.
	JUMPN D,[SKIPE RCPSLT	; Match types also
		  SLNE D,RCPSLT	;  If not same type,
		   JRST RCPP99	;   ignore.
		 JRST .+1]
	SKIPE RCPSLT		; If matched, force this rcpt into list.
	 JRST RCPP99
RCPP25:	;; From here on D is a flag: -1 => This is pseudo-recipient, expand it.
	SETZ D,			; Zero D as flag
	CAME C,OWNHST		; Local?
	 JRST RCPP10		;  No, store it and return. D=0.
RCPP30:	;; Local rcpt (ptr in A to rcpt's attrib list, in B to name-string LN)
	;; Check options and equivalency.
	PUSH P,A		; Save rcpt. attrib. list
	FINDA B,[A$RTYP,,[A]]	; Find type of rcpt
	 JRST [	SETZ B, ? JRST RCPP31]
	MOVE B,LISTAR(B)+1
	ADD B,$LSLOC(L)		; Get abs SPT or zero for type.
RCPP31:	FINDA A,[A$RNAM,,[A]]	; Now get name.
	 JSR AUTPSY
	MOVE A,LISTAR(A)+1
	ADD A,$LSLOC(L)		; Make abs SPT to that too.	
	CALL RCPEQV		; Find if on EQV list.
	 JRST [	POP P,A		;   Nope...
		JRST RCPP39]
	MOVE B,A		; Save returned pointer in B
	POP P,A			; and restore LP to rcpt's list.
	JRST RCPP60		; Go handle EQV list.

	;; Not found on EQV. (Sometimes, fake up an EQV entry
	;; and continue as if he HAD been found on EQV!)
RCPP39:	FINDA B,[A$RTYP,,[A]]	; Find its type...
	 JRST RCPP72		; None, assume NAME, go check INQUIR.
	SLNEA B,[ASCNT [NAME]]
	 CAIA			; nope.
	  JRST RCPP72		; Type NAME, go check INQUIR.

	SLNEA B,[ASCNT [BUG]]	; BUG?
	 JRST RCPP42
	CALL RXPBUG		;no option or eqv list. try BUG translation
	 JSR AUTPSY		; Should always eqv to something!!
	JRST RCPP60		; Ah so.

RCPP42:	SLNEA B,[ASCNT [@FILE]]	; Indirect through file?
	 JRST RCPP09		; Give up if not this either.
	CALL RXPAFL		; Aha, go to expand file!
	JRST RCPP6		; Make indirect-file spec a pseudo, and
				; go handle returned eqv-list in B if any.

	;; Come here when we have managed to EQV to something.
	;; Original rcpt. attrib. list in A, EQV list in B.
	;; The first order of business is to take random options at the
	;; top level of the list in B, and put them on to A.  
	;; Note: B is not the a$rcp but its car, i.e. a chain of both
	;; options and A$RCP LLN's which are EQV-LISTs.
	;; Note further that if B = 0 it means that the recipient is to
	;; be made pseudo, but not expanded.  (and likewise it has no options).

RCPP60:	JUMPE B,RCPP6		; Check for explicit flushing, jump if so.
	PUSHAE P,[B,D]
	MOVE D,$ARLOC+EQVAR	; Reference EQVAR now...
	EXCH D,L		; Save current area, reference EQVAR.
	CAIA
RCPP61:	 HRRZ B,LISTAR(B)	; Get CDR to next thing on EQV list.
	JUMPE B,RCPP69
	LDB C,[$LAFLD,,LISTAR(B)]
	CAIE C,A$RTYP		; Ignore type attribute.
	 CAIN C,A$RCP		; Ignore EQV-LISTs, they get hacked later.
	  JRST RCPP61		; 
	CAIE C,A$RNAM		; Also ignore recipient-name,
	 CAIN C,A$RHST		; and site spec.
	  JRST RCPP61
	EXCH D,L		; Restore normal LSE
	LNCOPY C,[D ? B]	; Copy from EQVAR to normal LSE.
	HRRM A,LISTAR(C)
	HRRZ A,C
	EXCH D,L		; Restore EQV for reference...
	JRST RCPP61		;continue after consing onto attr list

RCPP69:	EXCH D,L		; When done, restore right LSE reference.
	POPAE P,[D,B]
	;--------------------

; Now we have the enhanced rcpt attrib list in A, and the original
;   EQV list in B.  Now decide whether to actually do expansion or not.
; Currently this is a half hearted attempt to do "right" thing when
; some loser does :QSEND BUG-FOO.

RCPP05:	FINDA C,[A$RMDS,,[A]]	;does rcpt have send-switch?
	 JRST RCPP04
	SKIPLE LISTAR(C)+1	; has switch... always mailing too?
	 JRST RCPP04		; Yes, so expand list.
	FINDA C,[A$RTYP,,[A]]	; May not ever mail; check type
	 JRST RCPP10		; Default to NAME - don't expand list.
	SLNEA C,[ASCNT [NAME]]
	 JRST RCPP04		; Not type NAME, so DO expand list.
	JRST RCPP10		; NAME - don't expand list further.

	;get both ptr to a name's eqvlist and its options, whichever.
	;first insert options then expand eqvlist;
	;if eqv-list exists, 1)gives it attrib of psuedo-name,
	;2) if showlist attrib exists, gives it no-show.

RCPP04:	PUSH P,L
	MOVE L,$ARLOC+EQVAR	; Temporarily set LSE to EQV for search.
	FINDA C,[A$RCP,,[B]]	;see if it has eqv-list
	 JRST [	POP P,L ? JRST RCPP70]	;no, tis atomic w/options, check INQUIR
	POP P,L
	FINDA C,[A$RNAM,,[A]]	;get name of current rcpt to check with parent
	 JSR AUTPSY
	SLNE C,RCPSLF		;compare with parent
	 JRST RCPP6		;nope, not same, continue.
	FINDA D,[A$RTYP,,[A]]	;also compare the types
	 JRST .+3
	  SLNEA D,[ASCNT [NAME]]
	   JRST [ SKIPE RCPSLT
		   SLNE D,RCPSLT
		    JRST RCPP6	;not same type
		  JRST .+3 ]	;winning, skip FIND-failed case
	 SKIPE RCPSLT
	  JRST RCPP6		;not same type
	SETZM RCPSLF		;ah, same! parent refers to self...clear,
	MOVE D,RCPSLP		;get ptr to parent's A$RCP,
	MOVE D,LISTAR(D)+1	;get its list ptr to attribs,
	MAKELN C,[A$ROPT,,0	;and make a flag LN to add on, such that
		%LTSTR,,[LITSTR [NO-CLI]]] ;it means "no cli" for this name.
	LNAPP [D ? C]		; Append onto its attrib list.
	SETZM RCPSLP		;clear ptr used just in case (like rcpslf)
	SETZ D,			;indicate atomic
		;and go store the rcpt as atomic, with given options.
		;if found again, won't be stored again since rcpslf cleared.

	; Come here for an atomic rcpt which needs to be checked out
	; in the INQUIR database.
RCPP70:	FINDA C,[A$RTYP,,[A]]	; Here when rcpt was seen in EQV; "known".
	 JRST RCPP71		; Must make sure that it's type NAME.
	SLNEA C,[ASCNT [NAME]]
	 JRST RCPP09		; Nope, don't check INQUIR!!
RCPP71:	SKIPA B,[-1]		; Indicate to RXPNAM that name was in EQV.
RCPP72:	 SETZ B,		; Here when rcpt not in EQV - "unknown".
	CALL RXPNAM		; Check it out.
	 JRST RCPP09		; Now truly atomic, store it.
	JRST RCPP60		; Hmm, got eqv'd! Go process returned eqv-list.

RCPP6:	JUMPE B,[CSTAT (,("::={IGNORED}"))	; Pseudo-ing without eqv'ing.
		JRST RCPP6Y]
	CSTAT (,("::="),LPAR)
	TRO F,%NOCOM		;don't output comma in next stat stuff
RCPP6Y:	SETO D,			;set flag to process eqv-list later.
	MAKELN A,[A$RPSN,,[A]	;indicate rcp is psuedo - attach pseudo attrib
		%LTVAL,,[JUNK]]
	FNDSTR C,A,A$ROPT,[SHOWLIST]	;does psuedo have showlist option?
	 JRST RCPP10		;no, means show psuedoname, suppress its list
				;yes, don't show psuedo (but show list)
	JRST RCPP11		;skip any duplicate noshow option

	;; Store attrib list for this rcpt on rcp list
RCPP09:	SETZ B,			; No EQV list.
RCPP10:	SKIPE LSTSUP		; Hack suppression option.
RCPP11:	 MAKELN A,[A$ROPT,,[A] ? %LTSTR,,[LITSTR [NOSHOW]]]
	SKIPE RCPSLA				;anything need copying onto it?
	 JRST [	LNCOPY C,[0 ? SETZ RCPSLA]
		LNAPP [A ? C]			; Copy & append on.
		JRST .+1]
	MOVEM A,RCPTR				;save for later use
	MAKELN A,[A$RCP,,[$LLLST(L)]		;make list LN for it and plug on list
		%LTLST,,[A]]
	MOVEM A,$LLLST(L)			;now officially on list.

	AOS RCPNUM		;increment # of names in area
	JUMPN D,RCPP50		;if d=-1 then psuedo, don't increment rrcps,hack further.
	AOS RRCPS		;increment # of atomic recipients
RCPP99:	POPAE P,[RCPTRC,RCPSLA,RCPSLP,RCPSLT,RCPSLF,D,C,B,A]
	RET

.SCALAR RCPTR	; Temp var

	;here, handle a psuedo-name. b contains ptr to eqv list.
RCPP50:	JUMPE B,RCPP99			; ignore totally if nothing.
	MOVEM A,RCPSLP			;store ptr to pseudo's A$RCP LN.
	MOVE C,RCPTR			;get ptr to attrib-list back.
	FINDA D,[A$RNAM,,[C]]
	 JSR AUTPSY
	MOVEM D,RCPSLF			;store ptr to name of parent of this list.
	SETZM RCPSLT			;also store type (ptr or zero if NAME)
	FINDA D,[A$RTYP,,[C]]
	 CAIA
	  JRST [ SLNEA D,[ASCNT [NAME]]
		  MOVEM D,RCPSLT
		 JRST .+1 ]
	FINDA D,[A$RMDS,,[C]]		;see if A$RMDS attrib exists, if so must
	 JRST RCPP51			;  ensure that it gets copied for descendants.
	LNCOPY D,[0 ? D]
	HRL D,RCPSLA			;push onto RCPSLA list.
	HLRM D,LISTAR(D)
	HRRZ D,RCPSLA
RCPP51:	FINDA D,[A$RMDM,,[C]]		;see if A$RMDM attrib exists, if so must
	 JRST RCPP53			;  ensure that it gets copied for descendants.
	LNCOPY D,[0 ? D]		;just like A$RMDS
	HRL D,RCPSLA			;push onto RCPSLA list etc
	HLRM D,LISTAR(D)
RCPP53:	SKIPE LSTSUP			;under suppression?
	 JRST RCPP52			;yes, don't bother trying to suppress again
	FNDSTR C,C,A$ROPT,[SHOWLIST]	;supposed to show list?
	 JRST [	SETOM LSTSUP
		CALL RCPEXT		;no, hence set 'noshow' for expansion items
		SETZM LSTSUP		;restore
		JRST RCPP55]

RCPP52:	CALL RCPEXT		;takes arg in a, puts the eqv blocks into rcp list.
RCPP55:	CSTAT (,RPAR)
;	SKIPE A,RCPSLA		;if a list was being copied from RCPSLA,
;	 CALL QBFREE		;free it. (but not yet.. this will lose by clobbering
				;parent's version of RCPSLA!)
	JRST RCPP99		;(note rcpext calls rcpput recursively!)

BVAR
RCPSLF:	0	;if non-z holds ptr to name which (if found) is to be forced into list
		;irregardless of previous existence or psuedoness.
RCPTRC:	0	;linked list of A$RCP LN,,thread for rcpt expansion recursion
RCPSLT:	0	;ptr to type of name in RCPSLF (zero if type=NAME)
RCPSLP:	0	;holds ptr to A$RCP LN of above pseudo.
RCPSLA:	0	;holds ptr to list of stuff which needs copying onto attrib list of
EVAR		;descendants for a rcpt. (if it is eqv'd)

; RCPEXT - takes LP to eqvlist in B, inserts all the A$RCP's into the
;	attrib list.  Mainly involves copying.

RCPEXT:	PUSHAE P,[A,B,C]
	MOVE C,$ARLOC+EQVAR	; Set up for swapping LSE's
	EXCH C,L		; L gets EQVAR, C gets ex-current 
	CAIA
RCPXT1:	 HRRZ B,LISTAR(B)	; CDR to next thing on EQV list
	FINDA B,[A$RCP,,[B]]	;pluck one off list
	 JRST RCPXT9		;done when no more
	EXCH C,L		; Get normal LSE back
	LNCOPY A,[C ? SETZ LISTAR(B)+1]	; Copy what EQV-rcpt points to and leave LP in A
	CALL RCPPUT		;and put it in. (yup, corecursive)
	EXCH C,L		; Get EQV back.
	JRST RCPXT1

RCPXT9:	EXCH C,L		; Restore normal LSE.
	POPAE P,[C,B,A]
	RET


; Set SHITSW==1 to use HSNAME hackery.
IFNDEF SHITSW,SHITSW==1

IFE SHITSW,[
; The RXPNAM routine is called when sending to a NAME-type rcpt who is
;   not found in the EQV.  The algorithm is:
; 0) If the name has > 6 characters, goto 3.
; 1) If he is a UNAME in the LSR file,then
;   A)	If he has has a net address field, translate to that,
;   B)  else just send to him.
; 2) If he has a file in (INIT), a file in _MSGS_, or a directory,
;	then send to him.
; 3) Look him up as a lastname in LSR1.  If found,  translate to all
;	entries in LSR1 with that last name.
; 4) Else sender probably mistyped.  Send the mail, but also send a warning
;	to the sender and to "FAILED".
] ;END IFE SHITSW
IFN SHITSW,[
COMMENT |
	The RXPNAM routine is called during equivalence-expansion of
the recipient list of a message, for each NAME-type rcpt
which is not found in the EQV file, so as to determine ultimate
mailbox name.  Algorithm is:

[1] If name has > 6 chars, it can't be a UNAME, so:
	1a. If name exists as a lastname in LSR, return eqv-list for it.
	1b. Recipient unknown, send error message.  Note no attempt to
		send it anywhere.
[2] If UNAME is not in LSR file, then
	2a. If directory exists for that UNAME, return OK.
	2b. If UNAME exists as a lastname in LSR, return eqv-list for it.
	2c. Find default HSNAME, append to rcpt list.
	2d. See if any traces of loser (init files etc), if so return OK.
		Else jump to [8] to send error message.
[3] UNAME is in LSR; if it has valid net-addr entry, return eqv-list for it.
[4] If directory exists for that UNAME, return OK.
[5] If LSR entry has "Fildir" for local machine, set that as HSNAME, return OK.
[6] Determine default HSNAME using tables, append to rcpt list, & return OK.
  ------
[8] Recipient is totally unknown.  Unless msg is send-only, or
	error msg already sent at [1b], send error message then return OK.

|] ;END IFN SHITSW

; If it skips, then B contains the EQV stuff.  Else just send the message.

BVAR
RXPNRL:	0		; LLP to rcpt list.
RXPNSP:	0		; SLP to rcpt name
RXPNSL:	0		; # chars in name
RXPNS6:	0		; SIXBIT name of rcpt, even if > 6 letters in name.
RXPNEA:	0		; LSR entry addr if any.
RXPNEX:	0		; -1 if exists in EQV, 0 otherwise.
EVAR

RXPNAM:	PUSHAE P,[RXPNEX,RXPNEA,RXPNRL,RXPNSP,RXPNSL,RXPNS6,C,D,A]
	MOVEM A,RXPNRL
	MOVEM B,RXPNEX		; Save EQV-existence argument.
	SETZB D,RXPNS6		; Initialize.
	SETZM RXPNEA
	FINDA A,[A$RNAM,,[A]]
	 JSR AUTPSY
	MOVEM A,RXPNSP
	MOVE A,LISTAR(A)+1
	ADD A,$LSLOC(L)
	HLRZM A,RXPNSL		; Save char cnt of name
	CALL CVT76C
	MOVEM A,RXPNS6		; Save SIXBIT UNAME.

	; Setup finished, start the algorithm.
	MOVE B,RXPNSL		; STEP 1: Get # of chars in name.
	CAIG B,6		; If > 6, try lastname check.
	 JRST RXPN20		; else can skip straight to uname check.

	CALL RXPLNM		; Try looking up name as lastname...
	 JRST RXPN90		; Successful, return eqv-list in B.
	SETZM RXPNS6		; Ugh, zap 6bit so RXPNER does right thing.
	CALL RXPNER		; Sigh, send msg saying failed.
	SETZ B,			; And return with B=0 to indicate
	JRST RXPN90		; it's considered pseudo, but doesn't expand.

RXPN20:	MOVE B,RXPNS6		; STEP 2: looks up as UNAME in LSR1.
	MOVEI A,LSR1C
RXPN21:	CALL LSRTNS"LSRUNM
	 CAIA
	  JRST RXPN30		; Found as UNAME, go hack LSR entry.
	JUMPE B,RXPN25		; Not found
	CAIA			; Wait for core to be available
	 JRST RXPN21		; Go CORBLK sleeping...
	JSR CORLOS

RXPN25:	MOVE A,RXPNS6		; STEP 2A: Not in LSR file, does it
	CALL HASDIR		; have a directory?
	 CAIA
	  JRST RXPN99		; Yes, return OK, will write to that dir.
	CALL RXPLNM		; STEP 2B: check to see if lastname.
	 JRST RXPN90		; Yes, return eqv-list in B!
	MOVE A,RXPNS6		; No, so on to
				; STEP 2C: Look for any record of existence.
IFN SHITSW,[
	SETZ B,
	CALL RXPNHS		; Find HSNAME & append to rcpt list.
	 JRST RXPN80		; Failed????
]
	CALL HASREC		; Hunt for clues that user exists.
	 JRST RXPN80		; Failed, send error message.
	JRST RXPN99		; Found loser's grimy tracks, so assume exists.


	; STEP 3: Here when LSRTNS"LSRUNM has just won, i.e. loser exists
	; in LSR file.  Look for a NET ADDRESS.
RXPN30:	MOVEM B,RXPNEA		; Save LSR entry address.
	MOVEI A,LSRTNS"I$NETA
	CALL LSRTNS"LSRITM	; Try to find the net address.
	 JRST RXPN40		; None, so go try fildir.
	MOVE B,A		; Save arg to RNETAD.
	CALL LBPASZ		; Get the length of the ASCIZ string.
	JUMPE A,RXPN40		; If entry is null, ignore it.
	MOVE C,RXPNSP
	MOVE C,LISTAR(C)+1
	ADD C,$LSLOC(L)		; Last arg is ASCNT to rcpt name.
	PUSH P,L
	MOVE L,$ARLOC+EQVAR	; Do any CONSing in EQVAR.
	CALL RNETAD		; Try to parse it.
	 SETZ B,		; If loser has bad field, just ignore it.
	JUMPE B,[POP P,L	; Jump if no net address.
	 	JRST RXPN40]

	;; Copy only 1 rcpt!!  This hack is necessary due to inter-machine
	;; INQUIR which can deliver a netaddr with two sites to BOTH sites
	;; in question, causing infinite loop! 
	;; so, restricted to 1 site per net address.
	HRRZ A,LISTAR(B)	; Is there more than 1 rcpt in list?
	JUMPE A,RXPN36		; Nope, no hair.
	HLLZS LISTAR(B)		; Ugh, yes, disconnect 1st from rest of list
	LNDEL A			; and delete rest of list.
RXPN36:
	;; Must check for netadr exactly same as name, so that can
	;; go check directory etc; if we just handed back to EQV rtns,
	;; duplicate would be suppressed & we'd never come back to RXPNAM.
	FINDA C,[A$RHST,,[LISTAR(B)+1]]
	 SKIPA A,OWNHST		; If no host, using local; OK.
	  MOVE A,LISTAR(C)+1	; Get right host #
	CAMN A,OWNHS2		; Identity crisis?
	 JRST [	MOVE A,OWNHST	;  Yes, change host to connected net.
		MOVEM A,LISTAR(C)+1
		JRST .+1 ]
	CAME A,OWNHST		; If non-local host, return to EQV.
	 JRST RXPN39
	FINDA C,[A$RNAM,,[LISTAR(B)+1]]	; local, so check name.
	 JSR AUTPSY
	MOVE C,LISTAR(C)+1
	ADD C,$LSLOC(L)		; Get abs ascnt ptr to new rcpt name string.
	POP P,L			; now can restore LSE
	MOVE A,RXPNSP		; and can use SLP to old rcpt name
	USLNEA A,C		; and compare strings.
	 JRST RXPN90		; If not equal, off to return EQV list.
	PUSH P,L		; Equal!  Must ignore...
	MOVE L,$ARLOC+EQVAR	; Point back at LSE we consed the list in.
	LNDEL B			; and flush it.
	POP P,L			; And go to check out directory, etc.
	JRST RXPN40

RXPN39:	POP P,L
	JRST RXPN90		; Win return.

	; STEP 4: Check for a file directory.
RXPN40:	MOVE A,RXPNS6		; Get 6bit
	CALL HASDIR		; Has a directory?
	 CAIA
	  JRST RXPN99		; Yes, return OK (will write to this dir).


	; STEP 6: Determine default HSNAME when no netaddr specified, but
	; user exists in INQUIR data.
RXPN60:
IFN SHITSW,[
	MOVE A,RXPNS6
	MOVE B,RXPNEA
	CALL RXPNHS		; Eeny, meeny, miny, mo!
	 JFCL			; Ignore lossage.
	JRST RXPN99		; And return OK.
];SHITSW

	; Here we have lost totally.  We must send a warning message to the
	;    sender, and perhaps also to the dead-letter box. (STEP 4).
	; But first, check some stuff and maybe punt the warning.

RXPN80:	SKIPE RXPNEX		; Perhaps name existed in EQV?
	 JRST RXPN99		; Yes, so technically "known"...
	MOVE A,RXPNRL		; Get back ptr to rcpt's list.
	CALL SETMSM		; Decode MAIL/QSEND modes.
	SKIPE SORMSW
	 SKIPLE SENDSW
	  CAIA
	   JRST RXPN99		; Not really mailing, don't bother.
	TRNE MF,M%CORG		; One other thing...did we originate the msg?
	 JRST [	STAT (,TAB,("COMSAT-originated msg LOST TO "),TLS(RXPNSP),EOL)
		JRST RXPN99]	;  and do nothing if so.
	MOVE A,RXPNSL
	CAILE A,6
	 JRST RXPN82		; (Ignore this check if name > 6 chars.)
	MOVE A,RXPNS6		; Get the SIXBIT name.
	CALL USRFND		; Is he logged in at the moment?
	 CAIA			; No, continue.
	  JRST RXPN99		; Do common-sense thing, just send...

	; Here we are fully committed to sending warning, etc.
RXPN82:	CALL RXPNER		; Send error message.
	CAIA
RXPN90:	 AOS -11(P)
RXPN99:	POPAE P,[A,D,C,RXPNS6,RXPNSL,RXPNSP,RXPNRL,RXPNEA,RXPNEX]
	RET

; RXPNER - Auxiliary for RXPNAM that sends error message
;	warning sender that recipient name is unknown.

.SCALAR RCHKTM,RXPNH6	; Temp stuffs

RXPNER:	MAKELN B,[A$RNK,,0
		%LTVAL,,0]
	LNAPP [RXPNRL ? B]	; Append "not known" attrib to rcpt's list.
	FINDA B,[A$RHSN,,[RXPNRL]]	; See if HSNAME carried along.
	 SKIPA B,[SIXBIT /COMMON/]	; If not, use default.
	  MOVE B,LISTAR(B)+1		; Uh huh, get it.
	MOVEM B,RXPNH6			; Save in temp variable.
	OUT(TMPC,OPEN(UC$SAO),(|"|),SL(RXPNSP),(|" at |),TZ(OWNNAM),(" is an unknown recipient."))
	SKIPE B,RCPTRC			; Backtrace of rcpt expansion available?
	 SKIPN B,(B)			; Skip the innermost (self)
	  JRST RXPNR1
	OUT(TMPC,EOL,("    This name came from the expansion of
    "))
RXPNR0:	HLRZ A,B
	OUT(TMPC,CALL(NSRNMO))		; Output rcpt name
	SKIPN B,(B)			; Get next
	 JRST RXPNR1
	OUT(TMPC,(", from "))
	JRST RXPNR0

RXPNR1:	SKIPE RXPNS6			; If name not too long, will try anyway
	 OUTCAL(TMPC,EOL,TAB,(|Will try sending to the file "|),6F(RXPNH6),(";"),6F(RXPNS6),(| MAIL".|))
	MAKELN B,[A$RRMG,,0 ? %LTSAO,,]	; Pack up output into a LN
	LNAPP [RXPNRL ? B]		; Append onto list of rcpt.
	AOS NRXPER			; Increment cnt of errors.
	RET


; RXPLNM - Auxiliary to RXPNAM, sees if rcpt name is a lastname and
;	if so conses up an eqv-list for it.  References RXPNSP,
;	clobbers A,B,C,D.  Returns eqv-list ptr in B and DOESN'T skip if
;	successful.

RXPLNM:	SAOBEG TMPC,
	OUT(TMPC,TLS(RXPNSP))	; Copy name,
	OUT(TMPC,TC([5,,[0]]))	; followed by 5 nulls to ensure fully asciz'd.
	MAKELN C,[0 ? %LTSAO,,]	; Make SLN of it.
	MOVE A,LISTAR(C)+1
	ADD A,$LSLOC(L)		; get abs ascnt to string,
	CALL UPPRZ		; uppercasify it,
	HRRZ B,A		; and give location as arg to LSRLNM.
	MOVEI A,LSR1C
	CALL LSRTNS"LSRLNM
	 JRST [	LNDEL C,	; Too bad.  Make sure temp SLN flushed.
		AOS (P)		; and take failure (skipping!) return.
		RET]
	LNDEL C,

	; Now B/ AOBJN to words with file addresses of LSR1 entries.
	PUSH P,L		; CONS up fake EQV in EQVAR!
	MOVE L,$ARLOC+EQVAR
	MOVE C,B
RXPLN3:	MOVEI A,LSR1C
	HRRZ B,(C)		; Get address in file of this entry.
	CALL LSRTNS"LSRGET	; Returns core address in B.
	 JSR AUTPSY
	MOVEI A,LSRTNS"I$UNAM		; We want the UNAME.
	CALL LSRTNS"LSRITM	; Gets B.P. in A to string.
	 JSR AUTPSY
	MOVE B,A		; Save the BP in B.
	CALL LBPASZ		; Get length of ASCIZ string in A.
	HRLZ A,A
	HRRI A,B		; A / <# chars>,,[BP to string]
	MAKELN A,[A$RNAM,,0	; Make an SLP of the name,
		%LTBPT,,[A]]
	MAKELN D,[A$RCP,,[D]	;    and CONS it onto the list.
		%LTLST,,[A]]
	AOBJN C,RXPLN3
	MOVE B,D		; And return the EQV stuff in B.
	POP P,L
	RET		; Win return doesn't skip!

IFN SHITSW,[
; RXPNHS - RXPNAM utility; Append HSNAME attrib onto rcpt list.
;	A/ UNAME
;	B/ <LSR entry addr>	; if 0 assumes tourist.
; Skips on success, with HSNAME in B.

RXPNHS:	PUSHAE P,[C,D]
	SETZ C,
	MOVEI D,DKIC		; Use this as temp chan if need to.
	CALL LSRTNS"LSRHSN	; Look up HSNAME, hacking fildir etc.
	 JRST RXPNH9
	MAKELN C,[A$RHSN,,0	; Got it in D, store in a LN.
		%LTVAL,,[D]]
	LNAPP [RXPNRL ? C]	; Append "Hsname" attrib to rcpt's list.
	AOS -2(P)
RXPNH9:	POPAE P,[D,C]
	RET
]

SUBTTL Auxiliary routines for RXPNAM: RNETAD, HASREC, etc.

; RNETAD - Routine given ASCNT ptr in C to a rcpt name, with
;	count in A, BP in B to a string
;	which is to be parsed as an INQUIR NETADR field,
;	i.e. a standard rcpt list with exception that
;	names without @'s are taken to be site names!
; Skip returns in B a LP to list of whatever rcpts result,
;	consed in the current LSE.  If B/ 0, no rcpts.

RNETAD:	PUSHAE P,[A,C,D,E]
	CALL RCPGBA		; Enter RCPGOB at special place.
	CAIE B,0
	 JRST RNTD95		; Return unsuccessfully - error.
	JUMPE A,[SETZ B,
		JRST RNTD90]	; Nothing there?  Succeed anyway.
	MOVE B,A		; Aha, save stuff
	MOVE D,A		; Keep current in D
	CAIA
RNTD10:	 HRRZ D,LISTAR(D)
	JUMPE D,RNTD90
	MOVE A,LISTAR(D)+1	; Get LP to rcpt's list
	MOVE E,LISTAR(A)	; Get 1st wd of 1st LP pointed to
	TRNE E,-1		; Is there a CDR, i.e. more than one LN?
	 JRST RNTD10		; Yes, it must be OK.
	LDB E,[$LAFLD,,E]	; Only one!  Make sure it's an RNAM...
	CAIE E,A$RNAM
	 JSR AUTPSY		; Glub? EQRCPL returned no-name rcpt??
	CALL HMATCH		; Try to get site #.
	 JRST RNTD95		; Uh-oh, error!
	PUSH P,B
	CAMN A,OWNHST
	 JRST [	HRRZ B,LISTAR(B)+1	;Just points to self,
		JUMPE B,.+1		;don't count (makes JPG happy)
		POP P,B
		JRST RNTD95 ]
	POP P,B
	LNDEL LISTAR(D)+1	; Flush the list for this rcpt
	MAKELN A,[A$RHST,,0 ? %LTVAL,,[A]]	; Make a RHST LN and
	MAKELN A,[A$RNAM,,[A]	; plug onto a new RNAM LN, using name given.
		%LTSTR,,[C]]
	MOVEM A,LISTAR(D)+1	; And make THAT the rcpt list!
	JRST RNTD10		; Back for another.

RNTD90:	AOS -4(P)
RNTD95:	POPAE P,[E,D,C,A]
	RET


; HASREC - Takes 6bit name in A, tries to find evidence of presence
;	on system by looking for file dir, INIT file, _MSGS_ file.
;	B/ HSNAME (only if SHITSW on)
;	Skips if found something.
;	Note: err codes 23 (File locked), 27 (Link depth exceeded)
;	and 47 (Link to non-ex file) all imply some kind of existence,
;	but no others.

HASREC:	PUSH P,C
IFE SHITSW,	MOVE C,[-3,,[FILINI ? FILDIN ? FILMSG]]
IFN SHITSW,	MOVE C,[-1,,[FILHSN]]
HASRC2:	ECALL @(C),[[23,HASRC7],[27,HASRC7],[47,HASRC7]]
	 CAIA
	  JRST HASRC7
	AOBJN C,HASRC2
	PJRST POPCJ	; Lose, none worked.

HASRC7:	.CLOSE DKIC,	; Win, found something.
	PJRST POPCJ1

IFN SHITSW,[
FILHSN:	SETZ ? SIXBIT/OPEN/ ? [DKIC] ? ['DSK,,0]
	A ? [SIXBIT />/] ? SETZ B
]
FILINI:	SETZ ? SIXBIT/OPEN/ ? [DKIC] ? ['DSK,,0]
	A ? [SIXBIT />/] ? SETZ [SIXBIT /(INIT)/]
FILDIN:	SETZ ? SIXBIT/OPEN/ ? [DKIC] ? ['DSK,,0]
	A ? [SIXBIT />/] ? SETZ [SIXBIT /DDTINI/]
FILMSG:	SETZ ? SIXBIT/OPEN/ ? [DKIC] ? ['DSK,,0]
	[SIXBIT /_MSGS_/] ? A ? SETZ [SIXBIT /_MSGS_/]

; HASDIR - Takes sixbit name in A, skips if has a file dir.

HASDIR: .CALL FILDIR
	 RET	; Any error means nope.
	.CLOSE DKIC,
	AOS (P)
	RET

FILDIR:	SETZ ? SIXBIT/OPEN/ ? [DKIC] ? ['DSK,,0]
	[SIXBIT /.FILE./] ? [SIXBIT/(DIR)/] ? SETZ A

; RXPAFL - Routine to hack a name of type @FILE, which reads
;	specified file and interprets as a distribution list.
;	Errors are put into an A$RRMG SLN for the indirect-file rcpt spec.
;	Shares routines with EQVGOB!!
;	A/ LLP to rcpt attrib list
; Returns
;	B/ LLP to list of rcpt LLNs (an EQV list), or 0 if none.

RXPAFL:	PUSHAE P,[A,C,D]
	MOVE C,A		; Save ptr
	FINDA A,[A$RNAM,,[C]]
	 JSR AUTPSY
	MOVE A,LISTAR(A)+1
	ADD A,$LSLOC(L)
	MOVE B,[LMFDEF,,LMFDEV]
	CALL FILPAR
	MOVEI A,LMFDEV
	CALL OPNFLI
	 JRST [OUT(TMPC,OPEN(UC$SAO),("Could not open the file "),6F(LMFDEV),(":"),6F(LMFDIR),(";"),6F(LMFFN1),(" "),6F(LMFFN2),( "because "),ERR)
		SETZ A,		; Say no list to return.
		JRST RXPA70]

	MOVSI A,IMSAR
	CALL TXFIN		; Read the file into IMSAR
	MOVEI A,IMSAR
	PUSH P,L
	MOVE L,$ARLOC+EQVAR	; Parse the file contents into EQV list area
				; (this eventually causes gronkage, so
				; maybe change someday to use temp list area?)
	CALL RCPGOB		; Gobble up the rcpts!
	POP P,L			; Restore list area for rcpt attribs
	JUMPE B,RXPA90		; If no errors, can just return!

	; If any errors, preserve them.
	OUT(TMPC,OPEN(UC$SAO),("------ "),D(B),(" error"))
	CAILE B,1
	 OUTCAL(TMPC,("s"))
	OUT(TMPC,(" trying to parse the file "),6F(LMFDEV),(":"),6F(LMFDIR),(";"),6F(LMFFN1),(" "),6F(LMFFN2),EOL,TA(ETXTAR),("  ------"))

	; There was an error processing this @FILE.
RXPA70:	MAKELN B,[A$RRMG,,0 ? %LTSAO,,]	; Pack up output into a LN.
	LNAPP [C ? B]			; Append onto rcpt's attrib list.
	AOS NRXPER			; Increment count of errors.

RXPA90:	MOVE B,A			; Return parsed list if any.
	POPAE P,[D,C,A]
	RET

; RCPE - Find recipient on EQV.
;    A/ ptr to rcpt attrib list
; Doesn't skip if it finds another rcpt attrib list with same A$RNAM,
; A$RTYP, and A$RHST.  (A$RNAM forced to uppercase unless A$RHST is Multics)
; Skips if unique, i.e. didn't find.

.SCALAR RCPECT	; Flag for type of compare

RCPE:	PUSHAE P,[A,B,C,D,E]
	MOVE B,A		;save ptr in b
	FINDA C,[A$RHST,,[B]]	;ptr to site #
	 SKIPA A,OWNHST
	  MOVE A,LISTAR(C)+1	;get host #
	MOVE D,A		;save host number for compare later
	SETOM RCPECT		;If Host is Multics, compare type -1=no uc force
	CALL NHMLTX		;Skip if host is Multics
	 SETZM RCPECT		;set compare type 0=uc force
	MOVE A,B		;restore A in case used below
	FINDA C,[A$RNAM,,[B]]	;ptr to name
	 JSR AUTPSY
	FINDA E,[A$RTYP,,[B]]	;ptr to type
	 TDZA E,E		;zero if type is missing
	  JRST [ SLNEA E,[ASCNT [NAME]]	;or NAME
		  JRST .+1
		SETZ E,
		JRST .+1 ]
	SETZM RCSQCT		;initialize rcpseq
RCPE1:	CALL RCPSEQ		;get ptr to a rcpt attr-list
	 JRST RCPE8		;won, didn't find
	FINDA B,[A$RHST,,[A]]	;get ptr to host
	 SKIPA B,OWNHST
	  MOVE B,LISTAR(B)+1	;get #
	CAME B,D		;compare
	 JRST RCPE1		;diff.
	FINDA B,[A$RTYP,,[A]]	;compare types
	 JRST [	JUMPE E,RCPE2	;both missing (default to NAME)
		JRST RCPE1 ]	;unequal
	JUMPE E,[ SLNEA B,[ASCNT [NAME]]
		   JRST RCPE1	;unequal
		  JRST RCPE2 ]	;equal
	SLNE B,E
	 JRST RCPE1		;unequal
RCPE2:	FINDA B,[A$RNAM,,[A]]	;ptr to name
	 JRST RCPE1
	SKIPE RCPECT		;skip if uc force.
	 JRST [	SLNE B,C	;no, avoid uppercase force
		 JRST RCPE1	;unequal
		JRST RCPE9]	;equal
	USLNE B,C		;compare strings, with uppercase force
	 JRST RCPE1		;no match, keep looking
	JRST RCPE9		;match, return w/o skipping (not unique)

RCPE8:	AOS -5(P)		;unique
RCPE9:	POPAE P,[E,D,C,B,A]	;not unique
	RET

; RCPEQV - takes ASCNT ptr in A to rcpt name, ASCNT ptr in B to rcpt type
;	(or 0 for default normal) and searches EQV-list for match.
;	If finds entry, skips with LP in A to its list.

RCPEQV:	PUSHAE P,[B,C,D,E,L]
	SKIPN $AROPN+EQVAR	; Ensure open.
	 CALL EQVGET
	MOVE L,$ARLOC+EQVAR	; Make it current LSE
	JUMPE B,[SETZ D,
		MOVE B,[ASCNT [NAME]]
		JRST RCPEQ0]
	HLRZ D,B		; See if type is NAME.
	CAIN D,4
	 JRST [	MOVE D,(B)
		TRZ D,377
		CAME D,[ASCII/NAME/]
		 JRST .+1	; nope.
		SETZ D,
		JRST RCPEQ0]	; Yep...
	SETO D,
RCPEQ0:	HRRZ E,$LLLST(L)	;get ptr to eqv list
	CAIA
RCPEQ1:	 HRRZ E,LISTAR(E)	; Get CDR to next to try.
	JUMPE E,RCPEQ9		; None left? Fail.
	FINDA C,[A$RNAM,,[LISTAR(E)+1]]	;find ptr to name
	 JRST RCPEQ1		; No name? Oh well
	USLNEA C,A		; Compare strings
	 JRST RCPEQ1		; No match, try another.
	FINDA C,[A$RTYP,,[LISTAR(E)+1]]	; Else find type
	 JRST [JUMPE D,RCPEQ7	; If no type, win if desired is NAME.
		JRST RCPEQ1]
	USLNEA C,B		; Compare types.
 	 JRST RCPEQ1		; Uh-oh, not same type.
RCPEQ7:	MOVE A,LISTAR(E)+1	; Match! get ptr to all of eqv list
	AOS -5(P)
RCPEQ9:	POPAE P,[L,E,D,C,B]
	RET

; RXPBUG - kludge routine, called when BUG type rcpt has no entry in eqv table.
;	Conses up an extra rcpt that points to (BUG RANDOM-PROGRAM).
;	This ensures all BUG's wind up someplace...

RXPBUG:	PUSHAE P,[C,D,L]
	MOVE B,L		; Save LSE pointer in B for LNCOPY later.
	FINDA C,[A$RNAM,,[A]]	; First make sure we aren't recursing.
	 JSR AUTPSY
	SLNEA C,[ASCNT [RANDOM-PROGRAM]]
	 CAIA
	  JRST [SETZ B, ? JRST RXPB90]	; Yikes, recursing!  Just ignore...
	MOVE L,$ARLOC+EQVAR	; All stuff must be CONSed in EQVAR.
	MOVE D,BUGHST		; Are we the magic host on which most program
	CAME D,OWNHS2
	CAMN D,OWNHST		;   maintainers reside?
	 JRST RXPB50		; Yes, send to (BUG RANDOM-PROGRAM)
	LNCOPY C,[B ? C]	; No, forward the mail to that host.
	MAKELN C,[A$RHST,,[C]
		  %LTVAL,,[BUGHST]]
	MAKELN C,[A$RTYP,,[C]
		  %LTSTR,,[LITSTR [BUG]]]
	JRST RXPB60		; Cons up A$RCP and return.

RXPB50:	MAKELN C,[A$RTYP,,0	; This is the host.  Forward to various people.
		  %LTSTR,,[LITSTR [BUG]]]
	MAKELN C,[A$RNAM,,[C]
		  %LTSTR,,[LITSTR [RANDOM-PROGRAM]]]
RXPB60:	MAKELN B,[A$RCP,,0
		  %LTLST,,[C]]
RXPB90:	AOS -3(P)
	POPAE P,[L,D,C]
	RET

;; Kludge -- magic host address which can deal with BUG-RANDOM-PROGRAM
BUGHST:	HN$MC				; MC.LCS.MIT.EDU this week...

SUBTTL RXPERR - send error message after expansion/header.

; RXPERR - Scans through MSG LSE and generates an error message
;	addressed to sender, incorporating all errors encountered
;	during rcpt expansion and header generation.  Should
;	only be called if NRXPER (# errs seen) is positive.
;	Errors are stored in A$RRMG attribs of rcpts.
;	The way to express an error, but not send anything, is to
;	make the rcpt pseudo; such rcpts will be converted to A$RDON.

RXPERR:	PUSHAE P,[A,B,C,D,E]
	UARPUSH EMSGAR		; Might as well be robust.
	SKIPA E,$LLLST(L)
RXPE10:	 HRRZ E,LISTAR(E)
	FINDA E,[A$RCP,,[E]]	; Find a rcpt.
	 JRST RXPE70		; No more?  Go hack rest of msg.
	FINDA A,[A$RRMG,,[LISTAR(E)+1]]	; Has err msg?
	 JRST RXPE10		; Nope, get another rcpt.

	; Have rcpt with err msg...
	SKIPN $ARLOC+EMSGAR	; If nec, open EMSGAR LSE and chan thereto.
	 OUTCAL(TMPC,CALL(EMSOPN),("============ A copy of your message is being returned, because: ============"),EOL)
	OUT(TMPC,SL(A),EOL)	; Simply output the text.
	FINDA A,[A$RPSN,,[LISTAR(E)+1]]	; Is this "rcpt" a pseudo?
	 JRST RXPE10		; No, just leave alone.  Get another...
	MOVEI A,A$RDON		; Yes, so smash it right here...
	DPB A,[$LAFLD,,LISTAR(E)]	; Smash!
	JRST RXPE10		; Now go get another.

RXPE70:	SKIPN $ARLOC+EMSGAR
	 JRST RXPE90		; No message at all, just return.
	FINDA A,[A$MTXT,,[$LLLST(L)]]	; Find ptr to msg-txt LN
	 JSR AUTPSY
	FINDA B,[A$MHDR,,[$LLLST(L)]]	; and ptr to default header
	 SETZ B,
	OUT(TMPC,("============ Failed message follows: ============
"),SL(B),SL(A))
	CALL EMSSND		; Now go send the message!

RXPE90:	UARPOP EMSGAR
	POPAE P,[E,D,C,B,A]
	RET

SUBTTL RCPSND - send message to all rcpts on list
;;; Takes ptr in L to a msg attrib list and sends to all rcpts listed,
;;; with no equivalencing nor header.  Will queue unless otherwise
;;; specified if temporarily unable to send.  After each result, adds the
;;; feature of "failed" or "sent" or "queued" to that rcpt.

.SCALAR RSNDQD,RSNDFL,NDLAST

RCPSND:	PUSHAE P,[A,B,C,D,E,RSNDQD,RSNDFL]
	TRZ F,%RBHD
	SETZM RSNDQD		; Clear 'a msg queued' flag
	SETZM RSNDFL		; Clear 'a msg failed' flag.
	SETZ N,
	SETOM NDLAST
	SKIPA E,$LLLST(L)
RSND01:	 HRRZ E,LISTAR(E)	; Get CDR to next msg attrib
	FINDA E,[A$RCP,,[E]]	; Get recipient list there.
	 JRST RSND50		;  No more, all done.
	MOVE D,LISTAR(E)+1	; Get ptr to rcpt's list.
	FINDA A,[A$RPSN,,[D]]	; Is this a psuedo?
	 JRST RSND05		;  No, OK to send.

	;; Here we have a psuedo recipient. (Probably going to CLI to it.)
	FNDSTR B,A,A$ROPT,[NO-CLI] ; First, look for "no CLI" flag.
	 CAIA
	  JRST RSND01
	FINDA A,[A$RNAM,,[D]]	; Get LN to recipient name.
	 JSR AUTPSY
	HLRZ B,LISTAR(A)+1	; How long is the name?
	CAILE B,6		; If it is longer than six chars
	 JRST RSND01		;  it can't possibly be a UNAME.
	FINDA B,[A$RTYP,,[D]]	; Get recipient type.
	 JRST RSND02
	USLNEA B,[ASCNT [NAME]]	; It should be NAME.
	 JRST RSND01
RSND02:	FINDA B,[A$SNM,,[$LLLST(L)]] ; Get the sender's name.
	 JSR AUTPSY
	USLNE A,B		; Check for sending to sender...
	 CAIA
	  JRST RSND01		; Names equal, don't send. KLH mailing to KLH.
	FINDA B,[A$CSN,,[$LLLST(L)]]	; Another check for sending to sender!
	 JRST RSND04		; If no CLAIMED-FROM, go ahead.
	USLNE A,B		;   Else must check it too.
	 CAIA			; Don't send. KLH0 is mailing to KLH.
	  JRST RSND01
RSND04:	CALL CVLS6		; Cvt list string into 6bit in A.
	MOVE B,D		; B holds LP to rcpt's list.
	CALL CLIOPT		; Now send to all matching x/unames
	 NOP
	JRST RSND01		; Done.

	;; Here we have an active recipient!
RSND05:	MOVE A,D		; Get recipient list in A.
	CALL SETMSM		; Find service required (mail or send).
	FINDA N,[A$RHST,,[D]]	; Find recipient's host.
	 SKIPA N,[0]		; Zero = local.
	  MOVE N,LISTAR(N)+1	; Get host #.
	CAMN N,OWNHS2		; Last chance for schizophrenia.
	 MOVE N,OWNHST
	CAMN N,OWNHST		; If this host is ourselves
	 SETZ N,		;   force to special "local" # of zero.
	CALL NETPTH		; Find path to host.
	 JRST [	SKIPN N		;  Already connected!
		 PSTAT (,("  Local-"),RABR,HST(OWNHST))
		JRST RSND20 ]
	CAMN N,NDLAST		; Need reconnection.
	 JRST RSND01		;  But don't try same site twice in a row.
RSND10:	CALL NTDISC		; Disconnect.
	MOVEM N,NDLAST		; Remember most recent host tried.
	CALL NETICP		; Attempt connection to host N.
	 JRST [	AOS RSNDQD	;   Net failure, bah.  Ensure stuff queued.
		JRST RSND01]	;   and go round again.
RSND20:	CALL NETSND		; Send to all rcpts this host and say byebye.
	 NOP			; Net failure doesn't matter here.
	MOVE A,NSCTQ		; Get # queued
	ADDM A,RSNDQD		; Add into sum.
	MOVE A,NSCTF		; Get # failed
	ADDM A,RSNDFL		; Likewise.
	JRST RSND01		; Then go back for another round.

	;; All done with sending. See if anything needs to be queued, etc.
RSND50:	CALL NTDISC		; Close any open net conns
	; This little instruction is VERY IMPORTANT - it limits
	; recursion to one level only, thus allowing the hack of MSGAR and
	; EMSGAR to win!  Since MAIL sends all its stuff with RCPSND,
	; this will catch all such recursive attempts.  (e.g. if a failure
	; message itself gets an error)  Any attempt to enable more
	; levels must use a more flexible "current message LSE" scheme.
	MOVEI A,MF%SNT+MF%QUD+MF%FAI
	TRNN MF,M%CORG		; If comsat originated, don't
	 CALL MSGFIN		; "finalize" message by sending receipts.

	SKIPLE RSNDQD		; If any msgs were queued,
	 CALL QUEUE		; then queue the MSG-LSE.

RSND95:	POPAE P,[RSNDFL,RSNDQD,E,D,C,B,A]
	RET
-
SUBTTL	Message Finalization (receipt generation/sending)

; MSGFIN - Scan msg rcpts and compose a confirmation msg if
;	necessary, according to flags.
;	A - flags.  Ask for maximum; sender options may limit what
;		actually can be used.
MF%SNT==:1	; Sent.
MF%QSNT==:2	; Queued-sent.
MF%QUD==:4	; Queued.
MF%FAI==:10	; Failed.
MF%KIL==:20	; Manually killed

MSGFIN:	PUSHAE P,[A,B,C,D,NSCTS,NSCTQ,NSCTF]

	; First see what flags need censoring.
	FNDSTR B,$LLLST(L),A$CNF,[ALL]	
	 JRST [	FNDSTR B,$LLLST(L),A$CNF,[FAIL]	; ALL not allowed.  FAIL only?
		 TRZA A,MF%SNT	; Neither ALL nor FAIL, only QUD/FAI allowed.
		  ANDI A,MF%FAI+MF%KIL	; Only FAIL allowed.
		JRST .+1]
	MOVE C,A		; Get flags out of way.
	UARPUSH EMSGAR		; Crock, ensure closed since will check later.

IRP FLG,,[SNT,QSNT,QUD,FAI]ATTR,,[A$RSNT,A$RSNT,A$RCP,A$RCPF]MSG,,[Sent to,Queued msg sent to,Queued,FAILED]
TRNE C,MF%!FLG		; Flag set?
 CALL [	MOVEI A,ATTR
	MOVE B,[ASCNT [MSG: ]]
	CALRET RSNREP]
TERMIN

	TRNN C,MF%KIL
	 JRST MSGF40
	SKIPN $AROPN+EMSGAR
	 OUTCAL(TMPC,CALL(EMSOPN))	; Open EMSGAR LSE & setup channel.
	OUT(TMPC,("This message was manually killed by Comsat maintainers."),CRLF())
MSGF40:	TRNE C,MF%FAI		; If hacked failed rcpts
	 CAIG A,		; and at least one DID fail, include msg text.
	  JRST MSGF50		; Nope, needn't include text.
	FINDA A,[A$MTXT,,[$LLLST(L)]]	; Find ptr to msg-txt LN
	 JSR AUTPSY
	FINDA B,[A$MHDR,,[$LLLST(L)]]	; and ptr to default header
	 SETZ B,
	OUT(TMPC,(" Failed message follows:
-------
"),SL(B),SL(A))

MSGF50:	SKIPN $AROPN+EMSGAR	; Now send the confirmation msg if any
	 JRST MSGF90		; Not there, nothing to send.

	CALL EMSSND		; Send EMSGAR, with stuff from current LSE.

MSGF90:	UARPOP EMSGAR
	CALL MSGFDN		; Change appropriate stuff to "done".
	POPAE P,[NSCTF,NSCTQ,NSCTS,D,C,B,A]
	RET

; MSGFDN - Mark all A$RCPF's and A$RSNT's "done"; ditto pseudos.
;	L - message LSE.

MSGFDN:	PUSHAE P,[A,B]
	MOVE A,$LLLST(L)
	MOVEI B,MSGFD2
	CALL MAPC
	 JSR AUTPSY
	POPAE P,[B,A]
	RET
MSGFD2:	LDB B,[$LAFLD,,LISTAR(A)]	; Get attrib
	CAIE B,A$RSNT
	 CAIN B,A$RCPF
	  JRST MSGFD5
	CAIE B,A$RCP
	 PJRST POPJ1
	FINDA B,[A$RPSN,,[LISTAR(A)+1]]	; See if pseudo; hack it too!
	 PJRST POPJ1
MSGFD5: MOVEI B,A$RDON
	DPB B,[$LAFLD,,LISTAR(A)]
	TLO F,%MSGMD
	PJRST POPJ1
	

; RSNREP - Subroutine for MSGFIN.  Given an attribute, output a header string
;	followed by the names of all rcpts with that attribute, plus each
;	rcpt's A$RRMG if any.
;	A - Attrib code
;	B - ASCNT ptr to heading string
; Returns
;	A - # rcpts that matched.

.SCALAR RSNTY	; Save ASCNT heading string.
.SCALAR RSNCMP	; Used to compose FINDA arg.
.SCALAR RSNCNT	; # rcpts found.
.SCALAR RSNSW	; switch for prettification (comma etc)
		; -1 nothing on line, 0 header only, +n header & rcpts.

RSNREP:	PUSHAE P,[B,C,D,E,L]
	SETOM RSNSW
	SETZM RSNCNT
	MOVEM B,RSNTY	; Save heading string
	HRLZS A		; put attrib in LH
	HRRI A,[D]	; and addr of addr of LP in RH.
	MOVEM A,RSNCMP
	SKIPA D,$LLLST(L)
RSNRP1:	 HRRZ D,LISTAR(D)
	FINDA D,RSNCMP
	 JRST RSNRP2			; No more...
	FINDA A,[A$RPSN,,[LISTAR(D)+1]]	; Is it a pseudo?
	 CAIA
	  JRST RSNRP1			; Ignore it if so.

	; Have a rcpt, make sure set up for output.
	SKIPN $AROPN+EMSGAR
	 OUTCAL(TMPC,CALL(EMSOPN))	; Open EMSGAR LSE & setup channel.

	AOS RSNCNT			; Bump cnt of # rcpts hit.
	FINDA B,[A$RRMG,,[LISTAR(D)+1]]	; See if err msg there
	 TDZA B,B
	  JRST [SKIPLE RSNSW		; If something already on line,
		 OUTCAL(TMPC,EOL)	; must get new one.
		SETOM RSNSW
		JRST .+1]
	SKIPGE RSNSW
	 OUTCAL(TMPC,TC(RSNTY))		; First time, show heading.
	AOSLE RSNSW			; Say heading there; if already a rcpt,
	 OUTCAL(TMPC,(", "))		; split off from it.

	MOVE A,LISTAR(D)+1
	CALL NSRNAM		; This will do for now.
	FINDA C,[A$RHST,,[LISTAR(D)+1]]
	 SKIPA C,[0]
	  MOVE C,LISTAR(C)+1
	OUT(TMPC,SL(A),(" at "),HST(C))
	CAIGE A,		; Must flush the SLN if NSRNAM says temporary.
	 LNDEL A,
	JUMPE B,RSNRP1
	OUTCAL(TMPC,("; "),SL(B),EOL)
	SETOM RSNSW		; Moved to new line.
	JRST RSNRP1

RSNRP2:	SKIPL RSNSW
	 OUTCAL(TMPC,EOL)
	MOVE A,RSNCNT		; Return # rcpts output.
	POPAE P,[L,E,D,C,B]
	RET

; EMSOPN - Auxiliary to open EMSGAR LSE and string-area output channel.
;	Uses standard output chan for generality.

EMSOPN:	PUSHAE P,[A,L]
	MOVEI A,EMSGAR
	CALL LSEOPN
	MOVE L,$ARLOC+EMSGAR
	OUT(,OPEN(UC$SAO))	; Open std output for string-area output.
	POPAE P,[L,A]
	RET

; EMSSND - Assumes EMSGAR contains a LSE which has been accumulating SAO
;	type output.  Finalizes and sends text to "sender" as determined
;	by attribs of current LSE.

;;; (Should DEAD-MAIL-RECEIPTS be an optional list? Could check RCPEQV...)

EMSSND:	PUSHAE P,[A,B,C,D]
	FINDA D,[A$TIM,,[$LLLST(L)]]
	 JSR AUTPSY
	MOVE D,LISTAR(D)+1	; Save time of msg in question
	CALL SNMRCP		; Find someone to give error receipt to.
	 CAIA
	  JRST EMSS10
	;; Final sink used when nobody to give the receipt to.
	MAKELN B,[A$RNAM,,0 ? %LTSTR,,[LITSTR [DEAD-MAIL-RECEIPTS]]]
	MAKELN A,[A$RCP,,[A] ? %LTLST,,[B]]
EMSS10:	MOVE B,A		; Save LP to A$RCP in B.
	PUSH P,L
	MOVE C,L		; Also save L in C
	MOVE L,$ARLOC+EMSGAR
	MAKELN A,[A$MTXT,,0	; Create message text.  make text LN
		%LTSAO,,]	; of output thus far.
	
	LNCOPY B,[C ? SETZ B]	; Copy A$RCP(s) to use from old LSE.
	HRRM B,LISTAR(A)	; Append onto 1st LN (text).
	MAKELN A,[A$CNF,,[A]	; Inhibit sent-confirmations if it gets queued
		  %LTSTR,,[LITSTR [FAIL]]]
	MOVEM A,$LLLST(L)	; All set now... finalize list.
	STAT (,("  CMSG "))
	MOVE B,D			; Set up time
	;; Save state of RCPEXP.
	PUSHAE P,[MF,RCPSLF,RCPSLT,RCPSLP,RCPSLA,RCPTRC,LSTSUP] 
	SETZM RCPTRC
	MOVEI MF,M%CORG\M%SSBJ+M%EMSG
	CALL MAIL
	 JFCL
	POPAE P,[LSTSUP,RCPTRC,RCPSLA,RCPSLP,RCPSLT,RCPSLF,MF]
	POP P,L
EMSS99:	POPAE P,[D,C,B,A]
	RET

; QUEUE - Queues message in MSGAR.
;	What this does is to call QUEHST once for every host for which
;	some rcpt got a temporary error.
;		This is only called from RCPSND.  So at this point all
;	expansion has been done, and some RCPT-LISTs have been changed to
;	RCPT-LIST-FAILED and RCPT-LIST-SENT. 

LVAR QUEUEN:	-1		; Last host for which we called QUEHST

QUEUE:	PUSHAE P,[A,B,C,L]
	MOVE L,$ARLOC+MSGAR
	CALL RCPSRT		; (KLH is paranoid) (so am I -- dlw)
	SETOM QUEUEN		; Presumably there is not host "-1"...

	SKIPA C,$LLLST(L)
QUEUE2:	 HRRZ C,LISTAR(C)
	FINDA C,[A$RCP,,[C]]
	 JRST QUEUE6		; No more rcpt's
	FINDA A,[A$RPSN,,[LISTAR(C)+1]]	; Make sure it's a "live" one,
	 CAIA				;   not pseudo.
	  JRST QUEUE2			; Bleah, a pseudo.  Ignore.
	FINDA A,[A$RHST,,[LISTAR(C)+1]]
	 TDCA A,A
	  MOVE A,LISTAR(A)+1
	CAMN A,OWNHST		;If local host, send locally.
	 SETZ A,		;This is important to avoid looping with server.
	CAMN A,QUEUEN		; Same host as last time?
	 JRST QUEUE2		; Yes, don't do it twice!! (Depends on RCPSRT working)
	MOVEM A,QUEUEN		; (Remember for next time).
	FINDA B,[A$ID,,[$LLLST(L)]]
	 JSR AUTPSY
	CALL QUEHST		; Make QML entry for this site.
	JRST QUEUE2

QUEUE6:	TLNE F,%QMLMD		; QML modified?
	 SKIPE MKQFLG		; And in normal mode?
	  JRST QUEUE9		; No, no writing necessary
	CALL MSGPUT
	CALL QMLPUT
QUEUE9:	POPAE P,[L,C,B,A]
	RET

	
; QUEHST - Given site # in A, SLP in B to MSG ID in current LSE,
;	makes entry on QML for specified site and MSG.

QUEHST:	PUSHAE P,[A,B,C,D,L]
	MOVE B,LISTAR(B)+1	; Get SPT
	ADD B,$LSLOC(L)		; Make abs.
	SKIPN $AROPN+QMLAR	; Ensure that
	 CALL QMLGET		; QML is in core.
	MOVE L,$ARLOC+QMLAR	; Make it current LSE
	MAKELN B,[A$ID,,0 ? %LTSTR,,[B]]	; Cons up LN for ID.
	STAT (,("Queued: "),TLS(B),(" for "),HST(A))
	HRRZ D,$LLLST(L)	; Get LP to start of QML
	JUMPN D,QUEHS3
	JRST QUEHS7
QUEHS2:	HRRZ D,LISTAR(D)	; Get CDR to next
	JUMPE D,QUEHS7		; Not found?
QUEHS3:	MOVE C,LISTAR(D)+1	; Get LP to sitelist.
	MOVE C,LISTAR(C)+1	; Get site #
	CAME A,C		; Check...
	 JRST QUEHS2		; Nope, try another site.

	; Found existing sitelist for host. Append to that...
	LNAPP [LISTAR(D)+1 ? B]
	JRST QUEHS9		; No need to schedule since request msut already exist.

	; No existing sitelist, must make one.
QUEHS7:	MOVE C,A		; Save site # in C
	MAKELN A,[A$QHST,,[B]	; Cons up LN for site # as first thing in sitelist.
		%LTVAL,,[C]]
	MAKELN A,[A$Q,,[$LLLST(L)]	; And now a LLN for whole thing.
		%LTLST,,[A]]
	MOVEM A,$LLLST(L)	; Store LP back, new sitelist is now consed onto front.
	MOVE A,QTMLEN		; Now must generate schedule request for unqueueing.
	MOVEI B,QUESND		; And routine addr to XCT in B.  Arg already in C.
	CALL SCHDI		; Schedule for c(QTMLEN) 30'ths from now.

QUEHS9:	TLO F,%QMLMD		; Indicate QML modified.
	POPAE P,[L,D,C,B,A]
	RET

SUBTTL Sending of queued messages
; QUESND - Routine to send queued messages.  
;	Given in A the site # to try un-queueing for, tries to send
;	all messages queued for that site.  Checks after each for
;	existence of any input request, and aborts recoverably if one exists.

QTMLEN:	30.*60.*15.	; Length of time to wait between unqueue attempts.

QUESND:	PUSHAE P,[A,B,C,D,E,L]
	TLZ F,%QULOS
	SKIPN $AROPN+QMLAR	; Check, and
	 CALL QMLGET		; make sure that QML is loaded.
	MOVE L,$ARLOC+QMLAR	; Make it current LSE.
	;; Now must find list for specified site.
	HRRZ D,$LLLST(L)	; Get LP to first LLN
	CAIA
QSND10:	 HRRZ D,LISTAR(D)	; Get next site LLN.
	JUMPE D,QSND90		; If exhaust list, ignore unqueue request.
	MOVE C,LISTAR(D)+1	; Get LP to its list.
	MOVE B,LISTAR(C)+1	; Get site # this list is for.
	CAME A,B		; If this is not the site we are unqueing for
	 JRST QSND10		;  get next site LLN.
	HRRZ C,LISTAR(C)	; Else see if any queued msgs here.
	JUMPE C,QSND65		; Jump to flush the LLN if not.
	;; Found site list!  D has LP to its LLN, C the LP to start of list
	;; which is either A$QFCT or A$ID; rest of list is A$ID.  There is
	;; one A$ID for each queued message.  A$QFCT is present if there
	;; have been failures to connect to the host, it holds the count.
	;; B holds this start of list so we can find the A$QFCT later.
	MOVE B,C		; Remember head of list.
	MOVE N,A		; Get host in N.
	CALL HFLS		; See if site is on losers list.
	 JRST [	STAT (,("Flushing messages queued to host "),HST(A))
		TLO F,%QULOS	; Make all message fail.
		JRST QSND20 ]
	STAT (,("Unqueueing to host "),HST(A))
	TLZ F,%MSGMD		; Say no messages in core yet.
	;; Begin loop thru list of queued msgs for this host.
QSND20:	LDB A,[$LAFLD,,LISTAR(C)]
	CAIE A,A$ID		; CDR down list until find an ID.
	 JRST [	HRRZ C,LISTAR(C)
		JUMPE C,QSND60
		JRST QSND20 ]
	MOVE A,C		; Set up SLP to message ID.
	CALL MSGGET		; Pull in that message.
	 JRST [	STAT (,("Note: Qmsg "),C(42),TLS(C),C(42),(" not found in MASTER, flushing."))
		JRST QSND50]	;  Couldn't find it, ignore and flush from QML.
	TLNE F,%QULOS		; If flushing these messages
	 JRST QSND22		;  don't bother connecting to the host.
	CALL SETMSM		; Find out if mailing or sending.
	CALL NETPTH		; Find path to host.
	 JRST QSND22		;   Maybe already connected.
	CALL NETICP		; Try to connect.
	 JRST QSND80		;  If lost, reschedule a request for this site.
	;; Site is alive; update the site list's failure count.
	LDB A,[$LAFLD,,LISTAR(B)]
	CAIN A,A$ID		; If are we at looking at msg-id list
	 JRST QSND22		;   there was no failure count yet.
	CAIE A,A$QFCT		; Is there a failure count?
	 JSR AUTPSY		;    Else random cruft - QML trashed?
	SETZM LISTAR(B)+1	; Zap failure count.
	TLO F,%QMLMD		; QML modified.
QSND22: ;; Now send to all rcpts queued for this site.
	CALL QSTSND		; Tries all.  Skips if still connected.
	 JRST QSND40		; Connections gone!
	SKIPN QSFCNT		; Check for any valid remaining rcpts
	 JRST QSND49		; Nope, flush current msg.
	JUMPL N,QSND40		; If net was zapped, always requeue!!
	TLNE F,%MSGMD		; Was MSG modified?
	 CALL MSGPUT		; Yes, write back out.
	JUMPN A,QSND55		; Some left - jump if that includes this site.
	JRST QSND50		; If not, go flush from this part of QML.

	; Network lossage while sending.  Re-queue, but first rotate the
	; message so another one gets its chance at first shot.  This
	; prevents a wedged msg from blocking all mail to that host.
QSND40:	LNCOPY B,[0 ? C]	; Copy SLN for this msg
	LNAPP [C ? B]		; Append copy to list for site.
	LNDEL C,LISTAR(D)+1	; Flush original from list.
	TLO F,%QMLMD
	JRST QSND70		; And now re-schedule for unqueueing.

	; Here when message is to be flushed from QML.
QSND49:	CALL MSGDEL		; Here to flush whole msg also.
QSND50:	HRRZ B,LISTAR(C)	; Get CDR before flushing...
	LNDEL C,LISTAR(D)+1	; Flush from list.
	TLO F,%QMLMD		; Indicate QML modified!
	MOVE C,B		; Next message after the flushed one
	MOVE B,LISTAR(D)+1	; Recover head of list (LNDEL may have changed)
	HRRZ B,LISTAR(B)
	JRST QSND56

	; Here when done with one message... check to see if OK to do another.
QSND55:	HRRZ C,LISTAR(C)	; Get CDR to next.
QSND56:	JUMPE C,QSND60		; If none left, exit.
	TLNN F,%QULOS		; If flushing, do them all at once
	 CALL IRQCHK		; Else, check for input requests first.
	  JRST QSND20		;  None, OK to continue!
	JRST QSND75		; Foo, must interrupt. Reschedule unqueueing...

	; Here when all (?) messages have been sent to site; see if
	; anything left, and flush site LLN if not.
QSND60:	FINDA B,[A$ID,,[LISTAR(D)+1]] ;See if any messages on list.
	 JRST QSND65		; None, flush list
	JRST QSND70		; Some, reschedule

QSND65:	LNDEL D,$LLLST(L)	; Else flush LLN from QML!
	CALL QMLPUT
	STAT (,("All qmsgs gone for "),HST(N))
	JRST QSND90		; Write out new QML and return.

	;Lost trying to ICP to site.  Keep count of failures.  If lost
	;700 times in a row, give up on the site. (i.e. down for a week)

QSND80:	FINDA B,[A$QFCT,,[B]]
	 CAIA
	  JRST QSND81		; Found failure count.
	HRRZ C,LISTAR(D)+1	; List for host.
	MAKELN B,[A$QFCT,,[LISTAR(C)]
		  %LTVAL,,0]	; Patch in failure count after host number.
	HRRM B,LISTAR(C)
QSND81:	TLO F,%QMLMD
	AOS A,LISTAR(B)+1	; Increase failure count.
	CAMGE A,HDOWN		; If not too many
	 JRST QSND70		;  try again in a while
	CSTAT (,("...Site down for a week, abandoning..."))
	HRRZ C,LISTAR(B)	; Get list of message IDs
	TLO F,%QULOS		; Flag to cause errors on all messages
	JRST QSND20

	; Must reschedule unqueue request for this site.  Entry at QSND75
	; reschedules for as soon as conveniently possible.
QSND70:	MOVE A,QTMLEN		; Try again in 15 min.  (later, be smarter)
	CAIA
QSND75:	 SETZ A,
	TLNE F,%MSGMD		; Make SURE that everything's safe!
	 CALL MSGPUT
	TLNE F,%QMLMD
	 CALL QMLPUT
	MOVEI B,QUESND		; Rtn to xct.
	MOVE C,-5(P)		; Site to crunch. (original arg in A)
	CALL SCHDI		; schedule at c(A) from now.

QSND90: CALL NTDISC		; Close net connections if any.
	POPAE P,[L,E,D,C,B,A]
	TLZ F,%QULOS
	RET

	; Kludge routine to flush messages for hosts in the HFLUSH table.
	; Skip return if site in N is not on losers list, else return.
HFLS:	PUSHAE P,[A,N]
	MOVSI A,-HFLUSN		; Get table of losing sites.
HFLS1:	CAMN N,HFLUSH(A)	; Is this site a loser?
	 JRST HFLS2		;  Yes, failure return.
	AOBJN A,HFLS1
	AOS -2 (P)
HFLS2:	POPAE P,[N,A]
	RET



; QSTSND - Takes current MSG-LSE, sends to all of its recipients at site
;	specified by N.  Skips on return if still connected to site.
;	Sets A 0 if no rcpts are left for this site, -1 if some are.

.SCALAR QSFREQ,QSFCNT

QSTSND:	PUSHAE P,[B,C,L]
	MOVE L,$ARLOC+MSGAR	; use MSG-LSE as current.
	FINDA A,[A$ID,,[$LLLST(L)]]
	 JSR AUTPSY
	STAT (,("QID="),TLS(A))
	CALL NETSND		; Do it.
	 CAIA			; Net broken, no skip on return.
	  AOS -3(P)		; Still there, skip.
	SKIPN NSCTF		; Anything failed?
	 SKIPE NSCTS		; or anything sent?
	  CAIA
	   JRST QSTS95		; Nope, just count # rcpts left and return.
	MOVEI A,MF%QSNT+MF%FAI	; Yes, go finalize message
	TLZE F,%QULOS
	 JRST [	CALL MSGFIN	; Stupid crock.
		TLO F,%QULOS	; To simulate push/pop of flag.
		JRST QSTS95]
	CALL MSGFIN		; for sents/fails only.
QSTS95:	CALL MSGRCK		; Get # of active rcpt's.
	MOVEM A,QSFCNT
	MOVE A,NSCTQ		; Return in A the # of rcpts queued for site.
	POPAE P,[L,C,B]
	RET

; MSGRCK - Counts # of active rcpts for LSE in MSGAR.
;	MSGAR - LSE holding message
; Returns .+1
;	A - # of active rcpts seen (non-pseudo A$RCP's).

MSGRCK:	PUSHAE P,[B,C,L]
	MOVE L,$ARLOC+MSGAR
	SETZ A,
	SKIPA B,$LLLST(L)
MSGRC2:	 HRRZ B,LISTAR(B)
	FINDA B,[A$RCP,,[B]]
	 JRST MSGRC4
	FINDA C,[A$RPSN,,[LISTAR(B)+1]]
	 AOJA A,MSGRC2
	  JRST MSGRC2
MSGRC4:	POPAE P,[L,C,B]
	RET


	; New XRCP-hacking routines.
.INSRT SYSNET;NETSND >

SUBTTL Reminder Queueing

;;; REMIND - Queue a (new) reminder.  Given current MSG-LSE that has reminder
;	specs, puts it into appropriate place on RML list.  Does not generate
;	a schedule request unless nothing was previously on the RML, since
;	one is always pending for the first thing on RML.

REMIND:	PUSHAE P,[A,B,L]
	SKIPN $AROPN+MSGAR	; Make sure there's a current MSG-LSE
	 JSR AUTPSY
	MOVE L,$ARLOC+MSGAR	; Make it current
	CALL RTMFND		; Find next (actually, first) time to send it 
	 SETZ A,		; If no next time, send right away - either not
				; a reminder, or time's already past!
	FINDA B,[A$TNXT,,[$LLLST(L)]]		; Find A$TNXT LN to save in.
	 JRST [	MAKELN B,[A$TNXT,,[$LLLST(L)]	; make it if necessary.
			%LTVAL,,0]
		MOVEM B,$LLLST(L)
		TLO F,%MSGMD	; If added LN, msg obviously modified.
		JRST .+1]
	CAME A,LISTAR(B)+1	; If about to change value in LN,
	 TLO F,%MSGMD		; indicate msg modified, must be written out.
	MOVEM A,LISTAR(B)+1	; Save next-time-to-send.

	; Now create a RML entry for this message.  LP to next-time-to-send
	; is in B.
	FINDA A,[A$ID,,[$LLLST(L)]]	; Get LP to msg-ID
	 JSR AUTPSY
	SKIPN $AROPN+RMLAR		; Make sure RML loaded.
	 CALL RMLGET
	MOVE L,$ARLOC+RMLAR		; Make RML current...
	LNCOPY B,[$ARLOC+MSGAR ? B]	; And create copy of msg-ID in RML LSE
	LNCOPY A,[$ARLOC+MSGAR ? A]	; Likewise for A$TNXT.
	HRRM B,LISTAR(A)		; Make A$TNXT first, pointing to msg-ID.
	MAKELN A,[A$T,,0		; And finally stick under a LLN.
		%LTLST,,[A]]

	; Have created RML entry (LLP in A) ready for insertion into
	; RML... now stick it in the right place.
	HRRZ B,$LLLST(L)	; Save RML pointer for checking after insert.
	CALL SRTINS		; Takes LLN in A, inserts into sorted RML list
	SKIPE MKQFLG
	 JRST REMIN0
	CALL RMLPUT		; Now write out new RML.
	TLNE F,%MSGMD		; And MSG-LSE if necessary. (see notes below)
	 CALL MSGPUT
REMIN0:	CAIN B,0		; If RML was empty before adding entry,
	 CALL SCHREM		; Then go schedule the remind-list!
	POPAE P,[L,B,A]
	RET

;;; NOTE about sequence of file updates:
;	The basic philosophy is that anything entered in MASTER must
; reflect the complete current state of the message, so that
; reconstruction of both RML, QML, etc can be done from MASTER and
; its messages alone.  Beyond that, some efficiency considerations:
;	Were the MSG-LSE (and MASTER) written out first and the system
; crashed before the RML was updated as well, then
; MASTER's info would be correct, but RML's would be incomplete as no
; entry for that message would exist, and no reminder-sending processing
; would be initiated for it.  The solution is to completely recreate the
; RML from MASTER after each COMSAT startup... ugh.
;	However, if the RML is written out before MASTER, then if a crash
; intervenes between writing RML and MASTER, the message will not have
; been stored although the RML contains an entry pointing to that
; message ID.  This is easily solvable by the strategy of ignoring any
; RML reference to a message not on the MASTER list, and requires no
; computational overhead.
;	Hence, RML is put out before the MSG-LSE is.

SUBTTL Reminder Sending
; REMSND - Send a reminder, invoked by scheduler when it thinks time has come
;	for first reminder on list.  Must check truth of this, then:
;	Must first pull into current MSG-LSE, find next time to send
;	and include this information in message if desired, then do a
;	normal-priority sending (all local, but queue if anything for net)
;	Then pop it off RML list and put new time back on in right place,
;	and write out new RML, etc... Then re-schedule self for next
;	due reminder.

	; Throughout this routine,
	;	E holds LLP to LLN for activated RML entry, and
	;	D holds LP to first LN on the entry's sublist, ie LISTAR(E)+1.
	;	C holds next-time-to-send, or 0 if no more.
REMSND:	PUSHAE P,[A,B,C,D,E,L]
	SKIPN RMLAR		; Ensure RML in core.
	 CALL RMLGET
	MOVE L,$ARLOC+RMLAR
	HRRZ E,$LLLST(L)	; Get LP to first LLN
	JUMPE E,REMS90		; If nothing on RML, ignore activation completely.
	MOVE D,LISTAR(E)+1	; Get LP to first LN of first RML node.

	CALL DATIME"TIMGT	; Get current time for use by remind rtns
	MOVEM A,RTMCUR		; Save.  See note at RTMCUR.
	MOVE A,LISTAR(D)+1	; And now get time for earliest reminder!
	CAMLE A,RTMCUR		; Less than or eq to current time??
	 JRST REMS80		; No, still in future?  Must re-schedule,
				; somehow got triggered too soon.

	; Time to process a RML entry - take it off RML, and
	; get msg it points to.
	HRRZ A,LISTAR(E)	; Get CDR to next entry, and make it
	HRRM A,$LLLST(L)	; new head of RML - current entry now off.
	FINDA A,[A$ID,,[D]]	; Find ID SLN in entry's list.
	 JSR AUTPSY		; Bleah??
	CALL MSGGET		; Now snarf up message.
	 JRST REMS75	; Ugh, not found. Flush RML entry, assuming MASTER is right.

	; Message read in, first check that it really wants to be sent.
	MOVE L,$ARLOC+MSGAR		; Make MSG-LSE current
	FINDA A,[A$IDRM,,[$LLLST(L)]]	; Is this message actually offspring of a reminder?
	 JRST REMS75			; If so, ignore this RML entry and flush it.
	FINDA A,[A$TNXT,,[$LLLST(L)]]	; Get next-time-to-send accding to message
	 JRST REMS75		; Foo, it has none?? ignore & flush RML entry.
	MOVE A,LISTAR(A)+1	; Get value of next-time-to-send
	CAMLE A,RTMCUR		; Less or eq to current time?
	 JRST [	MOVE L,$ARLOC+RMLAR	; In future! must reset RML entry.
		MOVEM A,LISTAR(D)+1	; by storing updated time-to-send
		JRST REMS50]		; and re-inserting on RML, and scheduling.

	; Yes, definitely sending message.  Before doing so, determine
	; next time this reminder will be sent.
	CALL RTMFND		; Now find next time to send.
	 JRST REMS30		; None - go inform that this is last reminder.
	MOVE C,A		; Save next-time in C.
	FINDA A,[A$TEXP,,[$LLLST(L)]]	; Find expiration spec.
	 JRST REMS40		; None? then don't bother with any status report.
	MOVE B,LISTAR(A)+1	; Get it
	TLNE B,-1		; If anything in LH, it's an expiration time.
	 JRST [	CAMGE B,C	; Compare with next time
		 JRST REMS30	; Ugh, next time is greater than expiration. No more.
		JRST REMS40]	; Is OK...
	SOSLE LISTAR(A)+1	; Expiration count.  Decrement count
	 JRST REMS40		; And jump if still OK.

	; No more reminders after this one.  Generate note to add onto
	; front of message.
REMS30:	SETZ C,			; First indicate no next-time.
	FINDA B,[A$MTXT,,[$LLLST(L)]]
	 JSR AUTPSY
	SAOBEG TMPC,
	FWRITE TMPC,[[Note: this is the final message for this reminder.

],TLS,B]
	LNDEL B,$LLLST(L)	; Flush old message-text
	MAKELN A,[A$MTXT,,[$LLLST(L)]	; make SLN of new text
		%LTSAO,,0]
	HRRM A,$LLLST(L)	; Store on msg list.
	

	; Must rename A$ID to A$IDRM and generate new unique A$ID for
	; message before sending.
REMS40:	FINDA A,[A$ID,,[$LLLST(L)]]
	 JSR AUTPSY
	MOVEI B,A$IDRM		; Turn ID into parent-reminder ID.
	DPB B,[$LAFLD,,LISTAR(A)]
	CALL IDSET		; Now create a new ID, tack onto list.
	TLO F,%MSGMD		; And indicate it's modified.
	SETZ MF,
	CALL MAIL		; Send it!  This will queue if necessary.
	 JFCL


	; Reminder sent, now see what time if any to resend.
	JUMPE C,REMS60		; If no more, flush from master & RML.

	MOVE L,$ARLOC+RMLAR		; Else must fix up next time.
	FINDA A,[A$ID,,[D]]		; Get back ID to reminder
	 JSR AUTPSY
	CALL MSGGET			; recover message.
	 JSR AUTPSY			; Huh? we did this OK before!

	FINDA B,[A$TNXT,,[$LLLST(L)]]	; Find LN to hold time-of-next-sending
	 JSR AUTPSY
	MOVEM C,LISTAR(B)+1		; Store time to send next.
	FINDA B,[A$TEXP,,[$LLLST(L)]]	; see if have expiration count
	 CAIA
	  JRST [MOVE A,LISTAR(B)+1	; have expiration spec,
		TLNN A,-1		; skip unless it's a count
		 SOS LISTAR(B)+1	; in which case decrement it!
		JRST .+1]
	CALL MSGPUT		; Now write back reminder.

	; Now pop RML entry off RML list, and put back in right place.
	MOVE L,$ARLOC+RMLAR		; Make RML current
	HRRZ A,LISTAR(E)		; Get CDR to next entry, and make it
	HRRM A,$LLLST(D)		; first thing on list.
	MOVEM C,LISTAR(D)+1		; Now store new time to hack this rmdr!

	; Now re-insert entry onto RML, and write list back.
	; RML must be current.
REMS50:	MOVE A,E		; arg - LLP to entry.
	CALL SRTINS		; In it goes.
	CALL RMLPUT
	JRST REMS80		; Now schedule next reminder.

	; Here when reminder isn't to be sent any more.  Flush.
	; Assumes MSG-LSE current.
REMS60:	FINDA B,[A$ID,,[$LLLST(L)]]
	 JSR AUTPSY
	MOVE A,LISTAR(A)+1		; Get SPT for MSG ID.
	ADD A,$LSLOC(L)			; Abs.
	CALL MSGDEL			; Glurgle! Down it goes. (see note below)
REMS75:	MOVE L,$ARLOC+RMLAR		; Here to flush current entry from RML.
	LNDEL E,$LLLST(L)
	CALL RMLPUT			; Out with RML, and that's it.

REMS80:	CALL SCHREM			; Schedule next reminder.
REMS90:	POPAE P,[L,E,D,C,B,A]
	RET

; NOTE about sequencing of file writes:
;	As opposed to REMIND, REMSND writes out the MSG/MASTER before the
; updated RML.  This is optimal because at worst an old RML will initiate
; a reminder-send request immediately for the message in question, which may
; even be deleted - if deleted, the rule about "ignore it" wins here, and if
; not then the information in the MSG-LSE itself (specifically A$TNXT) will
; serve to correct RML's backwardness quite satisfactorily, and the message will
; not be sent again until it is actually time to do so.

IFN 0,[
Algorithm for finding next time to send is simply to calculate the earliest
possible time for each time-spec, and use the earliest one found.  To actually
find the earliest time for an individual time spec:

1) Get current time (or base time if given)
2) Plug given time into all wild slots.
3) Later than current time?  If so, go to 7!
4) not late enough.  If using a base time, use current time instead and go to 2.
5) using current time.  Increment, using LOWEST wild item or given abs increment.
	(Must check to ensure that eventual winnage is actually possible.
	obviously if year is '65, we'll never get anywhere)
6) Go back to 3 and so forth, until get a time in the future.
7) Check: is obtained time past specified expiration date if any?
8) If not, found it... stop and rest.
]

DATIME"TM%DOW==700000		; 2.9-2.7	0-7	1-7
DATIME"TM%HR==  76000		; 2.6-2.2	0-31	0-23
DATIME"TM%MIN==  1760		; 2.1-1.5	0-63	0-59

DATIME"TM$DOW==(.BP DATIME"TM%DOW,)
DATIME"TM$HR== (.BP DATIME"TM%HR,)
DATIME"TM$MIN==(.BP DATIME"TM%MIN,)
DATIME"TM$YR2==(.BP <DATIME"TM%YR&<DATIME"TM%YR#DATIME"TM%YR_2>>,)	; Byte ptr to bottom 2 bits of DATIME"TM%YR.

; Wildness is indicated by an 0 value for all of YEAR, MONTH, DAY.
; DOW is special; the following cases exist.
; DAY	DOW
;  *	 *	DAY is wild and message is sent every day.
;  *	 n	Message sent only on given DOW.
;  n	 *	Message sent only on given DOM.
;  n	 n	Message is sent only when both DOM and DOW agree.  This
;		is great for things like "Friday the 13th"!


; RTMFND - Given current MSG-LSE, finds next (future) time to send.
;	Hacks all time-specs, returns time in A and skips.
;	non-skip indicates no more reminders to be sent.
;	Does NOT hack any of: Expiration count/date or Next-send time.

RTMFND:	PUSH P,B
	SETZM RTMNXT		; Clear "best-so-far" variable.
	CALL DATIME"TIMGT	; Get current time
	MOVEM A,RTMCUR
	SKIPA B,$LLLST(L)	; Get LP to MSG-LSE list.
RTMFN2:	 HRRZ B,LISTAR(B)	; on loop, get CDR to next.
	FINDA A,[A$TLST,,[B]]	; Try to find spec list
	 JRST RTMFN5		; No more.
	CALL RTMFN		; Get next-time for this spec.
	 JRST RTMFN2		; None available.
	SKIPE RTMNXT		; If no spec stored yet, always store it.
	 CAMG A,RTMNXT		; Aha, compare with currently best (earliest)
	  MOVEM A,RTMNXT	; New time is less, use that!
	JRST RTMFN2		; Try the rest.

RTMFN5:	SKIPE A,RTMNXT		; Return this...
	 AOS -1(P)		; And skip if returning a real time.
	POP P,B
	RET

; RTMFN - Given LP in A to a reminder-time-spec LLN, figures out next
;	time to send according to that spec.  RTMCUR must have been set up.
;	Returns time in A and skips if found.

RTMFN:	PUSH P,B
	FINDA B,[A$TBAS,,[LISTAR(A)+1]]	; See if base time specified.
	 TDCA B,B
	  MOVE B,LISTAR(B)+1		; Get it if so, else set 0.
	MOVEM B,RTMBAS
	FINDA B,[A$TINC,,[LISTAR(A)+1]]	; See if time increment specified.
	 TDCA B,B
	  MOVE B,LISTAR(B)+1		; Same as above.
	MOVEM B,RTMINC
	FINDA B,[A$TSPC,,[LISTAR(A)+1]]	; Now find the time spec!
	 JSR AUTPSY			; Foo! must always have!!
	MOVE A,LISTAR(B)+1		; Get it, and
	POP P,B
	PJRST RTMF			; Now figure out next time!

BVAR
RTMNXT:	0	; Holds computed time for next reminder.
RTMCUR:	0	; Holds current time in standard format. (only Y/M/D used)
RTMBAS:	0	; Holds base time to use, also standard format. ( " )
RTMINC:	0	; Holds desired incr, if no wild specs.  Val in minutes.
EVAR

; RTMF - Messy monster to figure out next time to send a reminder,
;	given a reminder-time spec and the assumption a reminder has
;	just been sent (so no need to worry about sending one in the past)
;	RTMCUR must be set up with current time, RTMBAS with base time
;	to use, RTMINC with increment if any, A with time-spec.

RTMF:	PUSHAE P,[B,C,D,E]
	SKIPN B,RTMBAS		; If base time given, use it initially.
	 MOVE B,RTMCUR		; Else use current time.
RTMF05:	AND B,[DATIME"TM%YR+DATIME"TM%MON+DATIME"TM%DAY]
	TLNE A,(DATIME"TM%YR)		; Has year specification?
	 TLZ B,(DATIME"TM%YR)		; Yes, wipe from base time.
	TLNE A,(DATIME"TM%MON)		; Has month?
	 TLZ B,(DATIME"TM%MON)		; Yes, wipe from base.
	TLNE A,(DATIME"TM%DAY)		; This is getting
	 TLZ B,(DATIME"TM%DAY)		; monotonous.
	IOR B,A			; Now IOR them together to produce complete spec!
	LDB C,[DATIME"TM$HR,,B]	; Start getting time
	LDB D,[DATIME"TM$MIN,,B]	; into
	IMULI C,60.
	ADDI C,(D)
	IMULI C,60.*2.		; Standard halfsec form.
	HRR B,C			; Now have specified time in standard format!
RTMF10:	CAMG B,RTMCUR		; Is resultant greater than current time?
	 JRST [	SKIPN RTMBAS	; No.  Were we using a base time?
		 JRST RTMF30	; If not, just go increment since time is already past.
		SETZM RTMBAS	; If using base time, zap it and don't use any more.
		MOVE B,RTMCUR
		JRST RTMF05]

	; Time is greater than current!  Now must check esoteric stuff
	; like DOW, unless using RTMINC.
	SKIPE RTMINC		; Using RTMINC?
	 JRST RTMF70		; Yeah, ignore all else and return.
	TRNN A,DATIME"TM%DOW		; Is DOW specified?
	 JRST RTMF70		; No, all's well.
	LDB C,[DATIME"TM$DOW,,A]	; Bleah. Get DOW to check for.
	PUSHAE P,[A,B]
	MOVE A,B		; Set up current stuff for munching.
	CALL DATIME"TIMADY		; Get time in abs # days
	IDIVI A,7		; Get DOW-1 in B
	MOVEI D,1(B)		; Get DOW in D
	POPAE P,[B,A]		; Restore stuffs.
	CAIN C,(D)		; Compare with desired DOW.
	 JRST RTMF70		; Hurray!
	SUBI D,(C)		; Foo. Find difference in days.
	CAIL D,0
	 SUBI D,7		; If pos, adjust, to get # days in future for DOW.
	MOVMS D			; It really works...
	JRST RTMF32		; and jump into increment-day rtn.

	; Day increment!  Any attempt to find a more future date must start here!
	; If RTMINC is specified, it is applied without question.
RTMF30:	MOVEI D,1		; Before testing wildness, set up increment.
RTMF32:	SKIPE RTMINC
	 JRST RTMF60		; Uh-oh. RTMINC non-Z means simply add increment.
	TLNE A,(DATIME"TM%DAY)		; Wild day?
	 JRST RTMF40		; Nope, try incrementing month.
	LDB C,[DATIME"TM$DAY,,B]	; Get current day
	ADDI C,(D)		; Increment
	CAIG C,28.		; If less than final DOM for smallest month,
	 JRST [	DPB C,[DATIME"TM$DAY,,B] ; Win! put back into time
		JRST RTMF10]	; and continue.
	; Ugh, must check out day increment.
	LDB D,[DATIME"TM$MON,,B]	; Find what month it is.
	CAIN D,2		; February?
	 JRST [	LDB E,[DATIME"TM$YR2,,B] ; Pouah! Why can't the world be simple?
		CAMG C,(E)[29. ? 28. ? 28. ? 28.]
		 JRST RTMF10	; Is OK...
		JRST RTMF37]	; NOT OK!
	CAMG C,DATIME"TMONLN(D)	; Check max month length.
	 JRST RTMF10		; OK...

	; Day-increment went into next month!
RTMF37:	MOVEI C,1
	DPB C,[DATIME"TM$DAY,,B]	; Reset to 1, and drop thru to try incrementing month!

	; Increment month!
RTMF40:	TLNE A,(DATIME"TM%MON)		; Wild month?
	 JRST RTMF50		; Nope, try incrementing year.
	LDB C,[DATIME"TM$MON,,B]	; Get current month
	ADDI C,1
	CAIG C,12.		; Within year bounds?
	 JRST [	DPB C,[DATIME"TM$MON,,B] ; Yep, success.
		JRST RTMF10]	; Go back & try again.
	MOVEI C,1		; Oops, overstepped!  Must reset to 1 and increment year
	DPB C,[DATIME"TM$MON,,B]	; Drop through...

	; Increment year!
RTMF50:	TLNE A,(DATIME"TM%YR)		; Last chance! wild year?
	 JRST RTMF90		; No, no hope... can't find any more times to send.

	; Wild year - Increment year.  This loses only if going past 2027.
	LDB C,[DATIME"TM$YR,,B]	; Get year of current spec
	ADDI C,1
	CAILE C,177		; Overflow?
	 JRST RTMF90		; Yeah, this reminder is a goner.
	DPB C,[DATIME"TM$YR,,A]	; Put back.
	JRST RTMF10		; Back to check...

	; Increment via RTMINC.
RTMF60:	PUSH P,A
	MOVE A,B
	CALL DATIME"TIMSEC		; Convert current spec to abs # secs
	MOVE C,RTMINC
	IMULI C,60.		; Get # of secs to add as increment.
	ADD A,C			; Not ADDI, may be very large.
	CALL DATIME"SECTIM		; Convert back now.
	MOVE B,A
	POP P,A
	JRST RTMF10		; Now try again.

RTMF70:	MOVE A,B		; Return in A the time obtained.
	AOSA -4(P)
RTMF90:	 SETZ A,		; Return here when failed to find another time.
	POPAE P,[E,D,C,B]
	RET

SUBTTL SNDMSG - General Message Sending


; SNDMSG - Given LP to an A$RCP in A,  (assumes current LSE is MSG)
;	site # in N (0 for local), sends locally or over net.  If
;	net connections not yet open, tries to ICP.
;	Never skips.  A has return code; 0=win, 1=temp loss, 2=perm loss,
;	3= temp loss but keep sending to this host (e.g. disk full).
;	Whether successful or not, B if nonzero will contain a SLP to
;	an informational message, which should be passed along to sender.

SNDMSG:	PUSHAE P,[C,D]
	MOVE A,LISTAR(A)+1	; Replace LLP by LP to list in A.
	FINDA B,[A$RNAM,,[A]]	; Find name, and
	 JSR AUTPSY
	STAT (,("	TO-"),RABR,TLS(B)) ; Announce we're sending to it.
	CALL SETMSM		; Set Mail/Send modes.
	JUMPN N,[MOVEI B,SNDNET	; Check site #, jump if sending over net.
		JRST SNDMS4]

	FINDA B,[A$RTYP,,[A]]	; Not net, find rcpt type.
	 JRST [	MOVEI B,SNDNAM	; If no type, assume NAME.
		JRST SNDMS4]
	MOVSI C,-NRTYPS		; Some type, loop to find idx.
	SLNEA B,RTYPTB(C)
	 AOBJN C,.-1
	JUMPGE C,[MOVEI B,SNDNAM	; If no match, use NAME.
		JRST SNDMS4]
	MOVE B,SNDRTB(C)		; Get appropriate rtn for this type.

SNDMS4:	POPAE P,[D,C]
	PJRST (B)		; Execute - try to send.

BVAR
SENDSW:	0	; If sending,  -1 don't mail. 0 mail if failed.  1 always mail.
MAILSW:	0	; If mailing,  -1 don't notify. 0 notify if rcpt wants. 1 always notify.
SORMSW:	0	; 0 mail, -1 send.  Existence of A$RMDS sets SORMSW to -1, else 0.
EVAR

	; Given LP in A to rcpt list, sets mail/send modes.
SETMSM:	SETZM MAILSW
	SETZM SENDSW
	SETZM SORMSW
	PUSH P,B
	FINDA B,[A$RMDS,,[A]]
	 JRST SETMS3
	MOVE B,LISTAR(B)+1
	MOVEM B,SENDSW
	SETOM SORMSW
SETMS3:	FINDA B,[A$RMDM,,[A]]
	 JRST SETMS7
	MOVE B,LISTAR(B)+1
	MOVEM B,MAILSW
SETMS7:	POP P,B
	RET

SUBTTL SNDNET - Send message out over net.

	; Send message over net.  Hack ICP later if necessary.
SNDNET:	PUSHAE P,[C]
	MOVE C,A		; Save LP in C
	FINDA B,[A$RHST,,[C]]	; Find site
	 JSR AUTPSY		; If not sending over net, what are we doing here?
	MOVE B,LISTAR(B)+1
	CAME B,N		; Make sure we're sending to current site!
	 JRST [	MOVEI A,MR$TEH	; Hmm, site we want isn't current!
		 JRST SNDN95]	; Don't ret err msg but do fail temporarily.

	CALL NTSBEG		; Initiate message
	 JRST SNDN90		; Failed, investigate.

	; Rcpt accepted! send message.
	MOVEI A,(C)		; Get back ptr to rcpt's list.
	CALL NTSMID		; Send message text.
	 JRST SNDN90		; Error?
	CALL NTMEND		; Terminate msg (but don't disconnect)
	 JRST SNDN95		; Foo, temp error

	SETZB A,B	; Success return
	JRST POPCJ

SNDN90:	CAIE A,MR$TER	; Investigate failure return.
	 CAIN A,MR$TEH
	  CAIA
	   JRST POPCJ	; SLP to message in B.
SNDN95:	MOVEI B,0	; Temporary return.
	JRST POPCJ
SUBTTL SNDPGM - Send to Program, running as inferior.

;;; Send to Program.  Arrgh!  In default mode, routine
;;; only has responsibility for loading and starting, after
;;; which it returns successfully.  Failure return happens when
;;; the program cannot be found; temporary return when program
;;; is inaccessible for temp reasons.

; Routine given A$RCP of type PGM (LP in A to rcpt list) holding filename spec
; of program to load up and run, using message text as JCL.
; Default actions are to stick with the job, serving any JCL requests
; and handling interrupts, until either it has used 2 sec of run time
; or 1 minute of real time, whereupon it is disowned with BUMRTL set and
; left to its own devices.  These actions can be altered with the
; following attribute:

; The A$RPGD (R-PGM-DISOWN) value is interpreted as follows:
; RH -  corresponds to the ctl bits for the DISOWN symbolic call, which
; will be used when and if the job is disowned.  These bits are all
; passed on directly, with the following special actions:
;    Bit 1.4 is ALWAYS complemented - i.e. you must specify it to avoid the
;	"default" of setting BUMRTL (system guns job if it does nothing for 1/2 hr)
;    Bit 1.3 will cause immediate disowning.  Obviously no JCL can be supplied
;	for this case, and the LH bits are ignored.
; LH - bits specify mailer actions.
;    Bit 3.1 Says job never wants JCL.  This allows the mailer to proceed
;	asynchronously, handling interrupts as they occur.
;    Bit 3.2 Says job will want JCL again even after it manages to read
;	it all successfully.  This forces mailer to "stick with" job
;	until it gets disowned.
;    Bit 3.3 Says if disowning is attempted, job should instead be KILLED
;	and an error reported.  
;    Bit 3.4 Says that bits 4.9-4.1 contain the # secs of runtime job should
;	be allowed - real time is ignored.  Dangerous!!
;    Bit 3.5 Says job should not be run if another already exists with
;	identical UNAME/JNAME.  Note that this is checked BEFORE any
;	USET's are done (so setting JNAME that way will lose).  JNAME is
;	FN1 unless that is "TS", in which case it's the FN2.
; Future options?  - disown after n secs runtime, never disown?
; Use a special Batch-job to actually run a given program??
; Disown on lossage instead of killing (for debugging)?
P%NVRJ==1	
P%KEPJ==2
P%KILL==4
P%RTIM==10
P%UNIQ==20

BVAR
PGMQPS:	0	; Holds LP to rcpt's list.
PGMDSN:	0	; Holds value of R-PGM-DISOWN attrib
PGMJOB:	0	; -1 when inferior exists.
PGMJCL:	0	; -1 if have JCL for inferior.
PGMTXL:	0	; Holds # words of JCL we have in PGMAR.
PGMRTM:	0	; Runtime countdown
PGMSLP:	0	; Time to sleep
PGMSTA:	0	; Starting address of pgm.
EVAR

SNDPGM:	PUSHAE P,[C,D,E,L]	; Routine requires L be last pushed.
	MOVEM A,PGMQPS		;save LP pointing to rcpt's list.
	FINDA B,[A$RPGD,,[PGMQPS]]
	 TDCA B,B
	  MOVE B,LISTAR(B)+1		; Get value for PGM-DISOWN.
	MOVEM B,PGMDSN
	FINDA A,[A$RNAM,,[A]]
	 JSR AUTPSY
	STAT (,("Running program-"),RABR,TLS(A))
	SETZM PGMJOB			;not yet created inferior
	MOVE A,LISTAR(A)+1		;get the ascnt ptr
	ADD A,$LSLOC(L)			;make abs
	MOVE B,[PGFDEF,,PGFDEV]		; set up default block
	CALL FILPAR			;parse it
	MOVE A,[SIXBIT /DSK/]
	MOVEM A,PGFDEV			; Force device to DSK, so LOAD will win.

	; Hack JCL...
	SETZM PGMJCL			;Assume will be no JCL
	MOVE A,PGMDSN
	TLNN A,P%NVRJ			; Can we ignore JCL?
	 TRNE A,4			; Bit 1.3 means we certainly won't need any JCL.
	  JRST SNDP04			; Ah, yes!  Skip it...
	FINDA A,[A$MTXT,,[$LLLST(L)]]	; Must furnis JCL. Get msg text
	 JSR AUTPSY
	HLRZ B,LISTAR(A)+1		; Get char cnt
	ADDI B,4
	IDIVI B,5			; and # words
	CAIN B,0			; If nothing there,
	 JRST SNDP04			; Say we don't have any JCL.
	UAROPN [%ARTZM+%ARTCH,,PGMAR ? B]	; Open area for it.
	OUT(TMPC,OPEN(UC$UAR,PGMAR))
	OUT(TMPC,TLS(A))		; Stuff message text into it...
	MOVEM B,PGMTXL			; Save # wds of jcl
	SETOM PGMJCL			;indicate JCL available!
SNDP04:	ECALL OPNPGM,[[4,SNDP05],[11,SNDP05],[20,SNDP05],[27,SNDP05],[46,SNDP05]]
	 JRST SNDP95		; Any error not known perm. is assumed temp...
	JRST SNDP10		; Opened!

SNDP05:	MOVE A,[ASCNT [Couldn't open pgm file]]
	JRST SNDP79

	; Now try to create inferior...
SNDP10:	MOVE A,PGFFN1		;use FN1 as jname, unless it
	CAMN A,[SIXBIT /TS/]	; is "TS", in which case use FN2 as jname
	 MOVE A,PGFFN2

SNDP11:	ECALL OPNJB0,[[4,SNDP30]]
	 JRST SNDP95		;unknown type error, assume temporary.
	.UCLOSE USRI,		;hmph, it already exists. flush it if direct inferior.
	MOVE B,PGMDSN		; Check bit to see if OK to AOS
	TLNE B,P%UNIQ		; This bit set if wants unique job.
	 JRST SNDP90		; Already exists, doesn't want AOS'd, win return.
	AOS A			;"AOS" the jname in A (for now, really do AOS!)
	JRST SNDP11		;try again

SNDP30:	SYSCAL OPEN,[[.UIO,,USRO] ? ['USR,,0] ? [0] ? A ? CERR C]
	 JRST [	CAIN C,12	;was specified job somehow created meanwhile?
	 	 JRST SNDP11	; Try again.
		CAIE C,37	; No core available?
		 CAIN C,6	; or no slots available?
		  JRST SNDP95	; temp error, queue.
		JSR SYSLOS]	; ?? none of these, fail.
	SETOM PGMJOB		;indicate inferior created.
	SYSCAL OPEN,[[.UII,,USRI] ? ['USR,,0] ? [0] ? A ? CERR C]
	 JSR SYSLOS
	.USET USRI,[.RUNAME,,B]
	CSTAT (,(" Job-"),RABR,6F(B),(" "),6F(A))

	; Load the program from file
	SYSCAL LOAD,[CIMM USRI ? CIMM DKIC]
	 JRST [	MOVE A,[ASCNT [Couldn't load pgm file]]
		CSTAT (,(", couldn't load program file, killed."))
		JRST SNDP79]
	.IOT DKIC,PGMSTA		;get starting address
	HRRZS PGMSTA			;flush random PC flags, esp. FPD!
	.CLOSE DKIC,			;no further need for dsk chan
	.USET USRI,[.SUPC,,PGMSTA]	;set inferior's starting address
	.USET USRI,[.RINTB,,INFBIT]	;find interrupt bit for this inferior
	FINDA A,[A$CSN,,[$LLLST(L)]]	; Find sender's name
	 JRST [	FINDA A,[A$SNM,,[$LLLST(L)]]
		 JSR AUTPSY
		JRST .+1]
	CALL CVLS6		; Convert to 6bit
	XCTILO [.USET USRI,[.SXUNAME,,A]]	; and set job's XUNAME to that.
	 JFCL
	SKIPE PGMJCL			;If program has JCL, tell it
	 .USET USRI,[.SOPTION,,[%OPCMD,,]]

	; Now hack optional USETs if any.
	SKIPA D,PGMQPS
SNDP36:	 HRRZ D,LISTAR(D)
	FINDA D,[A$RPGU,,[D]]	; Look for R-PGM-USET
	 JRST SNDP38			; None or none left, let's go!
	SKIPN C,LISTAR(D)+1		; Get LP to list
	 JRST SNDP36			; Ignore if nothing.
	MOVE A,LISTAR(C)+1		; Get LH/Symbol to .USET.
	HRRZ C,LISTAR(C)
	CAIE C,0
	 MOVE C,LISTAR(C)+1		; Get value to .USET to.
	HRLZS A
	JUMPGE A,SNDP36			; Ignore if reading!!
	HRRI A,C			; Get USET word in A
	XCTILO [.USET USRI,A]		; Do it, carefully...
	 JRST [	CALL PGMBGE		; Ugh!
		FWRITE TMPC,[[ILOPR while XCT'ing a USET for job!
USET type ],LHV,A,[, attempted setting to ],HV,C]
		JRST SNDP76]
	JRST SNDP36

	; Ready to start program....
SNDP38:	MOVE A,PGMDSN
	TRNE A,4			; Is bit 1.3 set - immediate disown?
	 JRST SNDP70			; Yep, go off & do it.
	MOVEI B,2			; Get default # secs runtime.
	TLNE A,P%RTIM			; But if explicitly specified,
	 LDB B,[331100,,A]		; Pluck it out.
	IMUL B,[1000000./4]		; & get # of 4-usec ticks.
	MOVEM B,PGMRTM			; Store run-time countdown.
	TLNE A,P%RTIM			; Another test of same bit.
	 SKIPA A,[SETZ-1]		; If set, stick with job forever.
	  MOVEI A,12.			; Else only for 12*5 seconds.
	MOVEM A,PGMSLP
	TLO F,%PGMRN			;tell int. handler to handle the ints, and
	.SUSET [.SIMSK2,,INFBIT] 	;enable ints from inferior
	
SNDP40:	.USET USRI,[.SUSTP,,[0]]	;start it!
SNDP41:	SOSGE PGMSLP
	 JRST SNDP70			;disown after just so long
	.SUSET [.SADF2,,INFBIT]		;clear defer bit
	MOVEI C,30.*5			;sleep for 5 sec
	.SLEEP C,
	.SUSET [.SIDF2,,INFBIT]		;defer... don't let it interrupt.
	.USET USRI,[.RRUNT,,C]		;read run-time so far
	CAML C,PGMRTM			; Greater than allowed?
	 JRST SNDP70			;indeed. disown it.
	JRST SNDP41			;not yet, keep waiting

	; Dismiss to here upon inferior interrupt!
SNDP60:	.USET USRI,[.RPIRQC,,C]	; (Used below)
	.USET USRI,[.RSV40,,A]	;get .BREAK inst or whatever it is...
	LDB B,[$OPCOD,,A]	;What instruction caused it 
	CAIN B,<.BREAK_-33>
	 JRST SNDP62		;Go handle a .BREAK .
	CAIE B,<.LOGOUT_-33>
	 JRST SNDP75		;Not a .LOGOUT either, kill noisily.
	LDB B,[$ACFLD,,A]
	 JUMPE B,SNDP75		;.LOGOUT 0, is an error, kill noisily.
	JRST SNDP77		;Else it wants to die, kill quietly.

; Come here if the job did some .BREAK .
SNDP62:	LDB B,[$ACFLD,,A]
	CAIE B,12		;request-info type .BREAK?
	 JRST [	CAIN B,16	;type asking for death?
		 JRST SNDP77	;yes, die quietly
		JRST SNDP75]	;nope, die noisily.
	.ACCESS USRI,A		;point to effective address of .BREAK
	.IOT USRI,A		;get the command word [*** Should protect from MPV ***]
	LDB B,[221100,,A]	;see what type
	CAIE B,5		;JCL related?
	 JRST SNDP75		;nope, ugh
	.USET USRI,[.SAPIRQC,,[%PIBRK]] ;turn off the interrupt for inferior
	JUMPL A,[SETZM PGMJCL	;if type=write, indicate JCL no longer avail.
		.USET USRI,[.ROPTION,,A]
		TLZ A,%OPCMD
		.USET USRI,[.SOPTION,,A]
		JRST SNDP40]
	SKIPN PGMJCL		;if JCL not avail, ignore request to read.
	 JRST SNDP40

	;now write JCL into place specified by RH of A.
	MOVN B,PGMTXL		;make aobjn pntr to jcl
	HRLZ B,B
	HRR B,$ARLOC+PGMAR	; Get RH = loc of text.
	.ACCESS USRI,A
	.ACCESS USRO,A
SNDP64:	.IOT USRI,C		;read to ensure zero
	JUMPN C,SNDP40
	MOVE C,(B)		;if zero, get string word
	.IOT USRO,C		;and write into inf.
	AOBJN B,SNDP64
	;that's all-- no need to write terminating zero wd since wd is already
	;zero if such writing is possible!
	STAT (,("Program gobbled all JCL..."))
	JRST SNDP40
	
	;come here to disown job and let it run by itself.
SNDP70:	MOVE A,PGMDSN
	TRC A,10		; Complement 1.4 ...
	TRNE A,4		; Is 1.3 (start after disown) set?
	 JRST SNDP72		; Yup, go do that.
	TLNN A,P%KILL		; Well, is bit set to kill on disown attempt?
	 JRST SNDP72		; Nope, go ahead.
	CALL PGMBGE		; Ugh!  Must kill it.
	FWRITE TMPC,[[Program timed out, killing instead of disowning!]]
	CSTAT (,("Timed out, killing."))
	JRST SNDP76

SNDP72:	SYSCAL DISOWN,[CTLI (A) ? CIMM USRI]
	 JSR SYSLOS			;glub?
	.SUSET [.SAMSK2,,INFBIT]	;disable inferior ints.
	CSTAT (,(" Disowned."))
	JRST SNDP90			;done...

	;come here to kill job for some reason, noisily.  send message back
	;detailing circumstances.
SNDP75:	CALL PGMBGE		; Init for err processing.
	FWRITE TMPC,[[RUN-invoked job hit fatal interrupt.  File run was
],TI,133,6F,PGFDEV,[:],6F,PGFDIR,[;],6F,PGFFN1,[ ],6F,PGFFN2,TI,135,[  Relevant info follows:
]]
	MOVSI B,-20			;Dump ACs
	.ACCESS USRI,[0]
SNDP74:	.IOT USRI,A
	FWRITE TMPC,[RHV,B,[/ ],HV,A,[
]]
	AOBJN B,SNDP74
IRP VAR,,[UPC,JPC,MPVA,PIRQC,IFPIR,DF1,DF2,PICLR,RUNT,UUOH]
	.USET USRI,[.R!VAR,,A]
	FWRITE TMPC,[[.!VAR!/ ],HV,A,[
]]
TERMIN
	.USET USRI,[.RBCHN,,B]
	HRLZ A,B
	ADD A,[.RIOS,,A]
	.USET USRI,A
	FWRITE TMPC,[[.BCHN/ ],OCT,B,[
.IOS (of above)/ ],HV,A,[
]]
	TRNN C,%PIIOC			; If stopped on an IOC,
	 JRST SNDP76
	SYSCAL RFNAME,[CIMM USRI ? B	; find filenames for channel.
		CRET A ? CRET C ? CRET D ? CRET B]
	 JSR SYSLOS
	FWRITE TMPC,[[LOSING FILE: ],6F,A,[:],6F,B,[;],6F,C,[ ],6F,D,[
]]	
SNDP76:
	;now kill it
	AOSN PGMJOB
	 .UCLOSE USRI,
	CSTAT (,(" Killed."))
	.SUSET [.SAIFPIR,,INFBIT]
	JRST SNDP85		;go send text

	;here, kill job quietly.
SNDP77:	AOSN PGMJOB
	 .UCLOSE USRI,		;die.
	CSTAT (,(" Qilled."))
	.SUSET [.SAIFPIR,,INFBIT]
	JRST SNDP90		; we won!

SNDP79:	MOVE B,A
	CALL PGMBGE		; Initialize for error processing.
	FWRITE TMPC,[TC,B,[ - ],ERR,,]	; Get err msg
	JRST SNDP85

	;Bad condition of some sort during startup.  try to complain by
	;sending message.  ASCNT ptr in A to text.
SNDP80:	CSTAT (,(" Lost somehow"))
	CALL PGMBGE		; Init for err stuff.
	FWRITE TMPC,[TC,A,[
]]

	;send message to specified recipient detailing circumstances of
	;lossage. Create subject, sender name, etc.
SNDP85:	.CLOSE DKIC,		;program file may still be open
	AOSN PGMJOB		;skip if no job.
	 .UCLOSE USRI,		;else flush it.
	SKIPN $AROPN+PGMAR
	 JRST SNDP86
	FWRITE TMPC,[[Message follows:
-------
],TA,PGMAR]
SNDP86:	MAKELN A,[A$MTXT,,0 ? %LTSAO,,]
	PUSH P,L
	MOVE L,$ARLOC+MSGAR
	FINDA D,[A$TIM,,[$LLLST(L)]]
	 JSR AUTPSY
	MOVE D,LISTAR(D)+1		; Set up time
	FINDA C,[A$RPMR,,[PGMQPS]]	; Find name of maintainer if any.
	 SETZ C,			; None...
	POP P,L
	JUMPN C,[LNCOPY C,[$ARLOC+MSGAR ? C]
		MOVEI B,A$RCP
		DPB B,[$LAFLD,,LISTAR(C)]	; Copy into an A$RCP LN.
		JRST .+1]
	HRRM C,LISTAR(A)		; Cons onto message text.
	MOVEM A,$LLLST(L)
	MOVE B,D
	PUSH P,MF
	MOVEI MF,M%CORG+M%SSBJ+M%EMSG+M%ERR
	CALL MAIL
	 JFCL
	POP P,MF
	JRST SNDP99

			; For time being, loss return is actually win, since
			; err msg has already been sent and we don't need helpful
			; calling routine sending another one for us!

SNDP95:	MOVEI A,MR$TER		; Temp. error return.
	SETZ B,			; Indicate no message.
	JRST SNDPRT
SNDP90:	SNDP99:
	SETZB A,B			; No return of messages.
SNDPRT:	TLZ F,%PGMRN
	UARCLS PGMAR	;Flush copy of message made for JCL, if it's still around
	POPAE P,[L,E,D,C]
	RET
	

REMMSK:	0
	3777,,-1 ; 1 char in wd, mask out last 4
	17,,-1	; 2, zap last 3
	77777	; last 2
	377	; last 1

	;default file blk for pgm running
PGFDEF:	SIXBIT /DSK/
	SIXBIT /SYS/
	SIXBIT /TS/
	SIXBIT /FOO/

BVAR
PGFDEV:	0
PGFDIR:	0
PGFFN1:	0
PGFFN2:	0
INFBIT:	0		;holds interrupt bit for inferior
EVAR

OPNPGM:	SETZ ? SIXBIT /OPEN/ ? [.UII,,DKIC] ? PGFDEV
	PGFFN1 ? PGFFN2 ? SETZ PGFDIR

UBPFJ==10	;fail with 4 if no such job exists anywhere.
OPNJB0:	SETZ ? SIXBIT /OPEN/ ? [UBPFJ+.UII,,USRI] ? ['USR,,0]
	[0] ? A ? SETZ CERR C

	; Initialize for error when hit one in pgm.
PGMBGE:	PUSH P,A
	MOVEI A,EMSGAR
	CALL LSEOPN		; Open LSE for err message.
	MOVE L,$ARLOC+EMSGAR
	SAOBEG TMPC,		; Open chan for outputting on.
	POP P,A
	RET


SUBTTL SNDBUG, SNDBUL, SNDFIL, SNDAFL - various msg senders

	; For rcpts of type BUG.
SNDBUG:	PJRST SNDNAM		; For time being.

	; For rcpts of type @FILE.
SNDAFL:	JSR AUTPSY		; Shouldn't try to send.

	; For rcpts of type FILE.
SNDFIL:	PUSH P,C
	MOVE C,A
	FINDA A,[A$RNAM,,[C]]
	 JSR AUTPSY
	MOVE A,LISTAR(A)+1
	ADD A,$LSLOC(L)
	MOVE B,[LMFDEF,,LMFDEV]
	CALL FILPAR
	MOVEI A,LMFDEV
	MOVE B,C
	POP P,C
	PJRST WRTMSG

	; Rcpt type *MSG - Send Bulletin-board *MSG.
SNDBUL:	CALL BBDDFS		; Set up defaults for filename into LMFDEV.
	MOVEI B,LMFDEV
	EXCH A,B		; FILPT in A, LP to rcpt list in B.
	PJRST WRTMSG

BBDDFS:	PUSH P,B
	MOVE B,[LMBDEF,,LMFDEV]		; Set up defaults for filename.
	BLT B,LMFDEV+3
	FINDA B,[A$MFN1,,[$LLLST(L)]]	; First FN1 specified?
	 CAIA
	  JRST [MOVE B,LISTAR(B)+1	; If so, plug in.
		MOVEM B,LMFDEV+2
		JRST .+1]
	FINDA B,[A$MFN2,,[$LLLST(L)]]	; Second FN2 specified?
	 CAIA
	  JRST [MOVE B,LISTAR(B)+1
		MOVEM B,LMFDEV+3
		JRST .+1]
	POP P,B
	RET

LMBDEF:	SIXBIT /DSK/
	SIXBIT /.MSGS./
	SIXBIT /*MSG/
	SIXBIT />/

SUBTTL SNDNAM - Local Message sending

; SNDNAM - Sends normal NAME-type local message.
;	A has LP to rcpt's list.
;	returns in A, B like SNDMSG

LVAR LMPRCP:	0	; Holds LP to rcpt's list.

SNDNAM:	PUSHAE P,[C,D,E,LMPRCP]
	MOVEM A,LMPRCP		; Save LP to rcpt list
	FINDA A,[A$RNAM,,[LMPRCP]]
	 JSR AUTPSY
	MOVE E,LISTAR(A)+1
	ADD E,$LSLOC(L)		; Get ASCNT name in E.
	HLRZ D,E		; Check length of name.
	JUMPN D,SNDN10		; Null recipient?

	;; Hmmm, this message does not appear to be addressed to anyone.
	;; (Possibly this is a COMSAT error receipt failing.)  
	;; We will generate an error receipt with no recipients, so that
	;; this will be routed to DEAD-MAIL-RECEIPTS.

	STAT (,("	Null UNAME for local recipient!"))
	FINDA B,[A$RHDR,,[LMPRCP]]
	 JRST [	FINDA B,[A$MHDR,,[$LLLST(L)]]
		 JSR AUTPSY
		JRST .+1]
	MOVE B,LISTAR(B)+1		; B gets ASCNT to message header.
	ADD B,$LSLOC(L)
	FINDA C,[A$MTXT,,[$LLLST(L)]]
	 JSR AUTPSY
	MOVE C,LISTAR(C)+1		; C gets ASCNT to message text.
	ADD C,$LSLOC(L)
	UARPUSH EMSGAR			; Let's cons up an error receipt.
	OUTCAL(TMPC,CALL(EMSOPN))	; Open 
	PUSH P,L			; Stash LSE ptr.
	MOVE E,L			; Make copy for UUO rtns.
	MOVE L,$ARLOC+EMSGAR		; Switch to error message LSE.
	CALL IDSET			; Create ID for error receipt.
	CALL DATIME"TIMGET		; CDR holds message time.
	MAKELN A,[A$TIM,,0 ? %LTVAL,,[A]]
	HRRM A,$LLLST(L)		; Receipt will have no recipient!
	OUT(TMPC,("Message apparently addressed to local nobody??"),EOL)
	OUT(TMPC,("Text of bad message follows:"),EOL,("--------"),EOL)
	OUT(TMPC,TC(B),EOL,TC(C),EOL)	; Simply output the text.
	CALL EMSSND			; Now go send the message!
	UARPOP EMSGAR			; Recover LSE.
	POP P,L				; Recover ptr to it.
	JRST LMSN90			; Make like we delivered the message.

	;; Okay, apparently we actually have some kind of local recipient.

SNDN10:	MOVE D,[LMFDEF,,LMFDEV]
	BLT D,LMFDEV+4-1	; Set up defaults
	MOVE A,E		; Recipient name.
	CALL CVT76C		; Get SIXBIT name in A.
	HLRZ D,E		;get char cnt of name
	CAILE D,6
	 JRST [	STAT (,("	Name too long-- "),C(42),,TC(E),C(42),("."))
		FINDA D,[A$RNK,,[LMPRCP]]	;is recipient known?
		 JRST [	CSTAT (,(" Rcpt known, message not sent."))
			JRST LMSN90]		;return as "win" if known.
		CSTAT (,(" Rcpt not known, converted to "),C(42),6F(A),C(42))
		JRST .+1]	;not known, so send to truncation.
	MOVEM A,LMFFN1		;store into file block
	FINDA D,[A$RHSN,,[LMPRCP]]	; Must rcpt use a special dir?
	 CAIA				; Nope, assume <name>;<name> MAIL.
	  MOVE A,LISTAR(D)+1		; Yes, get 6bit dir name to use.
	CALL HASDIR		; Make sure dir really exists...
	 MOVE A,['COMMON]	; No, must use COMMON;<name> MAIL.
	MOVEM A,LMFDIR		; Store final choice in file block,
	CAME A,LMFFN1		; and if different from FN1 (usual case),
	 JRST [	CSTAT (,LPAR,6F(A),(";"),RPAR)	; document it in STATS.
		MOVE A,LMFFN1		; and ensure A has rcpt uname.
		JRST .+1]

	;now send as specified (push, append, cli-only, etc)
LMSN20:	SKIPN SORMSW
	 JRST LMSN50		;just mailing.

	; Sending. UNAME in A, LP to rcpt's list in B.
	MOVE B,LMPRCP
	CALL CLIMSG
	 CAIA
	  JRST [SKIPLE SENDSW	; Won. if > 0 then
		 JRST LMSN50	; also mail,
		JRST LMSN90]	; else done.
	SKIPGE SENDSW	; Failed. If  < 0, then
	 JRST LMSN90	; nothing else to do.

	; Here, a mail-if-failed SEND just failed, and we have to mail it!
	; Ugh, bletch, puke, phooey.
	; Must cons up new rcpt, sender, and message text.
	PUSH P,L
	MOVEI A,EMSGAR
	CALL LSEOPN		; Create new LSE for new message
	MOVE L,$ARLOC+EMSGAR	; Point at it

	EXCH L,(P)
	FINDA A,[A$RNAM,,[LMPRCP]]	; Find rcpt name
	 JSR AUTPSY
	FINDA B,[A$RNAM,,[LMPRCP]]	; and type
	 SETZ B,
	FINDA C,[A$SNM,,[$LLLST(L)]]	; and sender's name
	 JRST [	FINDA C,[A$CSN,,[$LLLST(L)]]
		 JSR AUTPSY
		JRST .+1]
	EXCH L,(P)

	MOVE D,(P)		; Set up ptr to LSE (UUO's shouldn't ref PDL)
	LNCOPY A,[D ? A]		; Copy rcpt name
	LNCOPY B,[D ? B]		; and type.
	LNCOPY C,[D ? C]		; and sender's name.
	HRRM B,LISTAR(A)		; Link type to name,
	MAKELN A,[A$RCP,,[C]		; and put under A$RCP, linking to 
		%LTLST,,[A]]		;   sender's name all at once.
	MOVEI B,A$CSN
	DPB B,[$LAFLD,,LISTAR(C)]	; Force sender name to claimed-from
	EXCH L,(P)
	FINDA B,[A$RHDR,,[LMPRCP]]	; Now find rcpt's header
	 JRST [	FINDA B,[A$MHDR,,[$LLLST(L)]]
		 JSR AUTPSY
		JRST .+1]
	MOVE B,LISTAR(B)+1		; Get absolute ascnt ptr
	ADD B,$LSLOC(L)
	FINDA C,[A$MTXT,,[$LLLST(L)]]	; and message text.
	 JSR AUTPSY
	MOVE C,LISTAR(C)+1		; Get absolute ascnt ptr
	ADD C,$LSLOC(L)
	EXCH L,(P)

	; Now form message text, and send the message.  What happens
	; here is that we just include the old header as part of the
	; message text, because we hve no way of knowing what sort
	; of header the rcpt(s) may want, and still have to show who
	; the original QSEND was addressed to!
	SAOBEG TMPC,			; Now form text of message.
	FWRITE TMPC,[TI,133,[COMSAT: This was a failing QSEND.],TI,135,[
],TC,B,TC,C]
	MAKELN A,[A$MTXT,,[A]
		%LTSAO,,]
	MOVEM A,$LLLST(L)		; Finalize.

	PUSH P,MF
	MOVEI MF,M%CORG+M%EMSG
	CALL MAIL		; Mail it!!
	 JFCL
	POP P,MF
	POP P,L
	JRST LMSN90		; Gasp wheeze pant, finally finished.

	; Mail.
LMSN50:	MOVEI A,LMFDEV
	MOVE B,LMPRCP		; Set up LP to rcpt list.
	CALL WRTMSG		;mail message!
	CAIE A,0
	 JRST LMSNRT		; If failed, return.

; Now decide whether or not to CLI to the rcpt.

	SKIPE SORMSW		; If we were intended to SEND,
	 JRST LMSN90		;    then all's done.
	SKIPGE MAILSW		; If suppressing notification,
	 JRST LMSN90		;    also done.
	
	TRNE MF,M%CORG		; If the message is from COMSAT, then skip the check
	 JRST LMSN85		;    (this is kind of random, but I found it here...)

	FINDA A,[A$SNM,,[$LLLST(L)]]	;find sender's name
	 JSR AUTPSY
	CALL CVLS6		; Convert to 6bit
	CAMN A,LMFFN1		;compare unames
	 JRST LMSN90		;equal, don't send!

LMSN85:	MOVE A,LMFFN1		; Get uname
	MOVE B,LMPRCP		; Get LP to rcpt's list.
	CALL CLIOPT		; Notification is up to rcpt...
	 JFCL

LMSN90:	SETZB A,B
LMSNRT:	POPAE P,[LMPRCP,E,D,C]
	RET
	

	;file to write message to.
BVAR
LMFDEV:	0	;default of "dsk"
LMFDIR:	0	;default of "common"
LMFFN1:	0	;default of "failed"
LMFFN2:	0	;default of "mail"
EVAR

LMFDEF:	SIXBIT /DSK/	;defaults
	SIXBIT /COMMON/
	SIXBIT /FAILED/
	SIXBIT /MAIL/

BCHPTB:	350700,,B
	260700,,B
	170700,,B
	100700,,B
	010700,,B

SUBTTL Actual local mailing

; WRTMSG - A has FILPT, B has LP to rcpt's list.  Writes msg to given file.
;	  This returns the same kind of error code as all the SNDxxx routines.
;	  See the dispatch table at RSND09.

.SCALAR WRFFAP	; -1 when FAST-APPEND; means DKOC open in write-over mode.

WRTMSG:	PUSHAE P,[C,D]
	MOVEM A,WFILPT		; Save FILPT.
	MOVEM P,WRTLEV'
	MOVEI C,WRTRLT		; Set up addr to return to if we time out.
	MOVEM C,RLTVEC
	CLKSET [5*60.*60.]	; Set up the clock for 5 minutes
	CLKON			; And enable interrupts
	call mbxlok		; Try for inbox lock
	 jrst wrtlkd		; Lost: temporary error
	CALL DEVCHK		; Set flags as to type of device.
	OUT(DKOC,OPEN(UC$BUF))	; And set up UUO chan for buffered output.

	; Check for FAST-APPEND option.
	TRZ F,%LMAPN		; Say not appending
	SETZM WRFFAP		; and not doing it fast-append way either.
	FNDSTR C,B,A$ROPT,[FAST-APPEND]	; Rcpt has the option?
	 JRST WRTM20		; Nope, do regular append or push.
	TRO F,%LMAPN		; Has it!  Proclaim append-mode.
	TRNN F,%DSKDV		; Device a DSK-type frob?
	 JRST WRTM20		; Nope, must hack regular way.

	; OK to try write-over mode!
	MOVE C,B		; Save rcpt-list ptr (B gets err code)
	CALL OPNAPP		; Attempt opening in append mode.
	 JRST [	EXCH B,C
		JUMPGE C,WRTM95	; Jump if temporary error
		JRST WRTM99]	; Else permanent...
	MOVE B,C
	SETOM WRFFAP		; WIN! Definitely doing fast-append.  Set flag
	SKIPLE EOFREC		; to avoid rename, and if not at beg
	 OUTCAL(DKOC,EOL)	; then prefix with CRLF,
	JRST WRTM50		; and go output message.

	; Regular mode (push or append) comes here to open output file.
WRTM20:	MOVEI D,WRTMO		; Default .CALL blk for DSK devs (for RENMWO).
	TRNN F,%DSKDV
	 MOVEI D,WRTMVO		; Else use this blk for true filespec.
				; Open output, to WRTM95 if temporary error
	ECALL (D),[[5,WRTM95],[6,WRTM95],[7,WRTM95],[10,WRTM95]]
	 JRST WRTM99		; Permanent errors - return with no skip.

	; Output file opened, now see if need to read input (existing) file.
	; Sometimes can skip completely.
	MOVE C,3(A)		; Get FN2
	CAME C,[SIXBIT /</]
	 CAMN C,[SIXBIT />/]	; FN2 special?
	  JRST WRTM49		; Yes, no need to read existing file.
	FNDSTR C,B,A$ROPT,[NEW-FILE]	; Does rcpt have make-new-file option?
	 CAIA
	  JRST WRTM49			; If so, ignore any input, etc.
				; Open input, to WRTM95 if temporary error
				; to WRTM49 if file does not exist
	ECALL WRTMI,[[4,WRTM49],[6,WRTM95],[7,WRTM95],[10,WRTM95],[16,WRTM95],[23,WRTM95]]
	 JRST WRTM99		; Some other error: permanent error

	; Have both input and output open!  See if must append or push...
	TRNE F,%LMAPN		; Already decided to append?
	 JRST WRTM30		; Yup, go do it.
	FNDSTR C,B,A$ROPT,[APPEND]	; See if rcpt has APPEND option.
	 JRST WRTM50		; Nope, go "push" the msg by writing first.
	TRO F,%LMAPN		; Appending, set flag.

	; Appending...
WRTM30:	CALL DKIOUT		; Copy text from DKIC to current output chan!
	 JRST WRTM95		; IOC error or something, assume temp.
	JRST WRTM50		; Then go write message.

	; Write message!
WRTM49:	TRO F,%LMAPN		; Entry pt for things that ignore input file.
WRTM50:	FNDSTR C,B,A$ROPT,[SUPER-QUOTE]	; Is message being super-quoted
	 CAIA				; for this rcpt?
	  JRST [FINDA C,[A$MTXT,,[$LLLST(L)]] ; Yes, output msg txt directly.
		 JSR AUTPSY
		XCTIOC [OUTCAL(DKOC,TLS(C))]
		 JRST WRTM95		; IOC? temporary.
		JRST WRTM70]		; Done with message!
	FINDA C,[A$RHDR,,[B]]	; Normal processing. Find rcpt's header if any.
	 JRST [	FINDA C,[A$MHDR,,[$LLLST(L)]]
		 JSR AUTPSY
		JRST .+1]
	XCTIOC [OUTCAL(DKOC,TLS(C))]		; Output if there.
	 JRST WRTM95			; IOC? temporary.
	FINDA C,[A$MTXT,,[$LLLST(L)]]	; Get SLP to message text.
	 JSR AUTPSY
	FINDA D,[A$MTXF,,[$LLLST(L)]]	; Is there a flag for msg?
	 JRST WRTM56			; NO
	SKIPE LISTAR(D)+1	; Check flag
	 JRST WRTM56		; Flag not 0, OK.
	MOVE A,C		; UGH!
	XCTIOC [CALL QBKAO]	; Output quoted-type ^_'s.
	 JRST WRTM95		; IOC - temp err.
	JRST WRTM60

WRTM56:	XCTIOC [OUTCAL(DKOC,TLS(C))]	; Output whichever msg text is necessary.
	 JRST WRTM95

	; Now add the "^_^M^J".  Insert CRLF in front if seems necessary.
WRTM60:	MOVE C,LISTAR(C)+1	; Examine last char
	ADD C,$LSLOC(L)
	HLRZ D,C		; Cnt into D,
	HRLI C,440700		; BP in C.
	PTSKIP D,C		; Get pointer to last+1 char!
	LDB C,C			; Snarf last char.
	MOVE A,[ASCNT [
]]
	CAIE C,^J		; Unless it's a LF,
	 MOVE A,[ASCNT [

]]				; add a crlf.
	XCTIOC [OUTCAL(DKOC,TC(A))]
	 JRST WRTM95

	; Message writing done, now see if "pushing".
	; Must add previous file if so.
WRTM70:	TRNE F,%LMAPN		; Skip if pushing.
	 JRST WRTM90		; Nope, all done.
	CALL DKIOUT		; Output stuff from DKIC over std output chan.
	 JRST WRTM95		; IOC, take temp-err return.

WRTM90:	XCTIOC [OUTCAL(DKOC,FRC)]	; Ensure output forced out.
	 JRST WRTM95		; Sigh, so close...
	TRNE F,%DSKDV		; Was output to a DSK device?
	 SKIPE WRFFAP		; Yes, was fast-append specified?
	  CAIA			; Not disk, or using fast-append, skip rename.
	   JRST [MOVE A,WFILPT	; Must rename output file!
		SYSCAL RENMWO,[CIMM DKOC
			2(A) ? 3(A)]	; Rename to proper names if so.
		 JRST WRTM99		; Glub, failed for some reason?
		JRST .+1]
	OUT(DKOC,CLS)	; Close the channel (both UUO and actual)
	SETZB A,B
	JRST WRTMRT		; Win return.

WRTRLT:	MOVE P,WRTLEV		; Timed out trying to write local message
	CSTAT (,("...TIMEOUT"))
	JRST WRTM96

	; Temporary errors come here.  Message will be queued,
	; and re-tried later.
WRTM95:	CSTAT (,("...TEMP ERR="),LBRC,ERR,RBRC)	; Return here for temp err.
WRTM96:	OUT(DKOC,RST,CLS)	; Reset and close the buffered chan.
	MOVEI A,MR$TER	; Code for temp error but don't give up on this host!
	SETZ B,		; Not returning any err message.
	TRNN F,%DSKDV	; Was output to dsk-type dev, hence to fake fnm?
	 JRST WRTMRT	; No, leave things as they are.
	MOVE C,WFILPT	; It was!  Must be public-spirited and
	SYSCAL DELETE,[(C) ? [SIXBIT /_MAIL_/] ? [SIXBIT /OUTPUT/] ? 1(C)]
	 JFCL
	JRST WRTMRT		; Flush the abortive output file.

	; Failure to lock inbox is a temporary error too:
wrtlkd:	cstat (,("... Inbox locked."))
	MOVEI A,MR$TER	; Code for temp error but don't give up on this host!
	SETZ B,		; Not returning any err message.
	jrst wrtmrt

	; Permanent errors come here.  The message will not be queued, and
	; COMSAT will try to return it to the sender, along with an
	; appropriate error string (given by SLP in B)
WRTM99:	CSTAT (,("...LOST, ERR="),LBRC,ERR,RBRC) ; Return here for perm err.
	MOVE A,WFILPT
	SAOBEG TMPC,
	FWRITE TMPC,[[Couldn't write message to file;
	"],6F,(A),[:],6F,1(A),[;],6F,2(A),[ ],6F,3(A),[" - ],ERR,,]
	MAKELN B,[0 ? %LTSAO,,]	; Make output into a SLN, with SLP in B.
	OUT(DKOC,RST,CLS)	; Reset and close the buffered chan.
	MOVEI A,MR$PER		; Code for perm error
WRTMRT:	CLKOFF
	SETZM RLTVEC
	.close lockch,		; Release inbox lock
	POPAE P,[D,C]	; Losing return.
	RET

.SCALAR WFILPT		; For holding argument FILPT while hacking stuff.

WRTMVO:	SETZ ? SIXBIT/OPEN/ ? [.UAO,,DKOC]
	(A) ? 2(A) ? 3(A) ? SETZ 1(A)

WRTMO:	SETZ ? SIXBIT /OPEN/ ? [.UAO,,DKOC]	; Unit Ascii out (for SIOT)
	(A) ? [SIXBIT /_MAIL_/] ? ['OUTPUT] ? SETZ 1(A)

WRTMI:	SETZ ? SIXBIT /OPEN/ ? [.BII,,DKIC]
	(A) ? 2(A) ? 3(A) ? SETZ 1(A)

; MBXLOK - Seize lock for inbox from FILPT in A.  Skips on success.
mbxlok:	pushae p,[b,c]
	move b,2(a)
	rot b,1
	add b,3(a)
	rot b,1
	add b,1(a)
	idivi b,777773
	hrli c,(sixbit /MBX/)
	syscal open,[moves b ? movsi .uao ? movei lockch
			[sixbit /LOCK/] ? move c]
	 jrst [	caie b,%enafl
		 jsr autpsy	; %ENSDV or %EFLDV ?
		jrst .+2 ]
	  aos -2(p)
	popae p,[c,b]
	ret

; QBKAO - Takes an SLP in A, and outputs the string to DKOC, converting ^_'s to
;	^ followed by _.

QBKAO:	PUSHAE P,[B,C]
	MOVE A,LISTAR(A)+1
	ADD A,$LSLOC(L)
	HLRZ B,A
	HRLI A,440700	; BP in A, char count in B
	JRST QBKAO6
QBKAO4:	ILDB C,A
	CAIN C,^_
	 JRST [	OUT(DKOC,C("^),C("_))
		JRST QBKAO6]
	OUT(DKOC,C((C)))
QBKAO6:	SOJGE B,QBKAO4
	POPAE P,[C,B]
	RET

; ARDSKO - takes ptr to filblk in A, area # to output in B.
;	skips if written, fails if unable to open file.  outputs
;	text in area.  Returns error string (SLP in A) if open fails.
;	This routine differs from WRTMSG primarily in that it simply
;	writes a file, wheras WRTMSG appends to an existing file.
;	This is used to procude the "NAMED ERRxxx" files.

ARDSKO:	PUSHAE P,[C,D]
	CALL DEVCHK		;set flags for device name (addr in A)
	MOVEI D,ARDVPN
	TRNE F,%DSKDV		;use ardopn if dsk dev,(renmwo hack)
	 MOVEI D,ARDOPN
	.CALL (D)		;try opening file with that dev, dir
	 JRST ARDKO6		;failure, hack error code/string
	OUT(DKOC,OPEN(UC$IOT),TA((B)))	; Set up chan as UUO chan, and output.
	TRNE F,%DSKDV		;done unless dsk dev, if so then
	 JRST [	.CALL ARDRNM	;rename while still open
		 JRST ARDKO7	;failed for some reason; any failure here is permanent.
		JRST .+1]
	.CLOSE DKOC,		;and close to complete write.
	POPAE P,[D,C]
	AOS (P)
	AOS (P)
	RET
	
; check out err code, decide if temporary or not and return error
;string if permanent.
; Temporary errors for writing on DSK devices are:
;	5 - DIR FULL, 10 - DEV NOT AVAIL, 23 - FILE LOCKED (For W-O mode only)
;perhaps also 17 - DIR NOT AVAIL?

ARDKO5:	TRNN F,%DSKDV		;skip if dsk dev, for checking known codes
	 JRST ARDKO8		;ugh lossage
ARDKO6:	CAIE C,5
	 CAIN C,10
	  JRST POPDC1		; Aha, temporary lossage only.
	CAIN C,17		; Test for this too, I guess.
	 JRST POPDC1

	;not dsk dev, or perm. disk error.
ARDKO7:	.CLOSE DKOC,
ARDKO8:	MOVE D,A
	SAOBEG TMPC,
	FWRITE TMPC,[6F,(D),[:],6F,1(D),[;],6F,2(D),[ ],6F,3(D),[ - ],ERR,]
	MAKELN A,[0 ? %LTSAO,,0]
	POPAE P,[D,C]
	RET

ARDVPN:	SETZ ? SIXBIT/OPEN/ ? [.UAO,,DKOC]
	CERR C
	(A) ? 2(A) ? 3(A) ? SETZ 1(A)

ARDOPN:	SETZ ? SIXBIT /OPEN/ ? [.UAO,,DKOC]	; Unit Ascii out (for SIOT)
	CERR C		;get error code if any into acc c
	(A) ? [SIXBIT /_MAIL_/] ? ['OUTPUT] ? SETZ 1(A)

ARDRNM:	SETZ ? 'RENMWO ? CIMM DKOC ? 2(A) ? SETZ 3(A)


; DEVCHK - Device Name checker 
;	Addr in A to 6bit device name,
;	sets %DSKDV if it is dsk-type device,
;	also %NETDV if it is dsk-type and actually a net site (e.g. ML:)

DEVCHK:	PUSH P,A
	TRZ F,%DSKDV+%NETDV
	CAMN A,[SIXBIT /SECOND/]
	 JRST DVCHK5
	HLRZ A,(A)	;get 3 chars 6bit device name, right justified
	CAIE A,'DSK
	 CAIN A,'COM
	  JRST DVCHK5
	CAIE A,'AI_6
	 CAIN A,'ML_6
	  JRST DVCHK6
	CAIE A,'MC_6
	 CAIN A,'MD_6
	  JRST DVCHK6
	CAIN A,'MX_6
	 JRST DVCHK6
	CAIN A,'SYS
	 JRST DVCHK5
	TRZ A,77
	CAIE A,'AR_6
	 JRST POPAJ
DVCHK5:	TROA F,%DSKDV		;come here when dsk device only
DVCHK6:	 TRO F,%DSKDV+%NETDV	;come here when also net device
	POP P,A
	RET


SUBTTL CLI sending routines

;   CLIOPT - This is for telling a person that he has mail; it is used
; both for notifying real and pseudo recipients.  Errors in CLIing
; are not reported.
; Option-dependent TTY message sender.  A has 6bit of rcpt name (to make
; things easier); B has LP to rcpt's list.

.SCALAR CLIRPT

CLIOPT:	PUSHAE P,[B,C,D]
	MOVEM B,CLIRPT
	FNDSTR C,B,A$ROPT,[NO-CLI]	; First things first... absolutely
	 CAIA				; no notification?
	  JRST CLIOP9			; Nope, just return.
	FINDA C,[A$NMH,,[$LLLST(L)]]
	 JRST CLIOP3
	MOVE C,LISTAR(C)+1	;get foreign originating site if any.
	MOVEM C,CLIANC
	MOVEI C,2
	MOVEM C,CLITYP
	JRST CLIOP4

	; Local-originated message.
CLIOP3:	FNDSTR D,CLIRPT,A$ROPT,[NO-LOCAL-CLI]
	 SKIPA
	  JRST CLIOP9		;don't cli if he doesn't want local
	FINDA C,[A$SNM,,[$LLLST(L)]]	; Find sender name
	 JSR AUTPSY
	MOVE C,LISTAR(C)+1
	ADD C,$LSLOC(L)
	MOVEM C,CLIANC		; And store ASCNT ptr as arg.
	MOVEI D,1		; Say "You have mail from <sender>"
	MOVEM D,CLITYP

	; Find how big rcpt wants it.
CLIOP4:	FNDSTR D,CLIRPT,A$ROPT,[SMALL-CLI]
	 SKIPA
	  JRST [SETZM CLIZMX ? JRST CLIOP6]	;give tiny msg if that's what he wants.
	FNDSTR D,CLIRPT,A$ROPT,[INFINITE-CLI]
	 JRST [	MOVEI D,10.		; Default - use smarts.
		MOVEM D,CLIZMX
		MOVEI D,4
		MOVEM D,CLIZMD
		JRST CLIOP6]
	HRLOI D,377777		; Wants everything in msg...
	MOVEM D,CLIZMX

CLIOP6:	SETOM CLIZSW		; For now, always use lines.
	MOVE B,CLIRPT
	CALL CLIMS
CLIOP9:	POPAE P,[D,C,B]
	RET

; CLIMSG - Send a "TTY message" type message to all users with a given
;	UNAME/XUNAME (in A).  B has the rcpt's attrib list.

CLIMSG:	PUSH P,C
	MOVEI C,3	; Use type 3, and
	MOVEM C,CLITYP
	FINDA C,[A$SNM,,[$LLLST(L)]]	; Find sender name
	 JSR AUTPSY
	MOVE C,LISTAR(C)+1
	ADD C,$LSLOC(L)
	MOVEM C,CLIANC		; And store ASCNT ptr as arg.
	SETZM CLIZSW		; Indicate char limit (faster)
	HRLOI C,377777		; Very big # to send all of msg.
	MOVEM C,CLIZMX
	SETZM CLIWIN
	POP P,C
	CALL CLIMS		; Send it...
	SKIPE CLIWIN
	 AOS (P)
	RET

; CLIMS - Sends a CLI message to all users with a given UNAME/XUNAME (in A).
;	Each user gets the kind of header he wants.
;	This is used when CLIing a piece of mail (which at the moment is
;	all we ever CLI).  It sets up the CLIHDR and CLITXT from the mail.

CLIMS:	PUSH P,C
	FINDA C,[A$RHDR,,[B]]
	 JRST [	FINDA C,[A$MHDR,,[$LLLST(L)]]
		 JSR AUTPSY
		JRST .+1]
	MOVE C,LISTAR(C)+1
	ADD C,$LSLOC(L)
	MOVEM C,CLIHDR
	FINDA C,[A$MTXT,,[$LLLST(L)]]
	 JSR AUTPSY
	MOVE C,LISTAR(C)+1
	ADD C,$LSLOC(L)
	MOVEM C,CLITXT
	MOVEI B,CLISND
	CALL XCTALU		; Call routine for all matching (X)UNAMEs.
	POP P,C
	RET

; takes ascnt ptr in a, returns in a the # lines in string.

NLINES:	PUSHAE P,[B,C,D]
	HLRZ D,A
	JUMPE D,[SETZ A, ? JRST NLINS9]
	HRLI A,440700
	MOVE C,A
	MOVEI A,1
NLINS1:	ILDB B,C
	CAIN B,^J
	 JRST [SOJLE D,NLINS9
		AOJA A,NLINS1]	;only add to cnt if something after a lf.
	SOJG D,NLINS1
NLINS9:	POPAE P,[D,C,B]
	RET


; XCTALU - Execute for All Users/Unames. (I know, delete the "for")
;	Given a UNAME in A, and a routine address in B,  executes
;	the routine once for each HACTRN on the system with a UNAME
;	or XUNAME matching the given one.  Furnishes the routine
;	with specific UNAME in A, but nothing else.  Does not touch
;	regs D or E, so these can be used for arguments.
; Note - if A is zero, it will match every X/UNAME!

XCTALU:	PUSHAE P,[A,B,C]
	MOVE B,['HACTRN]
	MOVE C,@USRHI
XCTAL1:	SUB C,LUBLK
	JUMPLE C,XCTAL7
	CAME B,@JNAME		; Is Jname = HACTRN?
	 JRST XCTAL1		; If not, don't even test uname.
	JUMPE A,XCTAL5		; If arg = 0, match any HACTRN.
	CAME A,@UNAME
	 CAMN A,@XUNAME
	  CAIA
	   JRST XCTAL1		;neither uname nor xuname matches.
XCTAL5:	MOVE A,@UNAME		;get real uname
	CALL @-1(P)		; Execute given routine.
	MOVE A,-2(P)		; Restore uname to look for...
	JRST XCTAL1

XCTAL7:	POPAE P,[C,B,A]
	RET

SUBTTL CLI Sending Primitive Routine.

; CLISND - Hairy primitive for sending CLI messages.  Args are given in the following
; block of variables.  CLITYP & CLIANC determine what will be in the beginning of
; the message.  Then the header (ASCNT in CLIHDR) is sent, andthen the message
; (ASCNT in CLITXT).  CLIZSW, CLIZMX and CLIZMD determine how much of the
; message is cut off; the actual number left unsent (in either characters or lines
; depending on CLIZSW) is returned in A.  The other "returned value" is in CLIWIN, which
; is AOSed every time a successful CLI message gets through.

; Types (in CLITYP)		Arg wanted (in CLIANC)
;  0 (no prelude)			(no arg)
;  1 (You have mail from)		(ASCNT name of "from")
;  2 (You have net mail from)		(host number)
;  3 (TTY message from)			(ASCNT name of "from")

BVAR
CLITYP:	0	; Specify type of CLI-message.  Determines announcement given.
CLIANC:	0	; Argument if any needed for announcement.
CLIHDR:	0	; Give ASCNT string as "header"
CLITXT:	0	; Give ASCNT string as text of message.
CLIZSW:	0	; Switch indicating type of limiting values.  (0 chars, -1 lines)
CLIZMX:	0	; Maximum limiting value
CLIZMD:	0	; Modulus if maximum exceeded.
CLIWIN:	0	; AOS'd whenever CLISND succeeds.
CLILEV:	0	; Value of P when CLISND was entered. 
EVAR
CLIDEV:	SIXBIT /CLI/	; For laziness.

CLITMO:	180.*60.		; 3 minute timeout (sounds good...)

	; Send CLI message using params above.  Takes UNAME in A to send to.
CLISND:	PUSHAE P,[B,C,D,E,CLILEV]
	MOVEM P,CLILEV
	MOVEI B,CLIRLT		; Set up addr to return to if we time out.
	MOVEM B,RLTVEC
	CLKSET CLITMO		; Set up the clock.
	CLKON			; And enable interrputs
	SYSCAL OPEN,[[.UAO,,CLIC] ? CLIDEV ? A ? ['HACTRN]]
	 JRST CLISN9		; If failed, just return.
	OUT(CLIC,OPEN(UC$IOT))	; Aha! Onward!
	OUT(CLIC,C(177))	; Initial rubout tells DDT not to print "Message from"
	SKIPN B,CLITYP		; Get type of msg
	 JRST CLISN2		; Skip announcements if type = 0
	CAIN B,1
	 JRST [	OUT(CLIC,("You have mail from "),TC(CLIANC))
		JRST CLISN2]
	CAIN B,2
	 JRST [	OUT(CLIC,("You have net mail from "),HST(CLIANC))
		JRST CLISN2]
	CAIN B,3
	 JRST [	OUT(CLIC,("TTY message from "),TC(CLIANC),("@"),HST(OWNHST))
		JRST CLISN2]
CLISN2:	SETZ D,
	SKIPE CLIZSW		; Now see whether to hack chars or lines.
	 MOVEI D,1		; Do it this way to be SURE of index.
	MOVE A,CLIHDR		; First count header
	XCT (D)[HLRZS A ? CALL NLINES]
	MOVE B,A		; Save result, and then
	MOVE A,CLITXT		; count text.
	XCT (D)[HLRZS A ? CALL NLINES]
	ADD A,B			; Now have total in A!
	SKIPG CLIZMX		; If Max limit is LE 0,
	 JRST [	OUT(CLIC,SP,LPAR,D(A))		; Just furnish length on single line.
		MOVE C,(D)[	ASCNT [ Chars)]
				ASCNT [ Lines)]]
		OUT(CLIC,TC(C))
		JRST CLISN8]
	OUT(CLIC,(":"),EOL)
	CAMG A,CLIZMX		; Well, now compare our count with maximum...
	 JRST [	OUT(CLIC,TC(CLIHDR),TC(CLITXT))	; Win, just output all.
		JRST CLISN8]
	MOVE C,CLIZMD
	CAMG B,C		; Foo, too much.  See if HDR fits within limit...
	 JRST [	OUT(CLIC,TC(CLIHDR))	; HDR fits, output it
		SUB C,B		; And get limited count for Text.
		MOVE B,CLITXT
		JRST CLISN4]
	MOVE B,CLIHDR		; Not enough for HDR even...

	; Output up to c(C) units from string in B, saying there are c(A) left.
CLISN4:	SUB A,C			; Find # units that will be left over.
	JUMPE D,[HRL B,C	; If units are chars, it's easy.
		OUT(CLIC,TC(B),SP,LPAR,D(A),(" more chars"),RPAR)
		JRST CLISN8]
	SETZ D,			; Clear cnt of chars (don't need type-of-limit now)
	PUSHAE P,[A,B]
	HRLI B,440700
CLISN5:	ILDB A,B		; Get char
	CAIN A,^J		; Each ^J is one line.
	 SOJLE C,.+2		; For each line, decr cnt and jump if done.
	AOJA D,CLISN5		; Else incr cnt of chars seen.
	POPAE P,[B,A]
	HRLI B,1(D)		; Put right count into LH of ASCNT ptr
	OUT(CLIC,TC(B),SP,LPAR,D(A),(" more lines"),RPAR)

CLISN8:	OUT(CLIC,EOL)
	.CLOSE CLIC,
	AOS CLIWIN		; Increment cnt of wins!
CLISN9:
CLIRLT:	SKIPL B,CLILEV
	 JSR AUTPSY		; That was no pdl pointer
	CLKOFF
	SETZM RLTVEC
	MOVE P,B
	POPAE P,[CLILEV,E,D,C,B]
	RET
SUBTTL MASTER Manipulation routines

; IDFIND - Given ASCNT ptr in A to a Message ID, looks up on MASTER
;	and skips if found, else doesn't skip.  Returns LP to LLN in A.

IDFIND:	PUSHAE P,[B,C,L]
	SKIPN $AROPN+MASTER	; Make sure
	 CALL MSTGET		; MASTER is in core!
	MOVE L,$ARLOC+MASTER	; Make MASTER the current LSE
	HRRZ B,$LLLST(L)	; Initialize B = current node
	CAIA
IDFND2:	 HRRZ B,LISTAR(B)	; Get next LN to check.
	JUMPE B,IDFND9		; If LP = 0, search failed.
	FINDA C,[A$ID,,[LISTAR(B)+1]]	; Search list of this node.
	 JSR AUTPSY		; Huh? All should have this.
	SLNEA C,A		; Compare strings
	 JRST IDFND2		; No match, get next.
	MOVE A,B		; Aha, match. return LP to it.
	AOS -3(P)
IDFND9:	POPAE P,[L,C,B]
	RET

; IDDELE - Takes current MASTRP and deletes the message
;	pointed to.  Writes out MASTER file so as to finalize the
;	deletion.

IDDELE:	PUSHAE P,[A,L]
	MOVE L,$ARLOC+MASTER
	MOVE A,MASTRP			; Get LP to doomed LN.
	FINDA A,[A$IDBL,,[LISTAR(A)+1]]	; Get length of LSE-blk on disk.
	 JSR AUTPSY
	MOVE A,LISTAR(A)+1		; Get # words used by LSE.
	ADDM A,$LHMFG(L)		; Increment cnt of garbage wds.
	MOVE A,MASTRP
	LNDEL A,$LLLST(L)		; Now kill it.
	CALL MSTPUT			; Write out MASTER file.
	POPAE P,[L,A]
	RET

SUBTTL MASTER and MSG-LSE Readin/Writeout

; MSGGET - Given SLP in A to a Message-ID string, brings that
;	MSG-LSE into core provided it exists; doesn't skip if not.
;	Checks to avoid reading in a MSG-LSE already in core.
; MSGGTA - Same, but A has ASCNT ptr instead of SLP.

MSGGTA:	PUSH P,A
	JRST MSGGT0
MSGGET:	PUSH P,A
	MOVE A,LISTAR(A)+1
	ADD A,$LSLOC(L)
MSGGT0:	PUSHAE P,[B,L]
	SKIPN $AROPN+MSGAR	; Is MSG-LSE open or not?
	 JRST MSGGT5		; Nope, obviously not in core!
	MOVE L,$ARLOC+MSGAR	; Set current LSE to MSG.
	
	FINDA B,[A$ID,,[$LLLST(L)]]
	 JRST MSGGT5		; If not ID, clearly also not in core.
	SLNEA B,A		; Compare requested ID with current, if any.
	 CAIA
	  JRST MSGGT8		; Already in!  Trivial return.

	; Not currently in core, must look up on MASTER.
MSGGT5:	MOVE B,A		; Save ID string.
	CALL IDFIND		; Look up... Leaves MASTER-ptr in A.
	 JRST MSGGT9		; But if couldn't find, tough.  Don't skip.
	MOVEM A,MASTRP
	MOVE L,$ARLOC+MASTER	; Found! Make MASTER the current LSE.
	FINDA A,[A$IDAD,,[LISTAR(A)+1]]	; Get the disk-address LN.
	 JSR AUTPSY		; none??
	MOVE A,LISTAR(A)+1	; Now get the disk address itself!
	CALL MLSGET		; and now slurp up the LSE.
	 JRST MSGGT7		; lost

MSGGT8:	AOS -3(P)		; Won, skip on return.
MSGGT9:	POPAE P,[L,B,A]
	RET

MSGGT7:	STAT (,(" =="),RABR,(" CAN'T READ IN MSG "),LBRC,TC(B),RBRC,(" BECAUSE "),LBRC,ERR,RBRC,(", FLUSHING "),LABR,("=="))
	CALL IDDELE
	JRST MSGGT9

; MSGDEL - Message Deletion.  Deletes current message from MASTER
;	and writes MASTER out, effectively flushing all traces of message.

MSGDEL:	PUSHAE P,[A,B,L]
	MOVE L,$ARLOC+MSGAR		; Set to MSG-LSE
	FINDA A,[A$ID,,[$LLLST(L)]]	; Find MSG-ID
	 JSR AUTPSY
	MOVE A,LISTAR(A)+1		; Get SPT
	ADD A,$LSLOC(L)			; Abs ASCNT ptr
	CALL IDFIND			; Look up in MASTER
	 JRST MSGDL9			; Not there, nothing to do.
	MOVEM A,MASTRP			; Found it, store LP.
	CALL IDDELE			; and go flush it.
MSGDL9:	POPAE P,[L,B,A]
	TLZ F,%MSGMD			; Certainly no modified message
	RET

; MSGPUT - Writes out current MSG-LSE and MASTER, to effect either a
;	new or changed MSG-LSE.  Inserts proper disk address into
;	MASTER entry and leaves LP to it in MASTRP.

MSGPUT:	PUSHAE P,[A,B,C,L]
	SKIPN $AROPN+MASTER	; Ensure that MASTER
	 CALL MSTGET		; is in core.
	MOVE L,$ARLOC+MSGAR
	SKIPN $AROPN+MSGAR
	 JSR AUTPSY		; Called with no message around (%MSGMD randomly on?)
	FINDA A,[A$ID,,[$LLLST(L)]]
	 JSR AUTPSY
	MOVE A,LISTAR(A)+1
	ADD A,$LSLOC(L)
	MOVE B,A		; Save ascnt ptr
	CALL IDFIND		; Find LP to MSG-ID node, leaves in A.
	 CAIA
	  JRST MSGPT5
	MOVE L,$ARLOC+MASTER		; Doesn't have one, must create one!
	MAKELN A,[A$IDBL,,0 ? 0]
	MAKELN A,[A$IDAD,,[A] ? 0]
	MAKELN A,[A$ID,,[A] ? %LTSTR,,[B]]
	MAKELN A,[A$I,,[$LLLST(L)] ? %LTLST,,[A]]
	MOVEM A,$LLLST(L)

MSGPT5:	MOVEM A,MASTRP
	MOVE C,A		; Save in C
	CALL MLSPUT		; Write out MSG-LSE; disk addr returned in A, len in B.
	PUSH P,B		; Save length.
	MOVE B,A
	MOVE L,$ARLOC+MASTER	; Set MASTER as current LSE
	FINDA A,[A$IDAD,,[LISTAR(C)+1]]	; Find LP to the disk-addr LN.
	 JSR AUTPSY
	MOVEM B,LISTAR(A)+1	; Store the disk address.
	POP P,B			; Recover length
	FINDA A,[A$IDBL,,[LISTAR(C)+1]]
	 JSR AUTPSY
	ADDM B,$LHMFL(L)	; Add into length of MSGS (as seen by MASTER).
	EXCH B,LISTAR(A)+1	; Store disk-block length.
	ADDM B,$LHMFG(L)	; Add previous length to garbage
	SKIPN MKQFLG
	 CALL MSTPUT		; Now output the MASTER LSE.
	POPAE P,[L,C,B,A]
	RET


; MLSGET - Message LSE Readin.  Given .ACCESS ptr in A to use, reads
;	LSE from there into MSG-LSE, clears %MSGMD.
; Skip return if win.

MLSGET:	PUSHAE P,[A,B,L]
	MOVE B,A			; Put .ACCESS ptr in B.
	MOVE A,[MSGAR,,MSGFN1]
	CALL DFGETA			; Get the LSE, using .ACCESS hack.
	 CAIA				; Hmm, investigate.
	  AOS -3(P)			; won, skip
MLSGT9:	TLZ F,%MSGMD			; Clear modify flag.
	POPAE P,[L,B,A]
	RET

; MLSPUT - Message LSE writeout.  Writes current MSG-LSE onto top
;	of MSGS file and returns in A its disk address, in B its length.

MLSPUT:	MOVEI A,MSGFN1
	CALL OPNMFW		; Open in write-over mode, trying hard.
MLSPT:	SYSCAL FILLEN,[CIMM DKOC ? CRET B]
	 JSR AUTPSY
	MOVEI A,MSGAR		; Now have args for LSEOUT...
	CALL LSEOUT		; Put out the LSE!  Returns addr in A,length in B.
	TLZ F,%MSGMD		; Clear modification flag.
	RET
	
; MSGSGC - GC's the MSGS file...  both MSGAR and MASTER should have been
;	written out before this is called.
; Sequence of file operations goes as follows:
; 1) Open <output> for writing, MSGS for reading.
; 2) copy from MSGS to <output>, updating MASTER internally.
; 3) When done, write out new MASTER as NMASTR.
; 4) RENMWO <output> -> NMSGS, and close to finalize.
; 5) Delete MASTER and MSGS in any order.
; 6) Rename NMASTR -> MASTER and NMSGS -> MSGS, also any order.

; If, on initialization, one of MASTER or MSGS is missing, it should be
; replaced by NMASTR or NMSGS as appropriate; error if new version doesn't exist!
; If one wants to be picky, it is possible to recover from crash between
; steps 4 and 5 by checking for existence of both NMASTR and NMSGS even when
; MASTER and MSGS both exist, but why bother?

BVAR
MGCNPT:	0	; To hold new disk address while copying.
MGCECT:	0	; Expected length of new file, should = MGCNPT at end.
EVAR

MSGSGC:	PUSHAE P,[A,B,C,D,L]
	SKIPN $AROPN+MASTER
	 CALL MSTGET		; Ensure MASTER in core
	MOVE L,$ARLOC+MASTER
	MOVE A,$LHMFL(L)	; Get length of MSGS
	SUB A,$LHMFG(L)		; Sub # garbage wds...
	MOVEM A,MGCECT		; Save as expected length of new file.
	STAT (,("Note: GC'ing MSGS, "),OCT($LHMFL(L)),("-"),OCT($LHMFG(L)),("="),OCT(A))
	SETZ A,
	CALL OPNMFO		; Open NMSGS for writing, (standard output fnm)
	SETZM MGCNPT		; Set write pointer to 0.
	MOVEI A,MSGFN1
	CALL OPNMFI		; And MSGS for reading.
	 JSR AUTPSY
	UAROPN [TMPAR ? [2000]]	; Allocate a 1K buffer.
	HRRZ D,$LLLST(L)	; D contains LP for stepping through MASTER.
	CAIA
MSGC10:	 HRRZ D,LISTAR(D)
	JUMPE D,MSGC50		; Jump when finished.
	FINDA A,[A$IDAD,,[LISTAR(D)+1]]	; Get LP to disk address
	 JSR AUTPSY
	FINDA B,[A$IDBL,,[LISTAR(D)+1]]	; & length.
	 JSR AUTPSY

	; Simply copy block specified from MSGS on top of current NMSGS.
	.ACCESS DKIC,LISTAR(A)+1	; Read from given address
	MOVE B,LISTAR(B)+1		; Get cnt

MSGC20:	SUBI B,2000		; Sub 1K from cnt
	MOVNI C,2000		; and use -2000 for IOT
	CAIGE B,0		; unless we subtracted too much,
	 MOVNI C,2000(B)	; in which case add back and use -cnt.
	HRLZS C
	HRR C,$ARLOC+TMPAR		; AOBJN set up.
	PUSH P,C
	.IOT DKIC,C		; Read in.
	POP P,C
	.IOT DKOC,C		; Write out.
	JUMPG B,MSGC20

	; Fall through when done.
	MOVE C,MGCNPT		; Get back new address, within NMSGS.
	MOVEM C,LISTAR(A)+1	; Store it in A$IDAD (Disk-addr) LN.
	SYSCAL RFPNTR,[CIMM DKOC	; Find next address within NMSGS
		CRET MGCNPT]	; I know I can keep a count, this makes SURE.
	 JSR AUTPSY
	JRST MSGC10		; Get another msg.

	; All done with copying, now for file shell game.
MSGC50:	SETZM $LHMFG(L)		; Zero cnt of garbage wds
	MOVE A,MGCNPT
	MOVEM A,$LHMFL(L)	; And set new length of MSGS.
	CAME A,MGCECT		; Same as that expected?
	 JRST [	CSTAT (,(", *Err?* NL="),OCT(A))
		JRST .+1]
	MOVE A,[MASTER,,NMSFN1]	; Now write out NMASTR.
	.IOPUSH DKOC,
	CALL DFPUT
	.IOPOP DKOC,
	TLZ F,%MSTMD
	SYSCAL RENMWO,[CIMM DKOC ? NMGFN1 ? NMGFN1+1]	; Actually rename to NMSGS.
	 JSR AUTPSY		; Bleah?
	.CLOSE DKOC,		; And finalize.  Now have NMASTR, NMSGS.

	; Begin Step # 5.
	SYSCAL DELETE,[SATDEV ? MSTFN1 ? MSTFN1+1 ? SATDIR ? CERR A]
	 JRST [	CAIN A,4	; If already vanished, that's odd...
		 JRST .+1	; but OK anyway.
		JSR AUTPSY]
	SYSCAL DELETE,[SATDEV ? MSGFN1 ? MSGFN1+1 ? SATDIR ? CERR A]
	 JRST [	CAIN A,4	; If already vanished, that's odd...
		 JRST .+1	; but OK anyway.
		JSR AUTPSY]

	; Now do Step # 6
	SYSCAL RENAME,[SATDEV ? NMSFN1 ? NMSFN1+1 ? SATDIR
		MSTFN1 ? MSTFN1+1]
	 JSR AUTPSY
	SYSCAL RENAME,[SATDEV ? NMGFN1 ? NMGFN1+1 ? SATDIR
		MSGFN1 ? MSGFN1+1]
	 JSR AUTPSY

	CSTAT (,(", Done!"))
	POPAE P,[L,D,C,B,A]	; Done.....
	RET

LVAR	GCQFLG:	0	; Flag for when to GC queues.

; MGCFIX - Called at startup to check for crashing during MSGS GC'ing,
;	& thereby fix it.

MGCFIX:	PUSH P,A
	SYSCAL OPEN,[[.UII,,DKIC] ? SATDEV
		MSGFN1 ? MSGFN1+1 ? SATDIR ? CERR A]
	 JRST [	CAIN A,4
		 JRST .+2
		JSR AUTPSY]	; Random file error?
	JRST MGCFX5
	STAT (,("Note: No MSGS file, using NMSGS"))
	SYSCAL RENAME,[SATDEV ? NMGFN1 ? NMGFN1+1 ? SATDIR
		MSGFN1 ? MSGFN1+1]
	 JSR AUTPSY
MGCFX5:	SYSCAL OPEN,[[.UII,,DKIC] ? SATDEV
		MSTFN1 ? MSTFN1+1 ? SATDIR ? CERR A]
	 JRST [	CAIN A,4
		 JRST .+2
		JSR AUTPSY]
	JRST MGCFX8
	STAT (,("Note: No MASTER file, using NMASTR"))
	SYSCAL RENAME,[SATDEV ? NMSFN1 ? NMSFN1+1 ? SATDIR
		MSTFN1 ? MSTFN1+1]
	 JSR AUTPSY

MGCFX8:	.CLOSE DKIC,		; All's well...
	SYSCAL DELETE,[SATDEV ? NMGFN1 ? NMGFN1+1 ? SATDIR]
	 JFCL
	SYSCAL DELETE,[SATDEV ? NMSFN1 ? NMSFN1+1 ? SATDIR]
	 JFCL
MGCFX9:	POP P,A
	RET

; MSTGET - Reads in MASTER.

MSTGET:	PUSH P,A
	MOVE A,[MASTER,,MSTFN1]
	CALL DFGET
	 JSR AUTPSY
	TLZ F,%MSTMD
	POP P,A
	RET

; MSTPUT - Writes out MASTER.

MSTPUT:	PUSH P,A
	MOVE A,[MASTER,,MSTFN1]
	CALL DFPUT
	TLZ F,%MSTMD
	POP P,A
	RET

; QMLGET - Reads in QML.

QMLGET:	PUSH P,A
	MOVE A,[QMLAR,,QMLFN1]
	CALL DFGET
	 JSR AUTPSY
	TLZ F,%QMLMD
	POP P,A
	RET

; QMLPUT - Writes out QML.

QMLPUT:	PUSH P,A
	MOVE A,[QMLAR,,QMLFN1]
	CALL DFPUT
	TLZ F,%QMLMD
	POP P,A
	RET

; RMLGET - Reads in RML.

RMLGET:	PUSH P,A
	MOVE A,[RMLAR,,RMLFN1]
	CALL DFGET
	 JSR AUTPSY
	TLZ F,%RMLMD
	POP P,A
	RET

; RMLPUT - Writes out RML.

RMLPUT:	PUSH P,A
	MOVE A,[RMLAR,,RMLFN1]
	CALL DFPUT
	TLZ F,%RMLMD
	POP P,A
	RET



SUBTTL Data File primitives

; DFGET - Data File Readin.  A = <ARPT>,,<filblk addr>
;	Opens specified file, and reads it into a LSE, using given ARPT.
;	Skips unless file not found.

DFGET:	PUSH P,B
	SETO B,			; Indicate not interested in .ACCESS hacking.
	CALL DFGETA		; Get it.
	 JRST POPBJ		; No such file?
	POP P,B
	AOS (P)
	RET

; DFGETA - Data File Readin, with .ACCESS.  A = <ARPT>,,<filblk addr>
;	and B = .ACCESS ptr.
;	Opens specified file, and reads from specified location into
;	a LSE, using given ARPT.  Skips unless file not found,
;	or IOC error while trying to read (e.g. access pointer off end of file)

DFGETA:	CALL OPNMFI		; Open it, trying hard.
	 RET		; there isn't any such file?
	MOVSS A			; Get ARPT into RH.
	CALL LSEIN		; Read in LSE, using .ACCESS ptr in B.
	 SOSA (P)		; lost, non-skip return
	  .CLOSE DKIC,		; If lost, don't close so as to leave error code around
				; in STASAV if it burns up because of this IOC error
	MOVSS A			; restore A.
	AOS (P)
	RET

; DFPUT - Data File writeout.  A = <ARPT>,,<FN1/2 addr>
;	Writes LSE specified by ARPT to specified file.

DFPUT:	PUSHAE P,[A,B,C]
	MOVE C,A		; Save for later.
	SETZ A,			; Actually open temporary file.
	CALL OPNMFO		; Open for output, trying hard.
	HLRZ A,C		; Put ARPT in RH of A.
	SETO B,			; Indicate not interested in .ACCESS hacking.
	CALL LSEOUT		; Write out the LSE.
	HRRZ A,C		; Restore addr to FN's
	.CALL RNMMFO		; and rename output file to right thing.
	 JSR AUTPSY
	.CLOSE DKOC,
	POPAE P,[C,B,A]
	RET

RNMMFO:	SETZ ? 'RENMWO ? CIMM DKOC ? (A) ? SETZ 1(A)

; OPNMFO - Given addr of FN1/2 in RH of A, tries hard to open
;	for Block Image Output, and skips.  Assumption is that it
;	MUST be possible to write the file.
;	Only error should be 5 = DIR FULL, which is handled.
;	If A = 0, uses a standard temporary output FN.
; OPNMFW - Similar, but in write-over mode without A=0 hack.

OPNMFO:	CAIN A,0
	 MOVEI A,TMPFN1
	SYSCAL OPEN,[[.BIO,,DKOC] ? SATDEV ? (A) ? 1(A) ? SATDIR ? CERR OMFERR]
	 CAIA		; Error?? Must skip to inspect.
	  RET	; Normally win - return.
	PUSH P,[OPNMFO]	; Hack - set up like PUSHJ and drop in.
OPNMLS:	PUSH P,B
	MOVE B,OMFERR
	CAIN B,%ENAFL	; 23 = File locked?
	 JRST [	MOVEI B,30.	; Some loser has file open!  Wait it out...
		.SLEEP B,	; Maybe later take more active action.
		PJRST POPBJ]	; Re-try.
	CAIE B,5	; 5 = DIR FULL is only other reasonable error.
	 JSR AUTPSY	; all others barf instantly.
	POP P,B
	CALL SDFULL	; Call on magic routine... if it returns at all,
	RET		; we can win!  Return to routines to try again.
		; Don't replace by PJRST, for easier debug tracing.

	CALL OPNMLS
OPNMFW:	SYSCAL OPEN,[[%DOWOV+%DORWT+.BIO,,DKOC] ? SATDEV ? (A) ? 1(A) ? SATDIR ? CERR OMFERR]
	 JRST OPNMFW-1
	RET

LVAR OMFERR:	0	; If error happens, code gets stuck here.
TMPFN1:	SIXBIT /_SATEL/
	SIXBIT /OUTPUT/

; OPNMFI - Similar, but opens for .BII instead and skips.

OPNMFI:	SYSCAL OPEN,[[.BII,,DKIC] ? SATDEV ? (A) ? 1(A) ? SATDIR]
	 CAIA
	  AOS (P)
	RET

	; General purpose opener
OPNFLI:	SYSCAL OPEN,[[.BII,,DKIC] ? (A) ? 2(A) ? 3(A) ? 1(A)]
	 CAIA
	  AOS (P)
	RET

SUBTTL File getters -- FILGET, FILIN, TXFGET, TXFIN

; FILGET -  File Getter.  A has <ARPT>,,<FILPT>; specified file
;	is opened and read into specified area; if <ARPT>=0 then
;	an area is created.  In either case the ARPT is returned in A,
;	and routine skips.  If the OPEN fails, its error code is
;	returned instead with no skip.
; FILIN - similar, but assumes DKIC already open on channel, and never skips,
;	since no lossage is possible. (!)
; FILINL - Like FILIN but takes a limit in B of max # wds to read.

FILINL:	PUSH P,B
	JRST FILIN0

FILGET:	.CALL OPNBII		; Try to open FILBLK specified.
	 JRST [	MOVE A,OPNERR	; For failure, return error code.
		RET]
	AOS (P)
FILIN:	PUSH P,B
	HRLOI B,377777		; Read up to maximum!
FILIN0:	PUSH P,C
	MOVE C,B
	MOVE B,[DKIC,,FGTDEV]
	.RCHST B,		; Get channel status for possible later ref.
	SYSCAL FILLEN,[CIMM DKIC ? CRET B ? CERR OPNERR]
	 JRST [	MOVE B,OPNERR	;  Failed?  Should only happen if
		CAIE B,34	;  error = wrong type dev.
		 JSR SYSLOS	;    No. this shouldn't happen.
		MOVEI B,400	;  If no length available, use 1/4 page.
		JRST .+1]
	ADDI B,1		; Add 1 so .IOT ptr won't count out completely
	CAMLE B,C		; If length greater than limit,
	 MOVE B,C		; Force to limit!!
	HLRZS A			; Put ARPT into RH.
	UAROPN A,[(A) ? B]	; Open area, with length at least that of file/
	MOVN B,B		; Negate.
	HRLZ B,B		; For .IOT ptr.
	HRR B,$ARLOC(A)		; Get addr to store it...starting addr of area.
FILGT5:	PUSH P,B
	HLRE B,B
	ADD C,B
	POP P,B
	JUMPL C,FILGT8		; Stop if past limit.
	.IOT DKIC,B		; grab
	HRRZM B,$ARWPT(A)	; set write ptr for area.
	JUMPGE B,[MOVEI B,400	; If counted completely out,
		UAREXP B,(A)	; expand and get more.
		HRRZ B,$ARWPT(A)
		HRLI B,-400
		JRST FILGT5]
FILGT8:	.CLOSE DKIC,
	POP P,C
	POP P,B
	RET

OPNBII:	SETZ ? SIXBIT /OPEN/ ? [.BII,,DKIC] ? CERR OPNERR
	(A) ? 2(A) ? 3(A) ? SETZ 1(A)
BVAR
OPNERR:	0
FGTDEV:	BLOCK 10	;channel status info
EVAR

; TXFGET - Text File Getter.  Like FILGET, but assumes file is text
;	and converts area to text, adjusting EOF properly.
; TXFIN - similar conversion but like FILIN assumes DKIC is open and
;	never skips.

TXFGET:	CALL FILGET		; Open and get file into area.
	 RET			;  Failed.
	AOSA (P)		; Won, skip into conversion & skip on return.
TXFIN:	 CALL FILIN
	UARTYP [%ARTCH,,(A)]	; Convert area to text, 
	RET			; adjusting EOF automatically.

TXFINL:	 CALL FILINL		; Like TXFIN but takes limit in B.
	UARTYP [%ARTCH,,(A)]	; Convert area to text, 
	RET			; adjusting EOF automatically.


;;; EQVLEN - Return in U2 the length of the equivalences binary file.
;;;   This is used by IRQGET to estimate how much space is available.

EQVLEN:	PUSH P,A
	.IOPUSH DKIC,		; Save chan.
	SETZ U2,		; Return size in U2.
	SKIPE $AROPN+EQVAR	; If already in core
	 JRST EQVLE9		;  don't count it.
	MOVEI A,EQVFN1		; EQV binary file.
	CALL OPNMFI		; Try to open it up.
	 JRST EQVLE9		;  Lost!
	SYSCAL FILLEN,[CIMM DKIC ? CRET U2]
	 NOP
	ADDI U2,22.*PG$SIZ	; Measurements seem to be off by this much?
EQVLE9:	.IOPOP DKIC,
	POP P,A
	RET



SUBTTL Disk Input copy

; DKIOUT - Copies from DKIC to DKOC until reaches EOF on DKIC.
;	Tries to be clever about finding "true" EOF, by using only
;	the byte-count length if the input file has byte-size 7, 
;	and if not examines the last input word to flush trailing
;	padding chars (^C, ^L, ^@).

DKIOUT:	PUSHAE P,[A,B,C,D]
	MOVE B,[DKIC,,FGTDEV]	; Arg to RCHST
	.RCHST B,		; Get chan status for possible later ref.
	UAROPN A,[OMFAR ? [PG$SIZ+1]]	; Open buffer area.
	SYSCAL FILLEN,[CIMM DKIC ? CERR OPNERR
		CRET JUNK ? CRET B ? CRET C ? CRET D]
	 JRST [	MOVE B,OPNERR	; Failed?  Should only happen if
		CAIE B,34	; error = wrong type dev.
		 JSR SYSLOS	; Shouldn't ever be anything else.
		JRST DKIOU4]	; Hmm, just proceed as for word-mode files.
	JUMPE C,DKIOU8	; Optimize for zero-length pathological case...
	CAIE D,7	; If file wasn't written with byte-size 7 (text),
	 JRST DKIOU4	; go handle word-mode.
	IDIVI C,5	; Text! Find how many words, and get only that many.
	CAIN D,		; If exact multiple of 5,
	 SKIPA D,[5]	; then say 5 chars in last word.
	  ADDI C,1	; else leave D correct, and make sure we read last wd.
DKIOU2:	SKIPG B,C
	 JRST DKIOU9
	CAILE B,PG$SIZ		; If # words to go is too large,
	 MOVEI B,PG$SIZ		; stick to limit.
	SUBI C,(B)		; Update count
	MOVNI B,(B)
	MOVSI B,(B)
	HRR B,$ARLOC(A)		; Set up IOT aobjn-type ptr.
	XCTIOC [.IOT DKIC,B]	; Slurp up.
	 JRST DKIOU9		; Probably a data error if get here.
	JUMPL B,DKIOU6		; If didn't complete, length was wrong!!
	JUMPLE C,DKIOU3		; Jump if this was the last slurp.
	MOVE B,$ARLOC(A)	; Not last,  output full buffer
	HRLI B,PG$SIZ*5
	XCTIOC [OUTCAL(DKOC,TC(B))]	; Output it,
	 JRST DKIOU9		; Failure of some sort.
	JRST DKIOU2		; and go slurp some more.

	; Have last slurp in buffer, B has addr+1 of last wd.
	; D has # chars in the last wd.
DKIOU3:	MOVEI B,-1(B)		; Get addr of last word
	SUB B,$ARLOC(A)		; Find # words
	IMULI B,5		; Get # chars
	ADDI B,(D)		; and include last partial word.
	MOVSI B,(B)
	HRR B,$ARLOC(A)
	XCTIOC [OUTCAL(DKOC,TC(B))]	; Output it!
	 JRST DKIOU9		; Failure of some sort.
	JRST DKIOU8		; Done...

	; Here, file is of indeterminate length.  Read it in
	; and always keep the last word of previous input
	; around for reference; it only gets output if there
	; is in fact more stuff following it.
DKIOU4:	MOVE B,$ARLOC(A)
	HRLI B,-<PG$SIZ+1>	; Get stuff plus one extra wd.
DKIOU5:	XCTIOC [.IOT DKIC,B]	; Gobble gobble
	 JRST DKIOU9
	JUMPL B,DKIOU6		; Snarfed last stuff?
	MOVE C,-1(B)		; Nope, recover last word input
	MOVE B,$ARLOC(A)	; and output all but that last word.
	HRLI B,PG$SIZ*5
	XCTIOC [OUTCAL(DKOC,TC(B))]
	 JRST DKIOU9
	MOVE B,$ARLOC(A)
	MOVEM C,(B)		; Store the remaining word as 1st in buffer
	HRLI B,-PG$SIZ		; and set new count accordingly
	AOJA B,DKIOU5		; so as to IOT above that word.

	; Read in last stuff.  B has addr+1 of last word read.
DKIOU6:	HRLI B,350700		; Make ptr to 1st non-ex char
	MOVE D,$ARLOC(A)	; And get fence for backing up.
DKIOU7:	DBP7 B			; Decrement it, point at previous char.
	CAILE D,(B)		; Continue as long as still in buffer.
	 JRST DKIOU8		; Backed too far, none to output! win.
	LDB C,B			; Get it
	CAIE C,^L		; Test for any of several garbage chars.
	 CAIN C,^C
	  JRST DKIOU7
	JUMPLE C,DKIOU7

	HRLI D,440700
	PTRDIF D,B		; Find # chars, B-D
	MOVSI D,(D)
	HRR D,$ARLOC(A)		; Make ASCNT ptr to string
	XCTIOC [OUTCAL(DKOC,TC(D))]	; and output forthwith.
	 JRST DKIOU9
				; Fall through when done.

DKIOU8:	AOS -4(P)		; Win return.
DKIOU9:	.CLOSE DKIC,		; Don't need any more.
	UARCLS (A)		; Likewise flush area buffer.
	POPAE P,[D,C,B,A]
	RET

SUBTTL	Open DKOC in Append Mode

	; Unfortunately ITS has no way to set file length nor to
	; keep length at original setting until channel closed.
; OPNAPP - Open DKOC in Append mode.
;	A - addr of filblk
; Skips on success,
; else	B - RH has error code for last sys call (or 0 for general lossage)
;		Sign bit set for "permanent" errors.

.SCALAR EOFFIL	; True file EOF, in 7-bit bytes
.SCALAR EOFREC	; "Record" EOF, in ditto.
EOFMRK:	037	; ctl underscore, normal MAIL file record separator.

OPNAPP:	PUSH P,C
	SETZM EOFFIL		; Clear file EOF
	SETZM EOFREC		; and record EOF
OPNAP0:	SYSCAL OPEN,[[.UAI,,DKIC]	; First, try opening for read...
		(A) ? 2(A) ? 3(A) ? 1(A)	; Use true filespec.
	 	CERR B]
	 CAIA
	  JRST OPNAP3
	CAIE B,%ENSFL	; No such file?
	 JRST OPNAP7	; Any other error, fail with check.
	MOVE C,3(A)	; See if AOS'd version exists...
	AOS C
	SYSCAL RENAME,[(A) ? 2(A) ? C ? 1(A)
			2(A) ? 3(A) ? CERR B]
	 CAIA
	  JRST OPNAP0	; Success, try reading with original name!
	CAIN B,%ENSFL	; No such file here either?
	 JRST OPNAP6	; Yup, just create file.
	CAIN B,%EEXFL	; Or File already exists?
	 JRST OPNAP0	; Well, well, try again.
	JRST OPNAP7	; Anything else means just fail, with temp/perm chk.

	; File opened for reading, now find EOF's.
OPNAP3:	SYSCAL FILLEN,[CIMM DKIC ? CRET C ? CERR B]
	 JRST OPNAP9	; Any error is permanent, since should only happen for
			; non-disk devices.

	; We know that bytesize of chan will be 7.  Perhaps later
	; some hair could depend on whether file was written in byte
	; or word mode, but current brutalgorithm works either way.
	MOVEM C,EOFFIL	; Store file's true EOF
	MOVEM C,EOFREC

	; Now read backwards thru file until hit record-EOF mark.
OPNAP4:	SOSG C,EOFREC
	 JRST OPNAP6		; Backed to start, re-create.
	.ACCESS DKIC,C
	.IOT DKIC,B
	CAME B,EOFMRK		; Char = to EOF mark?
	 JRST OPNAP4		; Nope, keep backing up.
	AOS EOFREC		; Win! Set final record-EOF value.

OPNAP6:	.CLOSE DKIC,
	MOVE C,[.UAO,,DKOC]
	SKIPLE EOFREC		; If record EOF is for real,
	 TLO C,%DOWOV+%DORWT	; use write-over mode.
	SYSCAL OPEN,[C		; OK, try opening!
		(A) ? 2(A) ? 3(A) ? 1(A)	; Use true filespec.
	 	CERR B]
	 JRST OPNAP7		; Error now?  Sigh...
	SKIPLE EOFREC		; Successful open!  Need to adjust write ptr?
	 .ACCESS DKOC,EOFREC	; Yep, just do it.
	AOS -1(P)		; Win return!
	JRST OPNAP9		; Value of B doesn't matter.

	; Check out err code to see if should advise temp or perm
	; (i.e. try again later, or give up.)
OPNAP7:	CAIE B,%EFLDR	; Dir full?
	 CAIN B,%ENADV	; or dev not avail?
	  JRST OPNAP8	; either is temp err.
	CAIE B,%ENAFL	; File locked?
	 CAIN B,%ERODV	; Device write-locked?  (This one maybe perm)
	  JRST OPNAP8
	CAIE B,%EFLDV	; Device full? (This one maybe impossible)
	 JRST OPNAP9	; Anything else is PERMANENT error.

OPNAP8:	TLZA B,-1	; Temp err, return positive B.
OPNAP9:	 TLO B,(SETZ)	; Perm err, return negative B.
	POP P,C
	RET

SUBTTL Filename Parsing

; Given ASCNT ptr in A to string, assumes it is bracketed filename and parses
;into block using defaults as specified by <defblk>,,<filblk> in B.

FILPAR:	PUSHAE P,[A,B,C,D,E]
	MOVE E,B
	BLT B,3(E)		;fill block with defaults before parsing
	CALL PARFNM		;parse it
	CAIE A,
	 MOVEM A,(E)		;store dev
	CAIE B,
	 MOVEM B,1(E)
	CAIE C,
	 MOVEM C,2(E)
	CAIE D,
	 MOVEM D,3(E)
	POPAE P,[E,D,C,B,A]
	RET

; given ascnt ptr in a to bracketed filename, returns in a,b,c,d the
;dev/dir/fn1/fn2 with zeros where none found.  parses like ddt, i.e.
;single name treated like fn1, not teco-style fn2.
; Flushes first "[" and last "]" if latter exists... ^Q quotes.

BVAR
PRFC:	0	; cnt for string
PRFP:	0	; bp to string
PRFDEV:	0	; DEV if any
PRFDIR:	0	; DIR  "
EVAR

PRCGT:	SOSGE PRFC	; Read a char
	RET
	ILDB A,PRFP
	AOS (P)
	RET

PARFNM:	HLRZM A,PRFC	;store cnt
	HRLI A,440700
	MOVEM A,PRFP	;and ptr
	SETZB C,D	; Clear FNM's
PARFN0:	CALL PRCGT
	 JRST [SETZB A,B ? RET]	;nothing there? Ensure nothing returned.
	CAIE A,"{
	 CAIN A,"[
	  JRST PARFN1
	CAIN A,40	;flush spaces
	 JRST PARFN0
	AOS PRFC	;else char is beg of filename; back up.
	D7BPT PRFP
	
PARFN1:	SETZM PRFDEV
	SETZM PRFDIR
PARFN2:	SKIPG PRFC
	 JRST PARFN9		;done
	CALL PRCGET		;get token
	CAIN B,":
	 JRST [MOVEM A,PRFDEV ? JRST PARFN2]
	CAIN B,";
	 JRST [MOVEM A,PRFDIR ? JRST PARFN2]
	CAIN B,40
	 JRST [CALL PRFNPT ? JRST PARFN2]
	; Any other terminator = EOF.  Falls thru...

PARFN8:	JUMPE A,PARFN9
	CALL PRFNPT
PARFN9:	MOVE A,PRFDEV
	MOVE B,PRFDIR
	RET

PRFNPT:	JUMPE C,[MOVE C,A ? RET]
	JUMPE D,[MOVE D,A ? RET]
	SKIPN PRFDEV
	 JRST [MOVEM A,PRFDEV ? RET]
	MOVEM A,PRFDIR
	RET

.SCALAR PRC6WD	; Holds accumulated 6bit

PRCGET:	MOVE B,[440600,,PRC6WD]
	SETZM PRC6WD
PRCGT1:	CALL PRCGT
	 JRST PRCGT6
	CAIE A,";
	 CAIN A,":
	  JRST PRCGT7
	CAIE A,40
	 CAIN A,"]
	  JRST PRCGT7
	CAIN A,"}
	 JRST PRCGT7
	CAIN A,^Q		;quoting?
	 JRST [	CALL PRCGT	;insert next char whatever it is.
		 JRST PRCGT6
		JRST .+1]
	CAIL A,141
	 CAILE A,172
	  SKIPA
	   SUBI A,40
	SUBI A,40
	TLNE B,770000
	 IDPB A,B	;don't deposit if already done 6 chars.
	JRST PRCGT1
PRCGT6:	TDCA B,B		;clear B and skip.
PRCGT7:	 MOVE B,A
	MOVE A,PRC6WD
	RET

SUBTTL Random little routines

; LASCIZ - Addr in A to asciz string, returns in A the # chars in string.
;	String must obviously start on word boundary.
;	Faster than LBPASZ (8 instrs/loop and no ILDB'ing).

LASCIZ:	PUSHAE P,[B,C]
	MOVE C,A
	TDCA A,A		;clear A and skip
LASCZ1:	 ADDI A,5
	SKIPE B,(C)
	 TLNN B,774000		; Test 1st char
	  JRST LASCZ7		; Null, done.
	TLNN B,3760		; Test 2nd char
	 AOJA A,LASCZ7		; Null, add 1 and done.
	TDNN B,[17,,700000]
	 JRST LASCZ6		; Go add 2, done.
	TRNN B,77400
	 AOJA A,LASCZ6		; Add 3
	TRNE B,376
	 AOJA C,LASCZ1		; Not ended yet; increment address and add 5.
	ADDI A,4
	JRST LASCZ7
LASCZ6:	ADDI A,2
LASCZ7:	POP P,C
	POP P,B
	RET

; LBPASZ - Takes a BP to an ASCIZ string in A, and returns the number of chars
;	in A.  (Like LASCIZ, but allows any BP).
LBPASZ:	PUSHAE P,[B,C]
	SETZ B,
	ILDB C,A
	CAIE C,0
	 AOJA B,.-2
	MOVE A,B
	POPAE P,[C,B]
	RET

; ASZCNT - Takes address of an ASCIZ string (in A), returns ASCNT
;	 to that string (in A).

ASZCNT:	PUSH P,A	; Save addr
	CALL LASCIZ	; Get length of asciz string.
	HRLM A,(P)	; Store cnt in LH of saved addr.
	POP P,A		; restore as ASCNT ptr.
	RET

; AZMCNT - Takes addr in A of string which stops on either ^@ or ^M
;	and returns ASCNT ptr in A.
AZMCNT:	PUSHAE P,[B,C]
	HRLI A,440700
	HRLZ B,A
	ILDB C,A
	JUMPE C,.+3
	CAIE C,^M
	 AOJA B,.-3
	MOVS A,B
	POPAE P,[C,B]
	RET

; CVT6PC - Takes <cnt>,,[<bp>] in A, returns SIXBIT in A.
CVT6PC:	PUSHAE P,[B,C]
	HLRZ B,A
	MOVE C,(A)
	PJRST CVT6

; CVLS6 - Takes SLP in A, returns SIXBIT in A.
CVLS6:	MOVE A,LISTAR(A)+1	;get SPT
	ADD A,$LSLOC(L)		;make abs and drop thru into CVT76C

; CVT76C - Takes an ASCNT in A, returns SIXBIT in A.
CVT76C:	PUSHAE P,[B,C]
	HLRZ B,A		;cnt in B
	MOVE C,A		;bp in C
	HRLI C,440700
CVT6:	PUSHAE P,[D,E]
	JUMPE B,[SETZ A,
		JRST CVT762]	;if cnt = 0
	CAILE B,6
	 MOVEI B,6		;force to 6 if greater than
	JRST CVT760

; CVT76 - Takes a BP to an ASCIZ string in A, returns SIXBIT in A.
;	Stops when reach 0 or get 6 chars, or hit blank and previous
;	chars were nonblank.

CVT76:	PUSHAE P,[B,C,D,E]
	MOVE C,A
	MOVEI B,6
CVT760:	SETZ A,
	MOVE D,[440600,,A]
CVT761:	ILDB E,C
	CAIN E,40
	 JUMPN A,CVT762	;if hit blank, stop only if something already accumulated
	JUMPE E,CVT762
	CAIL E,141	;convert to uppercase
	 CAILE E,172
	  CAIA
	   SUBI E,40
	SUBI E,40	;convert to 6bit
	IDPB E,D
	SOJG B,CVT761
CVT762:	POPAE P,[E,D,C,B]
	RET

;;;	Various uppercasifiers, all smash the string given in A as argument,
;;;	and clobber no ACs.

; SLNUPR - A/ SLP (LP to string)
; UPPRZ  - A/ ASCNT pointer
; UPPRZB - A/ <char cnt>,,[<byte ptr>]

SLNUPR:	PUSHAE P,[A,B,C]
	MOVE B,LISTAR(A)+1	; Get the SPT for it.
	HLRZ C,B		; Get count
	ADD B,$LSLOC(L)		; Make absolute addr
	HRLI B,440700		; And BP
	PJRST UPRCS7

UPPRZ:	PUSHAE P,[A,B,C]
	HLRZ C,A		; get count in C
	MOVE B,A
	HRLI B,440700		; and BP in B
	PJRST UPRCS7

UPPRZB:	PUSHAE P,[A,B,C]
	MOVE B,(A)		;get byte ptr
	HLRZ C,A		;get cnt
	JRST UPRCS7

UPRCS4:	ILDB A,B
	CAIL A,141		;lower a
	 CAILE A,172		;lower z
UPRCS7:	  SOJGE C,UPRCS4	; Not lowercase, continue.
	JUMPL C,PPCBAJ
	SUBI A,40		;lower, cvt to upper
	DPB A,B			;deposit back
	SOJGE C,UPRCS4
	POPAE P,[C,B,A]
	RET

SUBTTL Random variables, data areas.

;;; Table of ARPT's to all standard ARBLK's to be flushed when coring down.
ARPTBL:	MASTER
	MSGAR
	;SCHDAR		;keep this in core
	QMLAR
	RMLAR
	EQVAR
	TMPAR
	ETXTAR
	EMSGAR
	PGMAR
	OMFAR
	IMSAR
NAREAS==.-ARPTBL

BVAR
MASTER:	BLOCK $ARSIZ	; Master message-ID list area.
MSGAR:	BLOCK $ARSIZ	; Message list area.
SCHDAR:	BLOCK $ARSIZ	; Scheduler list area.
QMLAR:	BLOCK $ARSIZ	; Queued Message list area.
RMLAR:	BLOCK $ARSIZ	; Reminders list area.
EQVAR:	BLOCK $ARSIZ	; Equivalence list area.
ETXTAR:	BLOCK $ARSIZ	; Error-message text collection area.
EMSGAR:	BLOCK $ARSIZ	; Error-message attrib collection area.
OMFAR:	BLOCK $ARSIZ	; Area used for forming mail-file output.
PGMAR:	BLOCK $ARSIZ	; Area for holding composition of SNDPGM error message.
TMPAR:	BLOCK $ARSIZ	; Temporary area for various things.

MASTRP:	0		; Holds LP to "current" LLN of MASTER list.

IMSAR:	BLOCK $ARSIZ	;area containing input from file
IMSTIM:	0		;creation date of file

RCPNUM:	0		;# of Rcpts seen.
RRCPS:	0		;# of real (not psuedo-name) recipients.

HFLUSN==10.		;(How many sites in the following losers list.)
HFLUSH:			;List of sites whose queued mail should be flushed.
REPEAT HFLUSN,-1	;Use zero to indicate the local host.

HDOWN:	250.		; Number of tries before host permanently down.
			; 500. = 1 week
EVAR

LITTER:	CONSTANTS	; Now dump out all literals/constants here

	VARCHK		; Now dump out all variables, and find
			; how big impure and pure really are.

	; Memory management definitions (now that we know how big pgm is!)

LCKPAG==<.+2000-1>/2000
SYSWPG==LCKPAG+1	; Page # to use for mapping into system (for NQMFWR)
FREEPG==SYSWPG+1

LSWLOC=:LCKPAG*2000	;loc where switch page starts
LSWREQ=:LSWLOC		;init request flag
LSWDON=:LSWLOC+1	;init done flag
LOCK1=: LSWLOC+<1*2>	;switch 1 (2 wd block)
LOCK2=: LSWLOC+<2*2>	;switch 2  "  "  "   (Note: LOCK2 is not used at present.)
NLCKSW==2		; # of lock switches.
LSHTIM=:LOCK2+3

END INIT

