TITLE SMTDCN - Multiforking mail listener for DECnet
	SUBTTL From SMTPSV by Chris Maio, Columbia University, 21 July 194
	SEARCH MACSYM,MONSYM
	.REQUIRE SYS:MACREL
	SALL			; suppress macro expansions
	.DIRECTIVE FLBLST	; sane listings for ASCIZ, etc.
	.TEXT "/NOINITIAL"	; suppress loading of JOBDAT
	.TEXT "SMTDCN/SAVE"	; save as SMTDCN.EXE

; Version components

SMTWHO==0			; who last edited SMTDCN (0=developers)
SMTVER==6			; SMTDCN's release version (matches monitor's)
SMTMIN==1			; SMTDCN's minor version
SMTEDT==^D9			; SMTDCN's edit version

;  This version of SMTPSV manages a set of MAISER forks, and uses
; DECnet JFNs.
;
;  The maximum number of simultaneous connections allowed is controlled
; by the setting of NFKS below.  The maximum number of forks to allow to
; exist after they have finished (in order to cut down on MAISER startup
; overhead) is specified with MAXIDL.

IFNDEF MAXIDL,MAXIDL==2		; maximum allowable idle forks
IFNDEF NFKS,NFKS==10		; maximum simultaneous connections
IFNDEF PDLLEN,PDLLEN==200	; PDL size

A=1				; AC definitions
B=2
C=3
D=4
FX=14				; fork table index of current fork
CX=16

; Fork variables
;
;  The current fork index (not a TOPS-20 fork handle) is always kept in
; FX, so indexing into the fork table is done implicitly by the following
; DEFSTRs.

DEFSTR FH,FKSTAT(FX),17,18	; TOPS-20 fork handle
DEFSTR FKRUN,FKSTAT(FX),18,1	; set if fork is currently running
DEFSTR FKJFN,FKSTAT(FX),35,9	; fork's network JFN
 
DEFINE NOINT <CALL .NOINT>	; disable PSI
DEFINE OKINT <CALL .OKINT>	; enable PSI
DEFINE TMOSET (INTVL,RETAD) <
	SETZM CLKCNT
	PUSH P,[PC%USR+RETAD]
	POP P,CLKLOC
	PUSH P,[-<INTVL/5>]
	POP P,CLKCNT
>;DEFINE TMOSET

DEFINE TMOCLR <
	SETZM CLKCNT
	SETZM CLKLOC
>;DEFINE TMOCLR
	SUBTTL Data area

PC1:	BLOCK 1			; level 1 interrupt PC
IN1ACS:	BLOCK 20		; level 1 AC save
PC2:	BLOCK 1			; level 2 interrupt PC
IN2ACS:	BLOCK 20		; level 2 AC save
CLKCNT:	BLOCK 1			; clock count
CLKLOC:	BLOCK 1			; clock location
NRUN:	BLOCK 1			; active fork count list
NFORKS:	BLOCK 1			; subfork count
NJFNS:	BLOCK 1			; connection count
FKSTAT:	BLOCK NFKS		; fork status
PDL:	BLOCK PDLLEN		; stack
	SUBTTL Pure data

CHNTAB:	PHASE 0			; interrupt channel table
TIMCHN:!1,,TIMINT		; timeout
	BLOCK .ICIFT-.
.ICIFT:!2,,FRKINT		; fork termination interrupts
	BLOCK ^D36-.
	DEPHASE
	
LEVTAB:	PC1			; level 1
	PC2			; level 2
	BLOCK 1			; level 3 unused
	SUBTTL Main program

; Entry vector

EVEC:	JRST SMTDCN		; START address
	JRST SMTDCN		; REENTER address
	<FLD SMTWHO,VI%WHO>!<FLD SMTVER,VI%MAJ>!<FLD SMTMIN,VI%MIN>!<FLD SMTEDT,VI%EDN>
EVECL==.-EVEC

SMTDCN:	RESET%
	MOVE P,[IOWD PDLLEN,PDL]
	MOVX A,.FHSLF		; enable all capabilities
	RPCAP%
	IORB C,B
	EPCAP%
	MOVE B,[LEVTAB,,CHNTAB]
	SIR%			; set up interrupt channels
	EIR%			; enable interrupts
	MOVX B,1B<TIMCHN>!1B<.ICIFT> ; channels to interrupt on
	AIC%			; activate the interrupt channels
	JSP CX,SETTIM		; start the timer
	SETZM FKSTAT		; clear the fork table
	MOVE A,[FKSTAT,,FKSTAT+1]
	BLT A,FKSTAT+NFKS-1
	DO.
	  MOVE A,NRUN		; get running fork count
	  CAIL A,NFKS		; all in use?
	   WAIT%		; yes, wait for one to complete
	  CALL LISTEN		; listen for a connection
	  LOOP.			; and go back for another one
	ENDDO.
	SUBTTL Interrupt routines

; Here to initialize the timer, called via JSP CX,SETTIM.  Note that A,B,C
; are clobbered!

SETTIM:	MOVE A,[.FHSLF,,.TIMEL]	; tick the timer
	MOVX B,^D5000		; every 5 seconds
	MOVX C,TIMCHN		; on TIMCHN channel
	TIMER%
	 ERJMP .+1
	JRST (CX)

;;;Here on timer interrupt
TIMINT:	MOVEM 17,IN1ACS+17	; save ACs
	MOVEI 17,IN1ACS
	BLT 17,IN1ACS+16
	JSP CX,SETTIM		; reinitialize the timer
	AOSE CLKCNT		; should time out now?
	IFSKP.
	  SKIPE A,CLKLOC	; get time-out routine
	   MOVEM A,PC1		; set it
	ENDIF.
	MOVSI 17,IN1ACS		; restore ACs
	BLT 17,17
	DEBRK%

; FRKINT is called on fork termination to scan the fork list to find
; any halted forks and close the corresponding connections.

FRKINT:	MOVEM 17,IN2ACS+17	; save ACs
	MOVEI 17,IN2ACS
	BLT 17,IN2ACS+16
	MOVE 17,IN2ACS+17
	MOVE A,PC2		; get interrupt pc location
	MOVE A,-1(A)		; get waiting instruction
	CAME A,[WAIT%]		; waiting for a free fork?
	IFSKP.
	  SETONE PC%USR,PC2	; yes, make JSYS return to user
	ENDIF.
	MOVSI FX,-NFKS		; loop through all forks
	DO.
	  IFQN. FKRUN		; only "running" forks are checked
	    LOAD A,FH		; get the fork handle
	    RFSTS%		; get its status
	     ERJMP STOP
	    HRRZS B		; flush flags from PC
	    LOAD D,RF%STS,A	; get the fork status code
	    CAIE D,.RFHLT	; halted?
	     CAIN D,.RFFPT	; or terminated?
	      SOSA NRUN		; yes, one less running fork
	  ANSKP.
	    SETZRO FKRUN	; say fork is no longer running
	    CAIE D,.RFHLT	; halted normally?
	    IFSKP.
	      MOVE A,NFORKS	; get the number of existing forks
	      SUB A,NRUN	; subtract balance of running forks
	      CAILE A,MAXIDL	; too many free forks?
	    ANSKP.
	    ELSE.
	      LOAD A,FH		; get the fork handle back
	      KFORK%		; zap it
	       ERJMP STOP
	      SOS NFORKS	; one less fork to worry about
	      SETZRO FH		; say the fork is gone
	    ENDIF.
	    LOAD A,FKJFN	; get the JFN
	    CALL CLSJFN		; close the connection
	    SETZRO FKJFN	; delete it from the table
	  ENDIF.
	  AOBJN FX,TOP.		; loop if more forks to examine
	ENDDO.
	MOVSI 17,IN2ACS
	BLT 17,17
	DEBRK%			; return from the interrupt
	 ERJMP STOP

; CLSJFN - close the DECnet connection
;
; Accepts:
;	A/ network JFN
; Returns:
;	+1 Always

CLSABT:	TXO A,CZ%ABT		; abort the connection
CLSJFN:	MOVE D,A		; get a copy of the JFN
	TMOSET (30,CLSABT)
	CLOSF%			; close it
	IFJER.
	  TMOCLR
	  MOVE A,D		; get the JFN back
	  RLJFN%		; if close failed, just release JFN
	   ERJMP .+1
	ENDIF.
	TMOCLR
	SOS NJFNS		; one less connection
	RET
	SUBTTL LISTEN - listen for a connection

; Listens for a connection on the DECnet SMTP socket and starts up a copy
; of MAISER.
;
; Returns:
;	+1 open failed
;	+2 open succeeded, SMTP fork started

LISTEN:	STKVAR <SRVJFN>		; temp ac for storing JFN
	DO.
	  MOVX A,GJ%SHT
;;	  HRROI B,[ASCIZ/SRV:125/] ; SMTP on object 125
	  HRROI B,[ASCIZ/SRV:TASK.MX-LISTENER/] ; SMTP name DEC uses...
	  GTJFN%		; get a JFN to listen on
	  IFJER.
	    MOVX A,^D<10*1000>	; failed, wait a bit
	    DISMS%
	    LOOP.		; and try again
	  ENDIF.
	  MOVEM A,SRVJFN	; copy the JFN to a safe register
	  MOVX B,<OF%RD!OF%WR!<FLD ^D7,OF%BSZ>>
	  OPENF%		; wait for a connection
	  IFJER.
	    MOVE A,SRVJFN	; get the JFN back
	    RLJFN%		; throw it away
	     ERJMP .+1
	    MOVX A,^D<10*1000>	; failed, wait a bit
	    DISMS%
	    LOOP.		; and try again
	  ENDIF.
	ENDDO.
	DO.
	  MOVX B,.MORLS		; read link status
	  MTOPR%
	  IFNJE.
	    JXN C,<MO%CON!MO%WCC>,ENDLP. ; connected?
	  ANDXN. C,MO%WFC	; no, waiting for connect?
	    MOVX A,^D1000	; yes, wait a second
	    DISMS%
	    MOVE A,SRVJFN	; and try again
	    LOOP.
	  ELSE.
	    MOVE A,SRVJFN	; get the JFN back
	    CLOSF%		; throw it away
	     ERJMP .+1
	    RET			; print error message and return
	  ENDIF.
	ENDDO.
	AOS NJFNS		; bump connection count
	CALL GETFRK		; find a free fork table entry
	IFNSK.
	  MOVE A,SRVJFN
	  HRROI B,[ASCIZ/421 Insufficient system resources; please report this
/]
	  SETZ C,
	  SOUTR%
	   ERJMP .+1
	  CALLRET CLSJFN	; close the connection and return
	ENDIF.
	MOVE A,SRVJFN		; get the JFN back
	STOR A,FKJFN		; save the JFN
	LOAD A,FH		; get the fork handle in a
	LOAD B,FKJFN		; get the JFN
	HRLS B			; B/ input JFN,,output JFN
	SPJFN%			; set the primary JFNs
	 ERJMP STOP
	NOINT			; defer interrupts
	LOAD A,FH
	SETZ B,			; start the fork at normal entry
	SFRKV%			; start it
	 ERJMP STOP
	SETONE FKRUN		; say the fork has been started
	AOS NRUN		; bump running the running fork count
	OKINT			; allow interrupts again
	RET			; return to get another fork

	ENDSV.
	SUBTTL GETFRK - allocate a fork

;  Scan the fork table looking for an idle fork.  If one is found, its
; index is returned, otherwise a new fork is created unless the table is
; full.
;
; Returns:
;	+1 no more forks
;	+2 success, fork index in FX

GETFRK:	MOVSI FX,-NFKS		; loop through all forks
	DO.
	  IFQE. FKJFN		; fork in use?
	    JN FH,,RSKP		; no, if fork exists we can use it
	  AOBJN FX,TOP.
	  ENDIF.
	ENDDO.

; No idle fork exists, so create one if table can hold it

	MOVSI FX,-NFKS
	DO.
	  IFQE. FKJFN		; fork in use?
	    HRRZS FX		; no, isolate the fork index
	    JN FH,,RSKP		; if exists, idle fork appeared, use it
	    MOVX A,CR%CAP	; else make one with our caps
	    CFORK%
	     ERJMP STOP
	    AOS NFORKS		; bump the fork count
	    STOR A,FH		; save the handle
	    TXZ A,.FHSLF
	    MOVX A,GJ%SHT!GJ%OLD
	    HRROI B,[ASCIZ/SYSTEM:MAISER.EXE/]
	    GTJFN%		; get a handle on the file
	     ERJMP STOP
	    LOAD B,FH		; get the fork handle
	    HRL A,B		; A/ fork,,JFN
	    GET%		; load in the file
	     ERJMP STOP
	    RETSKP		; return with FX set up
	  ENDIF.
	  AOBJN FX,TOP.		; loop if more to try
	ENDDO.
	RET			; otherwise fail
	SUBTTL OKINT and NOINT - turn interrupts on and off

.OKINT:	SAVEAC <A>
	MOVX A,.FHSLF
	EIR%			; enable interrupts
	RET

.NOINT:	SAVEAC <A>
	MOVX A,.FHSLF
	DIR%			; disable interrupts
	RET
	SUBTTL Other randomness

STOP:	HRROI A,[ASCIZ/SMTDCN: /]
	ESOUT%
	MOVX A,.PRIOU
	HRLOI B,.FHSLF		; dumb ERSTR%
	SETZ C,
	ERSTR%
	 NOP			; undefined error number
	 NOP			; can't happen
	MOVX A,^D5000		; sleep for 5 seconds
	DISMS%
	JRST SMTDCN		; restart

; Literals

...VAR:!VAR			; generate variables (there shouldn't be any)
IFN .-...VAR,<.FATAL Variables illegal in this program>
...LIT:	XLIST			; save trees during LIT
	LIT			; generate literals
	LIST

	END EVECL,,EVEC		; The End