!TITLE Director Error Messages
! When a Director procedure is called, a record is kept of the procedure
! called and the value of the result returned.  Subsequently, a process
! may use the procedures described here to interpret the result value and
! generate a meaningful error message.
!STOP
!<Error Message Texts
CONSTINTEGER  TOPMESSAGE = 115
CONSTSTRING (39)ARRAY  MESSAGE(0:TOPMESSAGE) =  C 
{..0}  "Successful",
{..1}  "File too big or bad site",
{..2}  "Sconnect - bad parameters",
{..3}  "Alloc Dseg - no free segments",
{..4}  "Denable Terminal Stream fails",
{..5}  "File not available",
{..6}  "Use count on fsys negative",
{..7}  "No space for index",
{..8}  "Bad parameter",
{..9}  "Must allow 15 mins",
{.10}  "File system full",
{.11}  "Bad USER name",
{.12}  "Nkb must be 2 or 4 <= Nkb <= 32",
{.13}  "Name-number table (NNT) full",
{.14}  "User already has index on fsys",
{.15}  "No free file descriptors",
{.16}  "File already exists",
{.17}  "No free permission descriptors",
{.18}  "Invalid filename &",
{.19}  "Password truncated to 11 characters",
{.20}  "File & on offer",
{.21}  "File being executed",
{.22}  "Bits already clear for some pages",
{.23}  "Fsys not available",
{.24}  "error 24",
{.25}  "Disk transfer failed",
{.26}  "error 26",
{.27}  "File too big",
{.28}  "CBT freelist empty",
{.29}  "No free CONLIST entries",
{.30}  "error 30",
{.31}  "Ambiguous",
{.32}  "File & does not exist or no access",
{.33}  "Conflicting use of file & in another VM",
{.34}  "File is already connected",
{.35}  "Segment in use or GAP too small",
{.36}  "(De-)Nominate fails",
{.37}  "User & not known",
{.38}  "Already claimed",
{.39}  "File & is not connected",
{.40}  "File is connected",
{.41}  "Single file limit exceeded",
{.42}  "File is connected in another VM",
{.43}  "No free section descriptors",
{.44}  "DAP timed out",
{.45}  "User's parameters not accessible",
{.46}  "Bad permission",
{.47}  "Not enough stack",
{.48}  "TELL message rejected",
{.49}  "Permission list full (max 16)",
{.50}  "User(group) not in list",
{.51}  "OWNP is zero or no-destroy set",
{.52}  "File is connected in write mode",
{.53}  "No interrupt data",
{.54}  "No outward call set up",
{.55}  "System Call Table full",
{.56}  "Area crosses segment boundary",
{.57}  "End of session",
{.58}  "Bad parameter or RCB not accessible",
{.59}  "Archive index checksum failure",
{.60}  "Donate - user has no funds",
{.61}  "Process not available",
{.62}  "Max in DAP Claim Queue",
{.63}  "DAP not claimed at start",
{.64}  "DAP claim de-queued",
{.65}  "Bad Date/Time",
{.66}  "Close sequence cancelled",
{.67}  "Not claimed",
{.68}  "Failed to lock-down area",
{.69}  "CCK already done",
{.70}  "List is full",
{.71}  "No time left",
{.72}  "DAP not started",
{.73}  "DAP not available",
{.74}  "Not enough contiguous DAP blocks",
{.75}  "DAP closing",
{.76}  "User already has DAP",
{.77}  "Failed to claim archive semaphore",
{.78}  "Disconnect - cannot find file",
{.79}  "Area not locked",
{.80}  "Not in list",
{.81}  "LP is already MAIN",
{.82}  "Maximum areas already locked",
{.83}  "Total file space limit exceeded",
{.84}  "Restricted connect",
{.85}  "Fsys closing",
{.86}  "Failed to create and connect #msg",
{.87}  "Index corrupt",
{.88}  "Re-map fails",
{.89}  "Invalid file",
{.90}  "Maximum already allocated",
{.91}  "Block still active",
{.92}  "Interactive use not allowed",
{.93}  "User does not have privilege",
{.94}  "Failed to claim semaphore",
{.95}  "Cannot allocate main LP",
{.96}  "Password failure",
{.97}  "Bad page reported in NINDA",
{.98}  "Resources Scarce",
{.99}  "Obsolete index format encountered",
{100}  "Logged on",
{101}  "System Full",
{102}  "Logon Fails",
{103}  "Invalid Pass",
{104}  "Process Running",
{105}  "Invalid Name",
{106}  "Workfile Fail",
{107}  "No User Service",
{108}  "No Batch File",
{109}  "No Funds",
{110}  "User not found",
{111}  "FE closing",
{112}  "Node closing",
{113}  "TCP closing",
{114}  "Connected",
{115}  "???"
!>
OWNSTRING (6) PASSU,PASSW


CONSTINTEGER  YES = 1
CONSTINTEGER  ABORT = 5
CONSTINTEGER  AFTER = 2
CONSTINTEGER  ASYNC TYPE = 3
CONSTINTEGER  BATCH = 2; ! reason for STARTP
CONSTINTEGER  CLODACT = 8
CONSTINTEGER  CONNECT STREAM = X'370001'
CONSTINTEGER  DATKEY = 4; ! SYSAD
CONSTINTEGER  DEFAULT BMAX = 1
CONSTINTEGER  DEFAULT IMAX = 1
CONSTINTEGER  DEFAULT TMAX = 1
CONSTINTEGER  DIRDACT = 5; ! special director async messages
CONSTINTEGER  DISABLE STREAM = X'370004'
CONSTINTEGER  DISCONNECT STREAM = X'370005'
CONSTINTEGER  DT = 1; ! DATE and TIME required in PRINTSTRING
CONSTINTEGER  EEP8 = X'B000055'; ! EEP = 11, SET EEP, ZERO AND TEMPFI
CONSTINTEGER  ENABLE STREAM = X'370002';!START TRANSFER ON COMMUNICATIONS STREAM
CONSTINTEGER  ENDLIST = 255
CONSTINTEGER  EPAGE SIZE = 4
CONSTINTEGER  EVERY = 1
CONSTINTEGER  X29 ACTIVITY ADDON = 12

CONSTINTEGER  FEP INPUT CONNECT = 52
CONSTINTEGER  FEP INPUT CONNECT REPLY = 53
CONSTINTEGER  FEP INPUT DISABLE = 57
CONSTINTEGER  FEP INPUT DISABLE REPLY = 58
CONSTINTEGER  FEP INPUT DISCONNECT REPLY = 60
CONSTINTEGER  FEP INPUT ENABLE REPLY = 55
CONSTINTEGER  FEP INPUT MESS = 50
CONSTINTEGER  FEP OUTPUT CONNECT REPLY = 54
CONSTINTEGER  FEP OUTPUT DISABLE REPLY = 59
CONSTINTEGER  FEP OUTPUT DISCONNECT REPLY = 61
CONSTINTEGER  FEP OUTPUT ENABLE REPLY = 56
CONSTINTEGER  FEP OUTPUT REPLY MESS = 51
CONSTINTEGER  FORK = 5
CONSTINTEGER  INTDACT = 1; ! INT: messages from supervisor
CONSTINTEGER  INTER = 0; ! reason for STARTP
CONSTINTEGER  LEAVE = 8
CONSTINTEGER  LOG = 2; ! route PRINTSTRING to MAINLOG
CONSTINTEGER  NEWSTART = 4
CONSTINTEGER  NO = 0
CONSTINTEGER  OK = 0;                   !GENERAL SUCCESSFUL REPLY FLAG
CONSTINTEGER  OPERC = 1; ! reason for STARTP
CONSTINTEGER  OPTYPE = X'8000000'
CONSTINTEGER  PON AND CONTINUE = 6
CONSTINTEGER  REC SEP = 30
CONSTINTEGER  SYNC1 TYPE = 1
CONSTINTEGER  TOPEXEC = 3
CONSTINTEGER  TXTDACT = 3; ! text messages in file, P1 = start/finish
CONSTINTEGER  WRTOF = 4; ! route PRINTSTRING to private log file


CONSTINTEGERARRAY  WARN AT(0:3) = 16, 6, 3, 2; ! MINS TO GO

CONSTSTRING (1) SNL="
"

CONSTSTRING (6)ARRAY  EXEC(0:TOPEXEC) = "VOLUMS","SPOOLR","MAILER","FTRANS"

! see below owns %CONSTRECORD(COMF)%NAME COM = X'80000000' + 48 << 18
CONSTSTRINGNAME  TIME = X'80C0004B'



! COMMANDS TO DIRECT
CONSTINTEGER  TOPM = 93

! In the table below,
!     the first two digits give the number of the command in XOPER

!     the next three digits describe the (first) three parameters
!        0  none
!        1  string(6)
!        2  numeric
!        3  string(6.11)
!        4  string(4)
!        5  string(1)
!        6  string(6) or numeric
!        7  string 0 < length < 32
!        8  no checking done

!     the sixth digit
!        0  must have the specified number of parameters
!        1  may have fewer


!-------------------------------------------------!
! The following ACTivities are currently SPARE:   !
!                                                 !
!-------------------------------------------------!

CONSTSTRING (25)ARRAY  M(1:TOPM) = C 
"30-1220-ACR",
"40-2001-AUTOFILE",
"79-7000-AUTOSLOAD",
"13-2220-BADFSYSCYLTRK",
"43-2200-BADFSYSPAGE",
"18-1201-BASEF",
"50-2001-BROADCAST",
"07-2000-CCK",
"14-2000-CCKDONE",
"92-2000-CHECKFSYS",
"42-2000-CLEARBADPAGESLIST",
"10-2000-CLEARFSYS",
"26-2001-CLOSE",
"12-0000-CLOSEDOWN",
"70-2201-CLOSEFE",
"70-2201-CLOSEFEP",
"61-2201-CLOSEFSYS",
"80-2201-CLOSENODE",
"90-7201-CLOSEPAD",
"82-7201-CLOSETCP",
"91-2001-CLOSETIME",
"76-1000-CLOSETO",
"45-2001-CLOSEUSERS",
"72-0000-CLOSE?",
"60-2000-CONNECTFE",
"15-0000-CREATE",
"71-8801-DAP",
"89-0000-DAY",
"11-2200-DDUMP",
"55-8001-DELIVER",
"02-1200-DELUSER",
"28-0000-DESTS",
"39-1220-DIRMON",
"67-2001-DIRPRINT",
"57-2000-DISCONNECTFE",
"74-0000-EMPTYDVM",
"03-2221-ERTE",
"63-0000-FAIL",
"77-1000-FE",
"73-2001-FEUSECOUNT",
"06-1201-FSYS",
"69-2200-FSYSBITNO",
"68-2220-FSYSCYLTRK",
"44-2200-GOODFSYSPAGE",
"48-1500-INT:",
"29-1201-KILL",
"87-1220-LS",
"54-0000-LOGSPACE",
"32-0000-MAINLP",
"47-1201-MSG",
"16-1221-NEWSTART",
"01-1220-NEWUSER",
"51-1201-NNT",
"05-0000-OBEYFILE",
"78-2000-OPENFE",
"78-2000-OPENFEP",
"46-2000-OPENFSYS",
"81-2000-OPENNODE",
"93-7000-OPENPAD",
"83-7000-OPENTCP",
"52-1000-OPENTO",
"46-2001-OPENUSERS",
"38-1400-PASS",
"17-0000-PASSOFF",
"65-2001-PREEMPTAT",
"22-0000-PRG",
"25-2001-PRINT",
"04-3201-PRM",
"59-0000-PROMPTOFF",
"58-0000-PROMPTON",
"21-1121-RENI",
"35-2200-REP",
"20-3201-S",
"64-2001-SCARCITY",
"33-1201-SENDMSG",
"66-2001-SESSIONLENGTH",
"37-1201-SETBASEF",
"49-0000-SETMSG",
"75-1220-SIGMON",
"34-1200-SINT:",
"88-0000-SITE",
"08-0000-SNOS",
"31-1221-START",
"27-0000-STOP",
"41-1221-TESTSTART",
"36-2000-TEXT",
"24-0000-TRANSFER",
"62-2001-USECOUNT",
"23-0000-UNPRG",
"09-2000-USERNAMES",
"56-6201-USERS",
"19-0000-VSN",
"53-1201-XNNT"
!
!-----------------------------------------------------------------------
!
EXTERNALSTRINGFNSPEC  ITOS(INTEGER  I)
!
!-----------------------------------------------------------------------
!
EXTERNALSTRINGFN  DERRS(INTEGER  N)
STRING (63) W, M, A, B, C
      W = ITOS(N)
      N = TOPMESSAGE UNLESS  0 <= N < TOPMESSAGE
      M = MESSAGE(N); ! remove curlies
      M = A . C WHILE  M -> A . ("{") . B . ("}") . C
      RESULT  = " " . W . " " . M
END ; ! DERRS
!
!-----------------------------------------------------------------------
!
!<DFLAG
externalintegerfn  DFLAG(integer  FLAG, stringname  TXT)
!
! This procedure returns the text string associated with FLAG as
! described in the list above.  The text returned is of the form:
!      sp flag sp error-message
!>
      TXT = ITOS(FLAG)
      FLAG = TOPMESSAGE UNLESS  0 <= FLAG < TOPMESSAGE
      TXT = " " . TXT . " " . MESSAGE(FLAG)
      RESULT  = 0
END ; ! DFLAG
!
!-----------------------------------------------------------------------
!
      EXTERNALROUTINESPEC  C 
WRSNT(STRING (255)S, INTEGER  N, T)
      EXTERNALROUTINESPEC  C 
WRS(STRING (255)S)
EXTRINSICINTEGER  DIRFLAG
EXTRINSICINTEGER  DIRFN
!
!-----------------------------------------------------------------------
!
EXTERNALROUTINE  DREPORT(STRING (63)TEMPLATE)
INTEGER  LNBHERE, BASE, J, T, W, W1, TO, GLA, LNB
STRING (255)S
      RETURN  IF  TEMPLATE = "NIL"; ! for procs like DSFI
!
      TO = ADDR(S)
      *STLN_LNBHERE
      LNB = INTEGER(INTEGER(LNBHERE))
      BASE = LNB + 16
      GLA = INTEGER(LNB + 16)
      T = INTEGER(LNB + 12) << 8 >> 8 + INTEGER(GLA + 12) + 12
!
      PRINTSTRING(STRING(T)); ! procedure name
!
      J = 0
      WHILE  J < LENGTH(TEMPLATE) CYCLE 
         BASE = BASE + 4
         W = INTEGER(BASE)
         IF  J = 0 THEN  SPACE ELSE  PRINTSTRING(",")
         J = J + 1
         T = CHARNO(TEMPLATE, J)
!
         IF  T = 'I' START 
            WRITE(W, 1)
         FINISH 
!
         IF  T = 'X' START 
            WRSNT("", W, 6); ! hex
         FINISH 
!
         IF  T = 'S' START 
            BASE = BASE + 4
            W1 = INTEGER(BASE)
            *LDTB_W
            *LDA_W1
            *CYD_0
            *LDA_TO
            *MV_L =DR 
            PRINTSTRING(S)
         FINISH 
!
         IF  T = 'J' START ; ! integername
            BASE = BASE + 4
            W = INTEGER(INTEGER(BASE))
            WRITE(W, 1)
         FINISH 
      REPEAT 
!
      IF  DIRFLAG = 0 C 
      THEN  WRS(" OK") C 
      ELSE  WRS(DERRS(DIRFLAG))
END ; ! DREPORT
!
!-----------------------------------------------------------------------
!
OWNINTEGER  INITIAL DELAY
EXTRINSICINTEGER  MONITORAD
EXTRINSICINTEGER  BLKSI
EXTRINSICINTEGER  DDVSN; ! FORMAT IS FSYS<<18 ! DIRVSN
EXTRINSICINTEGER  DIRLOGAD
EXTRINSICINTEGER  DIROUTP0
EXTRINSICINTEGER  FILE1AD
EXTRINSICINTEGER  LOG ACTION
EXTRINSICINTEGER  PROC1 LNB
EXTRINSICINTEGER  PROCESS
EXTRINSICINTEGER  SUPLVN S START
EXTRINSICINTEGER  WORKBASE
EXTRINSICSTRING (6)PROCUSER
EXTRINSICSTRING (15)VSN
EXTRINSICBYTEINTEGERARRAY  FSYS USE COUNT(0:99)

      RECORDFORMAT  C 
FHDRF(INTEGER  NEXTFREEBYTE,TXTRELST,MAXBYTES,THREE, C 
      SEMA,DATE,NEXTCYCLIC,READ TO)
      RECORDFORMAT  C 
FINFOF(INTEGER  NKB, RUP, EEP, APF, USE, ARCH, FSYS, CONSEG,
      CCT, CODES, BYTEINTEGER  SP1, DAYNO, SP2, CODES2, INTEGER  SSBYTE,
      STRING (6)OFFER)
      RECORDFORMAT  C 
PROPF(INTEGER  TRACKS, CYLS, PPERTRK, BLKSIZE C 
      , TOTPAGES, RQBLKSIZE, LBLKSIZE, ALISTSIZE, KEYLEN,  C 
      SECTINDX)
INCLUDE  "PD22S_C03FORMATS"
CONSTBYTEINTEGERARRAY  IN CONTROL STREAM(ITP:X29) = 2, 8
CONSTBYTEINTEGERARRAY  OUT CONTROL STREAM(ITP:X29)= 3, 9


      EXTERNALROUTINESPEC  C 
ADJUST DLVN BIT(INTEGER  FSYS, SET)
      EXTERNALINTEGERFNSPEC  C 
ASYNC MSG(STRING (6) USER,INTEGER  INVOC,DACT,P1,P3)
      EXTERNALINTEGERFNSPEC  C 
AUTO COMM(STRING (255) S,INTEGER  ACT)
      EXTERNALINTEGERFNSPEC  C 
AV(INTEGER  FSYS, TYPE)
      EXTERNALINTEGERFNSPEC  C 
BAD PAGE(INTEGER  TYPE, FSYS, BITNO)
      EXTERNALINTEGERFNSPEC  C 
CCK(INTEGER  FSYS, CHECK, INTEGERNAME  PERCENT)
      EXTERNALROUTINESPEC  C 
CLEAR FSYS(INTEGER  FSYS)
      EXTERNALROUTINESPEC  C 
COPY TO FILE(INTEGER  FA1,FA2)
      EXTERNALINTEGERFNSPEC  C 
CREATE AND CONNECT(STRING (31)FULL,
      INTEGER  FSYS, NKB, ALLOC, MODE, APF, C 
      INTEGERNAME  SEG, GAP)
      EXTERNALROUTINESPEC  C 
CYCINIT(INTEGER  FAD, MAXBYTES)
      EXTERNALROUTINESPEC  C 
DCHAIN(INTEGER  SEG, DESTROY)
      EXTERNALINTEGERFNSPEC  C 
DCONNECTI(STRING (31)FULL, INTEGER  FSYS, MODE, APF,
      INTEGERNAME  SEG,GAP)
      EXTERNALINTEGERFNSPEC  C 
DCREATEF(STRING (31)FULL, INTEGER  FSYS, NKB, ALLOC, LEAVE,
      INTEGERNAME  DA)
      EXTERNALINTEGERFNSPEC  C 
DDAYNUMBER
      EXTERNALINTEGERFNSPEC  C 
DDELUSER(STRING (6)USER, INTEGER  FSYS)
      EXTERNALINTEGERFNSPEC  C 
DDESTROYF(STRING (31)FULL, INTEGER  FSYS, DEALLOC)
      EXTERNALINTEGERFNSPEC  C 
DDISCONNECTI(STRING (31)FULL, INTEGER  FSYS, LO)
      EXTERNALINTEGERFNSPEC  C 
DDTENTRY(INTEGERNAME  ENTAD, INTEGER  FSYS)
      EXTERNALINTEGERFNSPEC  C 
DDUMPINDNO(INTEGER  FSYS,INDNO)
      EXTERNALROUTINESPEC  C 
DERR2(STRING (31) S,INTEGER  FN, ERR)
      EXTERNALINTEGERFNSPEC  C 
DFINFO(STRING (31)FILE INDEX, FILE, INTEGER  FSYS, ADR)
      EXTERNALINTEGERFNSPEC  C 
DGETDA(STRING (31)USER, FILE, INTEGER  FSYS, ADR)
      EXTERNALINTEGERFNSPEC  C 
DISCSEGCONNECT(INTEGER  FSYS, SITE, SEG, APF, PGS, FLAGS)
      EXTERNALINTEGERFNSPEC  C 
DISC USE COUNT(INTEGER  FSYS,INCREMENT)
      EXTERNALINTEGERFNSPEC  C 
DNEWUSER(STRING (6)USER, INTEGER  FSYS, NKB)
      EXTERNALROUTINESPEC  C 
DOPERR(STRING (15)TXT,INTEGER  FN,RES)
      EXTERNALROUTINESPEC  C 
DOPER2(STRING (255)S)
      EXTERNALROUTINESPEC  C 
DOUTI(RECORD (PARMF)NAME  P)
      EXTERNALROUTINESPEC  C 
DOUT11I(RECORD (PARMF)NAME  P)
      EXTERNALINTEGERFNSPEC  C 
DPERMISSIONI(STRING (18)OWNER, USER, STRING (8)DATE, STRING (11)FILE,
      INTEGER  FSYS, TYPE, ADRPRM)
      EXTERNALROUTINESPEC  C 
DPOFFI(RECORD (PARMF)NAME  P)
      EXTERNALROUTINESPEC  C 
DPONI(RECORD (PARMF)NAME  P)
      EXTERNALINTEGERFNSPEC  C 
DPON3I(STRING (6) USER,RECORD (PARMF)NAME  P, C       
      INTEGER  INVOC,MSGTYPE,OUTNO)
      EXTERNALINTEGERFNSPEC  C 
DPRG(STRING (6)USER, STRING (11)FILE, INTEGER  FSYS, C 
      STRING (6)LABEL, INTEGER  SITE)
      EXTERNALINTEGERFNSPEC  C 
DRENAME INDEX(STRING (18)OLDNAME, NEWNAME, INTEGER  FSYS)
      EXTERNALINTEGERFNSPEC  C 
DSFI(STRING (31)INDEX, INTEGER  FSYS, TYPE, SET, ADR)
      EXTERNALROUTINESPEC  C 
DSTOP(INTEGER  REASON)
      EXTERNALINTEGERFNSPEC  C 
DTRANSFER(STRING (31)USER1, USER2, FILE1, FILE2,
      INTEGER  FSYS1, FSYS2, TYPE)
      EXTERNALINTEGERFNSPEC  C 
DUNPRG(STRING (31)USER, FILE, INTEGER  FSYS,
      STRING (6)LABEL, INTEGER  SITE)
      EXTERNALROUTINESPEC  C 
EMPTY DVM
      EXTERNALINTEGERFNSPEC  C 
ENCRYPT(INTEGER  MODE, STRING (63)PASS, LONGINTEGERNAME  E,
      INTEGERNAME  K, DT)
      EXTERNALINTEGERFNSPEC  C 
FBASE2(INTEGER  FSYS, ADR)
      EXTERNALROUTINESPEC  C 
FILE FOR HOTTOP(INTEGER  INVOC)
      EXTERNALINTEGERFNSPEC  C 
FIND NNT ENTRY(STRING (31)INDEX, INTEGERNAME  FSYS, NNAD,INTEGER  TYPE)
      EXTERNALSTRINGFNSPEC  C 
FROMSTRING(STRING (255)S, INTEGER  I, J)
      EXTERNALINTEGERFNSPEC  C 
FUNDS(INTEGERNAME  GPINDAD, INTEGER  INDAD)
      EXTERNALROUTINESPEC  C 
GET AV FSYS2(INTEGER  TYPE, INTEGERNAME  N, INTEGERARRAYNAME  A)
      EXTERNALINTEGERFNSPEC  C 
GET USNAMES(INTEGERNAME  N, INTEGER  ADDR, FSYS)
      EXTERNALINTEGERFNSPEC  C 
HINDA(STRING (6)UNAME, INTEGERNAME  FSYS, INDAD, INTEGER  TYPE)
      EXTERNALSTRINGFNSPEC  C 
HTOS(INTEGER  I, PL)
      EXTERNALINTEGERFNSPEC  C 
LISTMOD(STRING (6) S1,INTEGER  N1,N2)
      EXTERNALINTEGERFNSPEC  C 
LOGLINK(RECORD (PARMF)NAME  P,INTEGER  ACT)
      EXTERNALROUTINESPEC  C 
MOVE(INTEGER  LENGTH, FROM, TO)
      EXTERNALINTEGERFNSPEC  C 
NEWPAGE CHAR(RECORD (PARMF)NAME  P)
      EXTERNALROUTINESPEC  C 
OUTPUT MESSAGE TO FEP(RECORD (FEPF)ARRAYNAME  FEPS,
      INTEGER  FE, TYPE, ADR, LEN, STREAM, PROTOCOL)
      EXTERNALINTEGERFNSPEC  C 
PACKDT
      EXTERNALROUTINESPEC  C 
PLACE(STRING (39)TEXT, INTEGER  SCREEN,LINE,COL,ACTION)
      EXTERNALROUTINESPEC  C 
PREC(STRING (255)S, RECORD (PARMF)NAME  P, INTEGER  N)
      EXTERNALROUTINESPEC  C 
PRHEX(INTEGER  I)
      EXTERNALINTEGERFNSPEC  C 
SET CLOSING BIT(INTEGER  FSYS)
      EXTERNALINTEGERFNSPEC  C 
STRING TO FILE(INTEGER  LEN,ADR,FAD)
      EXTERNALROUTINESPEC  C 
SYMBOLS(INTEGER  N, SYMBOL)
      EXTERNALINTEGERFNSPEC  C 
SYSAD(INTEGER  KEY, FSYS)
      EXTERNALINTEGERFNSPEC  C 
TXTMESS(STRING (6) USER,RECORD (PARMF)NAME  RP, C 
      INTEGER  SYNC,INVOC,TXTLEN,TXTAD,FSYS,SACT)
      EXTERNALROUTINESPEC  C 
UCTRANSLATE(INTEGER  ADR, LEN)
      EXTERNALINTEGERFNSPEC  C 
VAL(INTEGER  ADR, LEN, RW, PSR)
      EXTERNALROUTINESPEC  C 
WRSS(STRING (255)S1, S2)
      EXTERNALROUTINESPEC  C 
WRSN(STRING (255)S, INTEGER  N)
      EXTERNALROUTINESPEC  C 
WRS3N(STRING (255)S1, S2, S3, INTEGER  N)


OWNRECORD (LOGF HDF)NAME  LOGH
OWNRECORD (FEPF)ARRAYNAME  FEPS


OWNINTEGER  BATCH STREAMS = 0
OWNINTEGER  BATCH PROCESSES = 0
OWNRECORD (PROCDATF)ARRAYFORMAT  PROCLF(0:255)
OWNRECORD (PROCDATF)ARRAYNAME  PROCLIST
OWNBYTEINTEGER  FREEHDI=0,LIVEHDI=ENDLIST,BACKHDI=ENDLIST
OWNBYTEINTEGERNAME  FREEHD,LIVEHD,BACKHD
OWNINTEGER  FCHECK PROCS=0

!-----------------------------------------------------------------------------

! The group of declarations which follow was introduced for
!       D/CLOSE FE,  D/CLOSE FSYS  etc.
OWNINTEGER  CLO FES=0, CLO FSYS=0, CLO NODES=0
OWNINTEGER  PENDG CLO FES=0, PENDG CLO FSYS=0, PENDG CLO NODES=0
OWNINTEGER  FES CLOSED=0, NODES CLOSED=0

 { The CLO FES etc variables are set by the D/CLOSE FE etc commands.         }
 { The contents are moved to the CLO FES ETC variables at 7 mins before the }
 { stated partial close time. In addition, at 2 mins to partial close time,  }
 { CLO FES, CLO NODES, CLO TCPS are moved to FES CLOSED, NODES CLOSED, TCPS CLOSED,these being   }
 { inspected by the CHECKSTART function to determine whether logons should   }
 { be rejected.                                                              }

OWNINTEGER  NEW CLOSE TIME=-1,SUPPRESS EXEC STOP=0
OWNINTEGERARRAY  PENDG FCLOSING(0:3)=0(4)
OWNINTEGERARRAY  FCLOSING(0:3)=0(4)

OWNBYTEINTEGERARRAY  FE USECTI(0:TOP FE NO)
OWNBYTEINTEGERARRAYNAME  FE USECOUNT

CONSTINTEGER  OPENG=1, CLOSG=2, TCPPENDG=4, TCPINITG=8, TCPCLOSED=16

!-----------------------------------------------------------------------------

OWNINTEGER  FES FOUNDI=0
OWNINTEGERNAME  FES FOUND

OWNINTEGER  DEFAULT SESSLEN=0
OWNINTEGER  STOP LOGFILE=0
OWNINTEGERARRAY  PR SRCE(0:10)
OWNINTEGERARRAY  OPSTAT(0:3)=0(3),X'08000000'
!  100 bits here for FSYS 0 -99
!        0 = closed
!        1 = open
!  101st bit initially set, and cleared by a D/CLOSE USERS (used to prevent
!  service being opened automatically if D/CLOSE USERS given in time.



!-----------------------------------------------------------------------

!<DERROR
externalintegerfn  DERROR(stringname  TXT)
!
! This procedure returns a string which describes the most recent call
! on a Director procedure.  The text has the form:
!      sp procedure-name sp flag sp error-message
! It is envisaged that sections of code could be written in the form:
!        FLAG = DCONNECT(...
!        -> FAIL %unless FLAG = 0 %or FLAG = 34
!        ...
!
!
! FAIL:
!        J = DERROR(TXT)
!        PRINTSTRING(TXT)
!        NEWLINE
!        %end
!>
      TXT = " " . STRING(DIRFN) . DERRS(DIRFLAG)
      RESULT  = 0
END ; ! DERROR
!
!-----------------------------------------------------------------------
!
INTEGERFN  DIRECT SYNC1 DEST
      RESULT =(COM_SYNC1 DEST + PROCESS)<<16
END ; ! DIRECT SYNC1 DEST

!-----------------------------------------------------------------------

EXTERNALINTEGERFN  STOI2(STRING (255) S, INTEGERNAME  I2)
STRING  (63) P
INTEGER  TOTAL, SIGN, AD, I, J, HEX
!MON MON(1) = MON(1) + 1
   HEX = 0;  TOTAL = 0;  SIGN = 1
   AD = ADDR(P)
A: IF  S -> P.(" ").S AND  P="" THEN  -> A;         !CHOP LEADING SPACES
   IF  S -> P.("-").S AND  P="" THEN  SIGN = -1
   IF  S -> P.("X").S AND  P="" THEN  HEX = 1 AND  -> A
   P = S
   UNLESS  S -> P.(" ").S THEN  S = ""
   I = 1
   WHILE  I <= BYTEINTEGER(AD) CYCLE 
      J = BYTE INTEGER(I+AD)
      -> FAULT UNLESS  '0' <= J <= '9' OR  (HEX # 0 C 
         AND  'A' <= J <= 'F')
      IF  HEX = 0 THEN  TOTAL = 10*TOTAL C 
         ELSE  TOTAL = TOTAL<<4+9*J>>6
      TOTAL = TOTAL+J&15;  I = I+1
   REPEAT 
   IF  HEX # 0 AND  I > 9 THEN  -> FAULT
   IF  I > 1 THEN  I2 = SIGN*TOTAL AND  RESULT  = 0
FAULT:
   I2 = 0
   RESULT  = 1
END ;                            ! STOI2

!-----------------------------------------------------------------------

EXTERNALINTEGERFN  STOI(STRING (255)S)
INTEGER  J, I2

      J = STOI2(S, I2)
      RESULT  = I2 IF  J = 0
      RESULT  = X'80308030'
END ; ! STOI

!-----------------------------------------------------------------------

ROUTINE  OPER(INTEGER  SRCE,STRING (255)S)
RECORD (PARMF) P
INTEGER  L
      WRSS("OPER: ", S)
      RETURN  IF  SRCE = 0
      P = 0
      P_DEST = SRCE

      CYCLE 
         P_S <- S
         DPONI(P)
         L = LENGTH(S) - 23
         RETURN  UNLESS  L > 0
         LENGTH(S) = L
         MOVE(L, ADDR(S)+24, ADDR(S)+1)
      REPEAT 
END ; ! THIS LOCAL VERSION OF OPER WHICH SENDS REPLIES BACK TO CALLER

!-------------------------------------------------------------------------------

INTEGERFN  CYL TRK CONVERT(INTEGERNAME  PPERTRACK,  C 
   CYL,TRK,PG,INTEGER  WHICH,FSYS)
! Converts an FSYS-CYL-TRK to a bitnumber (PG), for WHICH=0, or
! an FSYS-BITNO (PG) to a CYL-TRK-PG (WHICH=1).
! Result zero if OK, 23 if disc not found in disc table
INTEGER  LCYL,LTRKS,TKSPERCYL, ENTAD
INTEGER  J
RECORD (DDTF)NAME  DDT
RECORD (PROPF)NAME  PROP
      J = DDT ENTRY(ENTAD, FSYS)
      RESULT  = 23 UNLESS  J = 0

      DDT == RECORD(ENTAD)
      PROP==RECORD(DDT_PROPADDR)
      PPERTRACK=PROP_PPERTRK
      IF  WHICH=0 START 
         PG=((CYL*PROP_TRACKS) + TRK) *PROP_PPERTRK
      FINISH  ELSE  START 
         TKSPERCYL=PROP_TRACKS
         LTRKS=PG//PPERTRACK
         PG=PG - LTRKS*PPERTRACK
         LCYL=LTRKS//TKSPERCYL
         TRK=LTRKS - LCYL*TKSPERCYL
         CYL=LCYL
      FINISH 
      RESULT =0
END ; ! CYL TRK CONVERT

!-----------------------------------------------------------------------

INTEGERFN  EQUSER(STRING (MAXTCPNAME) USER,PASSU)
! RESULT 1 IF USER IS IN THE CLASS "PASSU", ELSE RESULT 0
INTEGER  J,CHP,CHU
      CYCLE  J=0,1,LENGTH(USER)          {test length byte first}
         CHU=BYTEINTEGER(ADDR(USER)+J)
         CHP=BYTEINTEGER(ADDR(PASSU)+J)
         UNLESS  CHU=CHP OR  CHP='?' THEN  RESULT =NO
      REPEAT 
      RESULT =YES
END ; ! EQUSER

!-----------------------------------------------------------------------

INTEGERFN  OPEN TO(STRING (MAXTCPNAME)USER, INTEGER  MASK, ACT, CNSL)
!
! This function is used to store usergroups for the "open to","close to"
! commands and to store TCP-names which are to be closed (partial system close).
!
! ACT is:
!
!      0  Set user into next free entry     RES=0 OK,  70 list full
!      1
!        user not null
!         Is user in one of the entries?    RES=0 OK,  as OPEN TO
!                                              -1 OK,  as CLOSED TO
!        user null
!                                              80 NO.  (-1 if USER null).
!      2  Clear all the (relevant) entries  Result not relevant
!      3  Clear the entry                   RES=0 OK,  80 not found
!      4  Move pendg bits to inited bits for TCPs
!      5  Move inited bits to closed bits for TCPs
!      6  Give array contents on OPER

! the bits in MASK are:
!
!      2**0  an "open to" entry  (D/OPENTO user)
!      2**1  a "closed to" entry  (D/CLOSETO user)
!      2**2  TCP pending  (D/CLOSETCP identifier time  If time is given its a
!                 potential close, else see 2**4)
!      2**3  TCP init   Get to here from '2**2', about 7 mins before time
!      2**4  TCP closed  (D/CLOSETCP identifier  also happens in final stage
!             of partial close involving a TCP)
!
CONSTINTEGER  TOP = 30
RECORDFORMAT  RF(STRING (MAXTCPNAME)NAME, INTEGER  DATA)
OWNRECORD (RF)ARRAY  RS(0 : TOP)
RECORD (RF)NAME  R
SWITCH  A(0 : 6)
INTEGER  J, K
STRING (31) S
      K = -1
      CYCLE  J = 0, 1, TOP
         R == RS(J)
         -> A(ACT)
A(0):         ! insert
         K = J IF  K < 0 AND  R_NAME = ""; ! remember first free
         IF  R_NAME = USER START ; ! found
            R_DATA = R_DATA ! MASK
            RESULT  = 0
         FINISH 
         CONTINUE 
A(1):          ! query
         IF  USER = "" START 
            RESULT  = 0 IF  R_DATA & MASK > 0
         FINISH  ELSE  START 
             IF  EQUSER(USER , R_NAME) = YES START 
               IF  R_DATA & MASK > 0 START 
                  IF  R_DATA = CLOSG C 
                  THEN  RESULT  = -1 C 
                  ELSE  RESULT  = OK
               FINISH 
            FINISH 
         FINISH 
         CONTINUE 
A(2):          ! clear all
         IF  R_DATA & MASK > 0 START 
            R_DATA = R_DATA & (¬MASK)
            R_NAME = "" IF  R_DATA = 0
         FINISH 
         CONTINUE 
A(3):         ! clear the entry
         IF  EQUSER(USER, R_NAME) = YES AND  R_DATA & MASK > 0 START 
            R_DATA = R_DATA & (¬MASK)
            R_NAME = "" IF  R_DATA = 0
            RESULT =0
         FINISH 
         CONTINUE 
A(4):       ! initialise closing sequence
         R_DATA = R_DATA ! TCPINITG IF  R_DATA & TCPPENDG > 0
         CONTINUE 
A(5):        ! TCP closed
         R_DATA = R_DATA ! TCPCLOSED IF  R_DATA & TCPINITG > 0
         CONTINUE 
A(6):        ! list to oper
         S = ""
         S = "Open to " IF  R_DATA = OPENG
         S = "Closed to " IF  R_DATA = CLOSG
         S = "Closure of TCP: " IF  R_DATA & TCPPENDG > 0
         OPER(CNSL, S . R_NAME) AND  S = "" UNLESS  S = ""
         S = "TCP closed: " IF  R_DATA & TCPCLOSED > 0
         OPER(CNSL, S . R_NAME) UNLESS  S = ""
      REPEAT 
!
      IF  ACT = 0 START 
         RESULT  = 70 IF  K < 0; ! no free entry
         RS(K)_NAME = USER
         RS(K)_DATA = MASK
         RESULT  = 0
      FINISH 
!
      RESULT  = -1 IF  ACT = 1 AND  USER = ""; ! no special TCP settings
!
      RESULT  = 80; ! not in list, or totally irrelevant result!
END ; ! OPEN TO

!-----------------------------------------------------------------------

INTEGERFN  BIT STATUS(INTEGERARRAYNAME  OPSTAT,
   INTEGER  OPEN,FSYS)
!   OPEN  = -1    test status for FSYS
!                 result  1  OPEN  (or any FSYS open if FSYS = -1)
!                 result  0  CLOSED  (or all file systems closed if FSYS = -1)
!            0    set status CLOSED
!            1    set status OPEN
!   FSYS  = -1    for all file systems
!         0-99    for thst file system
CONSTINTEGERARRAY  MOP(0:3) = -1(3), X'F8000000'
INTEGER  J,A,VAR,M
      VAR=0
      A=ADDR(OPSTAT(0))
      IF  OPEN>=0 START 
         !  OPEN  =  0  close
         !           1  open
         IF  FSYS<0 START 
            ! All file systems
            CYCLE  J=0,1,3
               IF  OPEN=0 THEN  OPSTAT(J)=OPSTAT(J)&(¬MOP(J))  C 
                  ELSE  OPSTAT(J)=OPSTAT(J) ! MOP(J)
               REPEAT 
         FINISH  ELSE  START 
            ! file system FSYS
            *LDTB_101
            *LDA_A
            *LB_FSYS
            *LSS_OPEN
            *ST_(DR +B )
         FINISH 
      FINISH  ELSE  START 
         !  OPEN < 0:  return the OPEN status
         IF  FSYS<0 START 
            ! if all file systems closed return  0  (closed)
            ! if any file system open< return  1  (open)
            CYCLE  J=0,1,3
               M=MOP(J)
               IF  J=3 THEN  M=M>>28<<28; ! drop bit 101
               IF  OPSTAT(J)&M#0 THEN  VAR=1
               REPEAT 
         FINISH  ELSE  START 
            *LDTB_101
            *LDA_A
            *LB_FSYS
            *LSS_(DR +B )
            *ST_VAR
         FINISH 
      FINISH 
      RESULT =VAR
END ; ! BIT STATUS

!-----------------------------------------------------------------------

ROUTINE  DISPLAY VSNS
INTEGER  FE NO,NUM,PENDG CLO TCPS
STRING (40) W,TEXT
!
!          1         2         3        3
!0....5....0....5....0....5....0....5...9
!FEs: 0,1,2,3     Closed CLOSEUSERS 17.00
!
      NUM = 0
      W = ""
      CYCLE  FE NO = 0, 1, TOP FE NO
         UNLESS  FES FOUND & (1<<FE NO) = 0 START 
            W = W . "," UNLESS  W = ""
            W = W . TOSTRING(FE NO + '0')
            NUM = NUM + 1
         FINISH 
      REPEAT 
!
      TEXT = "No FEs" IF  NUM = 0
      TEXT = "FE: " IF  NUM = 1
      TEXT = "FEs: " IF  NUM > 1
      TEXT = TEXT . W
      TEXT = TEXT . " " WHILE  LENGTH(TEXT) < 17
      LENGTH(TEXT) = 17 IF  LENGTH(TEXT) > 17
!
      W = "Closed "; ! if 'closed' set for all file systems, else blank
      W = "       " UNLESS  BIT STATUS(OPSTAT, -1, -1) = 0 
      TEXT = TEXT . W
!
      IF  COM_SECSTOCD > 1 START   { ACTUAL CLOSE }
         W = "Part close "
         PENDG CLO TCPS = (¬(OPEN TO("", TCPPENDG, 1, 0)))
         IF  PENDG CLO FES ! PENDG CLO FSYS ! PENDG CLO NODES ! PENDG CLO TCPS=0 START 
            IF  SUPPRESS EXEC STOP = 0 C 
            THEN  W = "Full close " C 
            ELSE  W = "Closeusers "
         FINISH 
      FINISH  ELSE  START 
         IF  NEW CLOSE TIME > 0 C 
         THEN  W = "Close time " C 
         ELSE  W = "           "
      FINISH 
!
      TEXT = TEXT . W
      PLACE(TEXT, 0, 1, 0, 0)
END ; ! DISPLAY VSNS

!-----------------------------------------------------------------------

EXTERNALINTEGERFN  SHOW USE COUNT(INTEGER  FSYS, SOURCE, CNSL)

! The parameter source indicates whether the information is to come from the
! disc table (SOURCE = 0) or from the process' use count array (SOURCE = 1)

INTEGER  ENTAD,K,N1,N2,COUNT, J
INTEGERARRAY  A(0:99)
RECORD (DDTF)NAME  DDT
      IF  FSYS < 0 START 
         GET AV FSYS2(1, K, A)
         OPER(CNSL, "No of discs: " . ITOS(K))
      FINISH  ELSE  START 
         IF  AV(FSYS, 1) = 0 START 
            OPER(CNSL, "Disc not available")
            RESULT  = 23; ! FSYS NOT ONLINE
         FINISH 
         A(0) = FSYS
         K = 1
      FINISH 
      J = 0
      CYCLE  N1 = 0, 1, K-1
         N2 = A(N1)
         IF  0 <= N2 <= 99 START 
            IF  SOURCE = 0 START 
               J = DDT ENTRY(ENTAD, N2)
               EXIT  UNLESS  J = 0
               DDT == RECORD(ENTAD)
               COUNT = DDT_CONCOUNT
            FINISH  ELSE  COUNT = FSYS USE COUNT(N2)
            OPER(CNSL, "Fsys ".ITOS(N2)." Count=".ITOS(COUNT))
         FINISH  ELSE  OPER(CNSL, "N2: " . ITOS(N2))
      REPEAT 
      RESULT  = J
END ; ! SHOW USE COUNT

!-----------------------------------------------------------------------

ROUTINE  KILLI(INTEGER  PROCNO)
RECORD (PARMF) P
      P=0
      P_DEST=(COM_ASYNCDEST+PROCNO)<<16 ! X'FFFF'
      DPONI(P)
END ; ! KILLI

!-------------------------------------------------------------------------------

ROUTINE  KILL(STRING (6) USER,INTEGER  PROCNO,CNSL)

!  USER = ""      kill all processes
! otherwise
!                 kill USER, but no action if more than one invocation

INTEGER  J,N
BYTEINTEGERARRAY  PROCS(0:255)
RECORD (PROCDATF) NAME  PROCE
      N=0
      J=LIVEHD
      WHILE  J#ENDLIST CYCLE 
         PROCE==PROCLIST(J)
         IF  USER="" OR  C 
            (USER=PROCE_USER#"" AND  PROCNO=PROCE_PROCESS) C 
         THEN  KILLI(PROCE_PROCESS) C 
         ELSE  IF  PROCE_USER=USER AND  PROCNO<0 C 
               THEN  PROCS(N)=PROCE_PROCESS AND  N=N+1
         J=PROCLIST(J)_LINK
      REPEAT 
      RETURN  IF  USER=""
      IF  N=0 THEN  OPER(CNSL,USER." not found") ELSE  C 
      IF  N=1 THEN  KILLI(PROCS(0)) ELSE  START 
         J=0
         WHILE  J<N CYCLE 
            OPER(CNSL,USER." proc ".ITOS(PROCS(J)))
            J=J+1
         REPEAT 
         OPER(CNSL,USER." not killed")
      FINISH 
END ; ! KILL

!-------------------------------------------------------------------------------

INTEGERFN  MAP XOP OWNS(INTEGER  DACT)

! Result 0 if OK, else non-zero.

INTEGER  J
RECORD (PARMF)P
      J = LOGLINK(P, DACT)
      RESULT  = J UNLESS  J = 0
!
      LOGH == RECORD(P_DEST)
      PROCLIST == LOGH_PROCLIST
      FREEHD == LOGH_FREEHD
      LIVEHD == LOGH_LIVEHD
      BACKHD == LOGH_BACKHD
      FES FOUND == LOGH_FES FOUND
      FE USECOUNT == LOGH_FE USECOUNT
      FEPS == LOGH_FEPS
      RESULT  = 0
END ; ! MAP XOP OWNS

!-------------------------------------------------------------------------------

ROUTINE  OPEN FEP(RECORD (PARMF)NAME  P)
INTEGER  DACT, WHICH FE, FLAG, PROTOCOL, ACT ADDON
OWNINTEGER  INIT=-1
RECORD (FEP DETAILF) NAME  FEP
SWITCH  ACT(FEP INPUT CONNECT : FEP OUTPUT DISCONNECT REPLY)
      INIT = MAP XOP OWNS(8) UNLESS  INIT = 0
      RETURN  UNLESS  INIT = 0
!
      DACT = P_DEST&255
      WHICH FE = (P_DEST>>8)&255
      IF  DACT > FEP OUTPUT DISCONNECT REPLY START 
         PROTOCOL = X29
         DACT = DACT - X29 ACTIVITY ADDON
         ACT ADDON = X29 ACTIVITY ADDON
      FINISH  ELSE  START 
         PROTOCOL = ITP 
         ACT ADDON = 0
      FINISH 
      FEP == FEPS(WHICH FE)_FEP DETAILS(PROTOCOL)
      -> ACT(DACT)
!*
ACT(FEP INPUT CONNECT):
   P = 0
   P_DEST = CONNECT STREAM
   P_SRCE = WHICH FE<<8!FEP INPUT CONNECT REPLY
   P_P1 = FEPS(WHICH FE)_FEP DETAILS(ITP)_INPUT STREAM
   P_P2 = DIRECT SYNC1 DEST!WHICH FE<<8!FEP INPUT MESS
                                        !INPUT HIGH LEVEL CONTROL MESSAGES TO THIS ACTIVITY
   P_P3 = 14<<24!WHICH FE<<16! IN CONTROL STREAM(ITP)
   FLAG = DPON3I("",P,0,0,6)
   P = 0
   P_DEST = CONNECT STREAM
   P_SRCE = WHICH FE<<8!FEP INPUT CONNECT REPLY+X29 ACTIVITY ADDON
   P_P1 = FEPS(WHICH FE)_FEP DETAILS(X29)_INPUT STREAM
   P_P2 = DIRECT SYNC1 DEST!WHICH FE<<8!FEP INPUT MESS+X29 ACTIVITY ADDON
                                        !INPUT HIGH LEVEL CONTROL MESSAGES TO THIS ACTIVITY
   P_P3 = 14<<24!WHICH FE<<16! IN CONTROL STREAM(X29)
   FLAG = DPON3I("",P,0,0,6)
   RETURN 
!*
!*
ACT(FEP INPUT CONNECT REPLY):                                !INPUT STREAM CONNECT REPLY
   IF  P_P2 = 0 START 
      FES FOUND = FES FOUND ! (1<<WHICH FE)
      FEP_INPUT STREAM = P_P1;    !STORE COMMS STREAM ALLOCATED
      P = 0
      P_DEST = CONNECT STREAM
      P_SRCE = WHICH FE<<8!FEP OUTPUT CONNECT REPLY+ACT ADDON
      P_P1 = FEP_OUTPUT STREAM
      P_P2 = DIRECT SYNC1 DEST!WHICH FE<<8!FEP OUTPUT REPLY MESS
                                        !OUTPUT HIGH LEVEL CONTROL MESSAGES TO THIS ACTIVITY
      P_P3 = 14<<24!WHICH FE<<16!OUT CONTROL STREAM(PROTOCOL)
      FLAG = DPON3I("",P,0,0,6)
   FINISH 
   RETURN 
!*
!*
ACT(FEP OUTPUT CONNECT REPLY):                                !OUTPUT STREAM CONNECT REPLY
   IF  P_P2 = 0 START 
      FEP_OUTPUT STREAM = P_P1
      FEP_INPUT CURSOR = 0
      P_DEST = ENABLE STREAM
      P_SRCE = WHICH FE<<8!FEP INPUT ENABLE REPLY+ACT ADDON
      P_P1 = FEP_INPUT STREAM
      P_P2 = FEP_IN BUFF DISC ADDR
      P_P3 = FEP_IN BUFF DISC BLK LIM
      P_P4 = 2<<4!1;                    !BINARY CIRCULAR
      P_P5 = FEP_IN BUFF OFFSET
      P_P6 = FEP_IN BUFF LENGTH
      FLAG = DPON3I("",P,0,0,6)
   FINISH  ELSE  START 
      WRSNT("CONNECT OUT STRM FE", WHICH FE, 5)
      WRSN(" FAILS ", P_P2)
   FINISH 
   RETURN 
!*
!*
ACT(FEP INPUT ENABLE REPLY):                                !ENABLE INPUT STREAM REPLY
   IF  P_P2 = 0 START 
      FEP_OUTPUT CURSOR = 0
      P_DEST = ENABLE STREAM
      P_SRCE = WHICH FE<<8!FEP OUTPUT ENABLE REPLY+ACT ADDON
      P_P1 = FEP_OUTPUT STREAM
      P_P2 = FEP_OUT BUFF DISC ADDR
      P_P3 = FEP_OUT BUFF DISC BLK LIM
      P_P4 = 2<<4!1;                    !BINARY CIRCULAR
      P_P5 = FEP_OUT BUFF OFFSET
      P_P6 = FEP_OUT BUFF LENGTH
      FLAG = DPON3I("",P,0,0,6)
   FINISH  ELSE  START 
      WRSNT("ENABLE IN STRM FE", WHICH FE, 5)
      WRSN(" FAILS ", P_P2)
   FINISH 
   RETURN 
!*
!*
ACT(FEP OUTPUT ENABLE REPLY):                                !ENABLE OUTPUT STREAM REPLY
   IF  P_P2 = 0 START 
      FEPS(WHICH FE)_AVAILABLE = YES
      WRSN("CONNECTED FE", WHICH FE)
   FINISH  ELSE  START 
      WRSNT("ENABLE OUT STRM FE", WHICH FE, 5)
      WRSN(" FAILS ", P_P2)
   FINISH 
   RETURN 

!--------------------- Entry for Disconnect Stream follows ---------------------

!*
ACT(FEP INPUT DISABLE):
   P = 0
   P_DEST = DISABLE STREAM
   P_SRCE = WHICH FE<<8!FEP INPUT DISABLE REPLY
   P_P1 = FEPS(WHICH FE)_FEP DETAILS(ITP)_INPUT STREAM
   P_P2 = 4; ! suspend
   FLAG = DPON3I("",P,0,0,6)
   P = 0
   P_DEST = DISABLE STREAM
   P_SRCE = WHICH FE<<8!FEP INPUT DISABLE REPLY+X29 ACTIVITY ADDON
   P_P1 = FEPS(WHICH FE)_FEP DETAILS(X29)_INPUT STREAM
   P_P2 = 4; ! suspend
   FLAG = DPON3I("",P,0,0,6)
   RETURN 
!*
!*
ACT(FEP INPUT DISABLE REPLY):
   IF  P_P2 = 0 START 
      P = 0
      P_DEST = DISABLE STREAM
      P_SRCE = WHICH FE<<8!FEP OUTPUT DISABLE REPLY+ACT ADDON
      P_P1 = FEP_OUTPUT STREAM
      P_P2 = 4; ! suspend
      FLAG = DPON3I("",P,0,0,6)
   FINISH  ELSE  START 
      WRSNT("DISABLE IN STRM FE", WHICH FE, 5)
      WRSN(" FAILS ", P_P2)
   FINISH 
   RETURN 
!*
!*
ACT(FEP OUTPUT DISABLE REPLY):
   IF  P_P2 = 0 START 
      P_DEST = DISCONNECT STREAM
      P_SRCE = WHICH FE<<8!FEP OUTPUT DISCONNECT REPLY+ACT ADDON
      P_P1 = FEP_OUTPUT STREAM
      FLAG = DPON3I("",P,0,0,6)
   FINISH  ELSE  START 
      WRSNT("DISABLE OUT STRM FE", WHICH FE, 5)
      WRSN(" FAILS ", P_P2)
   FINISH 
   RETURN 
!*
!*
ACT(FEP OUTPUT DISCONNECT REPLY):
   IF  P_P2 = 0 START 
      P_DEST = DISCONNECT STREAM
      P_SRCE = WHICH FE<<8!FEP INPUT DISCONNECT REPLY+ACT ADDON
      P_P1 = FEP_INPUT STREAM
      FLAG = DPON3I("",P,0,0,6)
   FINISH  ELSE  START 
      WRSNT("DISCONNECT OUT STRM FE", WHICH FE, 5)
      WRSN(" FAILS ", P_P2)
   FINISH 
   RETURN 
!*
!*
ACT(FEP INPUT DISCONNECT REPLY):
   IF  P_P2 = 0 START 
      FES FOUND = FES FOUND & (¬(1<<WHICH FE))
      FES CLOSED= FES CLOSED& (¬(1<<WHICH FE))
      DISPLAY VSNS
      FEPS(WHICH FE)_AVAILABLE = NO
      FEP_INPUT STREAM = 0;    ! reset for re-use
      FEP_OUTPUT STREAM = 1;   ! reset for re-use
      WRSN("DISCONNECTED FE", WHICH FE)
   FINISH  ELSE  START 
      WRSNT("DISCONNECT OUT STRM FE", WHICH FE, 5)
      WRSN(" FAILS ", P_P2)
   FINISH 
END ; ! OPEN FEP

!-------------------------------------------------------------------------------

ROUTINE  CONNECT FE(INTEGER  STRM)
INTEGER  FE NO,LO,HI
RECORD  (PARMF)P
! messages to FE have "destination" specified by a "stream id"
! messages to process1 have "destination" specified by a "stream no"
! (chosen by comms controller).
      LO=0
      HI=TOP FE NO
      IF  STRM>=0 THEN  LO=STRM AND  HI=STRM
      CYCLE  FE NO=LO,1,HI
         P=0
         P_DEST=FE NO<<8 ! FEP INPUT CONNECT
         OPEN FEP(P)
      REPEAT 
END ; ! CONNECT FE

!-----------------------------------------------------------------------

ROUTINE  DISCONNECT FE(INTEGER  STRM)
RECORD  (PARMF)P
INTEGER  FE NO,LO,HI
!MON MON(3) = MON(3) + 1
      LO=0
      HI=TOP FE NO
      IF  STRM>=0 THEN  LO=STRM AND  HI=STRM
      CYCLE  FE NO=LO,1,HI
         P=0
         P_DEST=FE NO<<8 ! FEP INPUT DISABLE
         IF  FES FOUND&(1<<FE NO)#0 START 
            OPEN FEP(P)
         FINISH  ELSE  START 
            IF  STRM>=0 THEN  DOPER2("No such FE")
            ! (here, we should call OPER(CNSL,..., not DOPER)
         FINISH 
      REPEAT 
END ; ! DISCONNECT FE

!-------------------------------------------------------------------------------

INTEGERFN  AUTOPRG(STRING (31)FILE, INTEGER  SITE)
INTEGER  J, SEG, GAP
RECORD (FINFOF)FINFO
CONSTINTEGER  SITESEG = 12
      J = DFINFO("", FILE, -1, ADDR(FINFO))
      -> OUT UNLESS  J = 0
!
      J = 27
      -> OUT IF  FINFO_NKB > 512 OR  FINFO_NKB < 128
!
      SEG = 0
      GAP = 0
      J = DCONNECTI(FILE, FINFO_FSYS, 5, 0, SEG, GAP)
      -> OUT UNLESS  J = 0 OR  J = 34
!
      DCHAIN(SITESEG, 1)
      DCHAIN(SITESEG + 1, 1)
!
      SITE = SITE + SUPLVN S START
      J = DISC SEG CONNECT(COM_SUPLVN, SITE, SITESEG, X'22'{apf}, 64{pgs}, 0)
      -> OUT UNLESS  J = 0
!
      J = DISC SEG CONNECT(COM_SUPLVN, SITE+X'40', SITESEG+1, X'22', 64, 0)
      -> OUT UNLESS  J = 0
!
      MOVE(FINFO_NKB<<10, SEG<<18, SITESEG<<18)
!
      J = DDISCONNECTI(FILE, FINFO_FSYS, 0)
      DCHAIN(SITESEG, 0)
      DCHAIN(SITESEG+1, 0)
      J = 0
OUT:
      RESULT  = J
END ; ! AUTOPRG
!
!-----------------------------------------------------------------------
!
ROUTINE  DESTROY TEMPS(STRING (6)USER, STRING (3)SUFFIX, C 
      INTEGER  FSYS)
CONSTINTEGER  TOPTN = 5
CONSTSTRING (8)ARRAY  TNAMES(0:TOPTN) = C 
   ".#STK", ".#LCSTK", ".#SIGSTK", ".T#LOAD", ".T#IT", ".#UINFI"
INTEGER  J, I, P3
STRING (18)FILE
! reverse order of destroying because we create #STK first and we
! want all the others to be gone before we create a new #STK with the
! same suffix
      P3 = 0
      CYCLE  J=TOPTN,-1,0
         FILE = USER . TNAMES(J) . SUFFIX
         I = DDESTROYF(FILE, FSYS, 5)
         WRS3N("Destroy", USER, TNAMES(J), I) IF  I # 0 AND  J < 3
      REPEAT 
END ; ! DESTROY TEMPS

!-----------------------------------------------------------------------

ROUTINE  PROCESS STOPS(STRING (6)USER, INTEGER  INVOC, STOPREASON, KINSTRS, PTRNS)
! post-stopping tidying-up done here:
!     . destroying known tempfiles
!     . disconnecting console streams
!     . freeing mag tapes
!     . etc7
RECORD (HF)NAME  NH
INTEGER  IX,J,INDAD,FSYS, CUR, FE NO, CPU, RES
BYTEINTEGERNAME  PREV
RECORD  (PROCDATF)PIX
RECORD  (PARMF)P
STRING (3)INVOCS
BYTEINTEGERNAME  USE
RECORD (DIRCOMF)NAME  DIRCOM
INTEGER  SITE, SITEA, COUNT
STRINGNAME  SITEF
CONSTSTRING (7)ARRAY  SITEN(1:2) = "SUBSYS", "STUDENT"
!
      -> OUT UNLESS  0 <= INVOC < 255
      INVOCS = ITOS(INVOC)
!
      PRINTSTRING(" STOPS: ")
      PRINTSTRING(USER)
      PRINTSTRING(" INVOC ")
      PRINTSTRING(INVOCS)
!
      IX = -1; ! if not found
      PREV == LIVEHD
      CUR = LIVEHD
      WHILE  CUR # ENDLIST CYCLE 
         IF  PROCLIST(CUR)_USER = USER ANDC 
             PROCLIST(CUR)_INVOC = INVOC C 
         THEN  IX = CUR AND  EXIT 
         PREV == PROCLIST(CUR)_LINK
         CUR = PROCLIST(CUR)_LINK
      REPEAT 
!
      IF  IX<0 START 
OUT:
         DOPERR("CANT FIND ".USER, 0, INVOC)
         RETURN 
      FINISH 
!
      PIX = PROCLIST(IX)
      WRSNT(" PROC", PIX_PROCESS, 5)
      WRSNT(" REAS", STOPREASON<<8>>8, 5)
      CPU = KINSTRS//COM_KINSTRS
      WRSNT(" CPU", CPU, 5)
      WRSNT(" PTRNS", PTRNS, 5)
      WRSNT(" PROCS", COM_USERS, 5)
      WRSNT(" PRIORITY", STOPREASON >> 24, 5) IF  PIX_REASON = BATCH
      NEWLINE
!
      IF  CPU > 30 AND  PTRNS > (CPU << 13) C 
      THEN  DOPER2(USER . " excessive PTRNS")
!
      IF  0 < PIX_SITE < 3 START ; ! ie 1 or 2
         SITE = PIX_SITE
         DIRCOM == RECORD(SYSAD(DIRCOMKEY, -1))
         SITEA = ADDR(DIRCOM_SUBSYS SITE COUNT)
         SITEA = SITEA + 24 IF  SITE = 2
         *LXN_SITEA
         *TDEC_(XNB +0)
         *ST_COUNT; ! original value
!
         SITEF == STRING(SITEA + 4)
         UNLESS  SITEF = "" START 
!
            IF  COUNT = 1 START ; ! now zero
               DOPER2(SITEN(SITE) . " site free")
               DOPER2("AUTOPRG " . SITEF)
               J = AUTOPRG(SITEF, X'300' + SITE << 7)
               DOPER2("Flag=" . ITOS(J))
               SITEF = "" IF  J = 0
            FINISH 
!
            IF  COUNT < 1 OR  1 < COUNT < 9 START 
               DOPER2(SITEN(SITE) . " site count " . ITOS(COUNT-1))
            FINISH 
!
         FINISH 
      FINISH 
!
      PREV=PIX_LINK; ! REMOVE FROM CHAIN
      IF  BACKHD=IX C 
      THEN  BACKHD=PIX_BLNK C 
      ELSE  PROCLIST(PREV)_BLNK=PIX_BLNK

      PROCLIST(IX)_LINK=FREEHD
      FREEHD=IX

      FSYS=PIX_FSYS
      RES = HINDA(USER, FSYS, INDAD, 2)
      DOPERR("PROC STOPS",12,RES) AND  RETURN  UNLESS  RES=0;  ! failed to find username (PROCESS STOPS)
      IF  PIX_REASON=BATCH START 
         BATCH PROCESSES=BATCH PROCESSES - 1

         P = 0; ! advise spoolr
         P_DEST = X'FFFF0035'
         P_P1 = PIX_ID
         J = BATCH STREAMS - BATCH PROCESSES
         P_P3 = J IF  J > 0
         P_P4 = STOPREASON<<8>>8
         P_P5 = KINSTRS
         P_P6 = PTRNS
         J = DPON3I("SPOOLR", P, 0, SYNC1TYPE, PONANDCONTINUE)

      FINISH  ELSE  START 
         IF  PIX_REASON=INTER START 
            COM_RATION = COM_RATION-1
            !
            ! Decrement use-count of FE
            !
            FE NO=(PIX_ID>>16)&255
            FE USE COUNT(FE NO)=FE USE COUNT(FE NO) - 1
            IF  CLO FES&(1<<FE NO)#0 AND  FE USE COUNT(FE NO)=0 START 
               DOPER2("FE no. ".ITOS(FE NO)." disconnecting")
               DISCONNECT FE(FE NO)
            FINISH 
         FINISH 
         ! Decrement usergroup counts in restrictions lists
         J=LISTMOD(USER,0,-1);         ! ignore flag. ",0,-1" indicates "decrement".
      FINISH 

      NH == RECORD(INDAD)
      IF  PIX_REASON = BATCH C 
      THEN  USE == NH_BUSE C 
      ELSE  USE == NH_IUSE

      USE = USE - 1 IF  USE > 0

      DESTROY TEMPS(USER, INVOCS, FSYS)
      J = DISC USE COUNT(FSYS,-1); ! covers normal stack file + other workfiles
      !
      ! Get index seg out of VM if this users's FSYS is closing
      IF  BIT STATUS(FCLOSING,-1,FSYS)#0 THEN  EMPTY DVM
END ; ! PROCESS STOPS

!-----------------------------------------------------------------------

ROUTINE  SET ITADDR(STRING (63)ITADDR, INTEGERNAME  NODENO, CONSOLE,
      STRINGNAME  TCPNAME)
!
! This routine is subject to change, with the changing comms formats for the
! ITaddr. Currently there are three flavours:
!
!   Old ITP TCPs   byte  0        no of bytes which follow (as "string length")
!   at Glasgow:    byte  1        node no ("binary")
!                  byte  2        network terminal no of TCP
!                  byte  3        console no (port no of interactive terminal on
!                                 its TCP
!                  bytes 4-end    TCP-name in (printable) text (no built-in counts or anything)
!                                 followed by one space, followed by console no as above.
!                                 Thus:

!                    <TCPname> <space char> <console no(bin)>

!        Example:    TCPD (X28)

!        The TCP-name is therefore the text from byte 4 up to and not including
!        the (first) space. This name is up to constinteger MAXTCPNAME in length
!
!-------------------------------------------------------------------------------
!   Ring TCPs:     byte  0        no of bytes which follow (as "string length")
!                  byte  1        node no ("binary")
!                  byte  2        network terminal no of TCP
!                  byte  3        console no (port no of interactive terminal on
!                                 its TCP
!                  bytes 4-end    TCP-name in (printable) text (no built-in counts or anything)
!                                 followed by a plus char, followed by console no in prinable chars.
!                                 Thus:

!                    <TCPname> <plus char> <console no. in prinable chars>

!        Example:    TCPD+X28

!        The TCP-name is therefore the text from byte 4 up to and not including
!        the (first) space. This name is up to constinteger MAXTCPNAME in length
!
!-------------------------------------------------------------------------------
!    New Edinburgh:   byte 0    no of bytes which follow, like string length
!
!             bytes 1 to end    TCP name (printable) followed by a '+' followed
!                               by two printable hex digits being the console
!                               number.
!                               OR, in the case of TripleX:
!                               All numeric PAD address.
!
!-------------------------------------------------------------------------------
!   Kent:          byte  0        no of bytes which follow (as"string length").
!                  bytes 1-end    (printable) text, with sub-fields delimited by
!                                 colons, as follows:

!                    <TCPname> : <line-speed> : <some number> : EMAS
!        Example:    GATEX : 9600 : 1 : EMAS

!        The TCP-name is therefore the text from byte 1 up to and not including
!        the first colon. This name may be up to 15 chars. See the constinteger
!        MAXTCPNAME.

INTEGER  J, CH, ALL TEXT, COLONS, FIRSTCOLON, FIRSTSPACE, ALL NUMERIC, L
      ALL TEXT=1
      CONSOLE = 0
      COLONS=0
      FIRSTCOLON=0
      FIRSTSPACE=0
      ALL NUMERIC=1

      ! First look to see if all chars are printable, and count the colons. Note
      ! position of first colon if any.

      J=0
      L = LENGTH(ITADDR)
      WHILE  J<L CYCLE 
         J=J+1
         CH=CHARNO(ITADDR, J)
         UNLESS  '0'<=CH<='9' THEN  ALL NUMERIC=0
         UNLESS  32<=CH<=126 THEN  ALL TEXT=0
         IF  CH=' ' AND  FIRSTSPACE=0 AND  J>4 THEN  FIRSTSPACE=J
         IF  CH=':' START 
            IF  COLONS=0 THEN  FIRST COLON=J
            COLONS=COLONS+1
         FINISH 
      REPEAT 

      NODENO=0
      TCPNAME <- ITADDR
      IF  ALL TEXT = 0 START ; ! old Edinburgh
         IF  CHARNO(ITADDR, L-2) = '+' C 
         THEN  TCPNAME = FROMSTRING(ITADDR, 4, L-3) AND  C 
         CONSOLE = (CHARNO(ITADDR, L-1)<<8) ! CHARNO(ITADDR, L) ELSE  C 
         IF  FIRSTSPACE > 0 THEN  TCPNAME = FROMSTRING(ITADDR, 4, FIRSTSPACE - 1)
         NODE NO = CHARNO(ITADDR, 1)
      FINISH  ELSE  START 
         IF  COLONS > 1 AND  FIRST COLON <= MAX TCP NAME START ; ! Kent
            TCPNAME = FROMSTRING(ITADDR, 1, FIRSTCOLON - 1)
            UCTRANSLATE(ADDR(TCPNAME)+1, LENGTH(TCPNAME))
            CONSOLE = CHARNO(ITADDR,FIRSTCOLON+2) - '0'
            IF  CHARNO(ITADDR,FIRSTCOLON+3) # ':' THEN  START 
               CONSOLE = CONSOLE*10 + (CHARNO(ITADDR,FIRSTCOLON+3) - '0')
            FINISH 
         FINISH  ELSE  START ; ! New Edinburgh
            IF  ALL NUMERIC = 0 START 
               IF  CHARNO(ITADDR, L-2) = '+' C 
               THEN  TCPNAME = FROMSTRING(ITADDR, 1, L-3)
            FINISH 
            CONSOLE = (CHARNO(ITADDR, L-1)<<8) ! CHARNO(ITADDR, L)
         FINISH 
      FINISH 

!      %IF ALL TEXT#0 %AND COLONS>=2 %AND FIRST COLON<=MAXTCPNAME %START
!         TCPNAME <- FROMSTRING(ITADDR, 1, FIRSTCOLON-1)
!      %FINISH %ELSE %START
!         TCPNAME <- FROMSTRING(ITADDR, 4, FIRSTSPACE-1)
!         NODENO=CHARNO(ITADDR, 1)
!      %FINISH

      TCPNAME="null" IF  TCPNAME=""   {which would be inconvenient}
END ; ! SET ITADDR

!-----------------------------------------------------------------------

ROUTINE  LOGON REPLY(STRING (63)S, USER, INTEGER  J,
   STREAM ID, PROTOCOL, STRING (63)ITADDR)
INTEGER  ITA0, ITA1, KK
STRING (63)W, TEMP, AA, BB
      W = S
      UNLESS  J = 0 START 
         J = TOPMESSAGE UNLESS  0 < J < TOPMESSAGE
         W = MESSAGE(J)
      FINISH 

      ! For new FE software, all these messages require CR-LF.
      KK = J
      KK = 0 IF  KK = 114
      TEMP = TOSTRING(KK) . TOSTRING(LENGTH(W)+2) . W . TOSTRING(13) . SNL
      OUTPUT MESSAGE TO FEP(FEPS, C 
         (STREAM ID>>16)&255,1,ADDR(TEMP)+1, C 
         LENGTH(TEMP), STREAM ID, PROTOCOL)
                                   ! INFO attempts to log on very frequently
                                   ! dont log these attempts
      UNLESS  J=0 OR  USER->AA.("INFO").BB START 
         PRINTSTRING(USER)
         PRINTSTRING(": LOGON FAILS ")
         IF  J = 103 AND  LENGTH(ITADDR) > 0 START 
            LENGTH(ITADDR) = 7 IF  LENGTH(ITADDR) > 7
            STRING(ADDR(ITA0)) = ITADDR
            PRHEX(ITA0); SPACE
            PRHEX(ITA1); SPACE
         FINISH 
         WRSN(W, J)
      FINISH 
END ; ! LOGON REPLY

!-----------------------------------------------------------------------
!                                      
EXTERNALINTEGERFN  STARTP(STRING (6)USER, STRINGNAME  FILE,
      STRING (63)ITADDR, INTEGERNAME  INVOC,
      INTEGER  FSYS, STARTCNSL, REASON, STREAM ID, DIRVSN, PROTOCOL)
!
!
! FILE parameter is:
!     - on input, for a batch job, SPOOLR's file
!     - on output, an error msg
!
! DIRVSN parmeter: if in range 0-3, use that dirvsn
!                  if -1, use default dirvsn
!                  if -2, use (and reset to default) INDEX DIRVSN
! and the DIRVSN parameter is set as follows:
!     start from oper:   -1, = default, unless explicitly given
!     start from login:  -2, = from index (and reset to default)

! reasons for starting:
!     0     interactive log-on
!     1     started at oper console
!     2     batch job started by SPOOLr
CONSTINTEGER  TOPREASON = 6
CONSTSTRING (4)ARRAY  REAS(0:TOPREASON) = C 
      "I", "D/", "B", "TEST", "NEW", "FORK", "???"
!
RECORDFORMAT  MF(INTEGERARRAY  START(0:TOPREASON), INTEGER  COUNT,
      STRING (6)ARRAY  USER(0:999))
RECORD (MF)NAME  M
!
RECORD  (PARMF)P
INTEGER  DIRSITE,INDAD,STKKB,GPINDAD,ISESSM,DEFAULT STKKB
INTEGER  CUR,IMAX,BMAX,TMAX,IUSE,BUSE,DACT IN SCHEDULE
INTEGER  J,LSTACKDA,DSTACKDA,DGLA DA,PROCNO,STARTRESULT,SEG,GAP
INTEGER  PREEMPT AT,FE NO
INTEGER  FUNDSI, LCSTKKB, UINFAD, PENDG CLO TCPS
INTEGER  DA, CONSOLE
STRING (7) SUFFIX,REST
STRING (31)SPOOLRFILE
INTEGER  FN, MINS, LINVOC
INTEGERNAME  ITADR
INTEGER  AITADR

! for start-process message to supervisor:
!  P1-P2  to string(6) USER ID
!  P3  to be DA of local controller stack
!  P4  to be DA of director code (or default director, if zero)
!  P5  to be DA of local stack (first section. cbt entries will be
!        set up for 255kbytes)
!  P6  to be DA for DIRECTOR GLA

! create local stack file
RECORD (HF)NAME  NH
RECORD (UINFF)NAME  UINF
BYTEINTEGERNAME  USEFIELD


      SPOOLRFILE = FILE
      FILE = ""
!
      REASON = TOPREASON UNLESS  0 <= REASON <= TOPREASON
      LOG ACTION = LOG ACTION ! DT
      PRE EMPT AT=(COM_RATION>>16)&255
      IF  FCHECKPROCS#0 THEN  RESULT  = 102; ! cannot start process
      IF  FREEHD=ENDLIST START 
         DOPERR("STRTP NO PRCLST CELLS", 0, -1); ! should not occur (no proclist cells)
         RESULT  = 102; ! cannot start
      FINISH 
! find out local stacksize to set from index hdr


      J = HINDA(USER, FSYS, INDAD, 0)
      RESULT  = 110 UNLESS  J = 0; ! User not found

      !                 Interactive users >= scarcity
      FUNDSI = FUNDS(GPINDAD, INDAD)
      IF  REASON=INTER AND  COM_RATION&255>=COM_RATION>>24 START 
         IF  FUNDSI<=0 THEN  RESULT  = 109
         IF  COM_RATION&255>=PRE EMPT AT START 
            CUR = BACKHD
            MINS = COM_SECSFRMN //60
            WHILE  CUR # ENDLIST CYCLE 
               IF  PROCLIST(CUR)_PRE EMPT # 0 START 
                  PROCLIST(CUR)_PRE EMPT = 0
                  PROCLIST(CUR)_SESSEND = MINS + 8
                  PRINTSTRING("PRE EMPTED: ")
                  WRSN(PROCLIST(CUR)_USER, PROCLIST(CUR)_INVOC)
                  EXIT 
               FINISH 
               CUR = PROCLIST(CUR)_BLNK
            REPEAT 
         FINISH 
      FINISH 

      J = HINDA(USER, FSYS, INDAD, 0)
      RESULT  = 110 UNLESS  J = 0
!
      NH == RECORD(INDAD)
      STKKB = NH_STKKB
      TMAX = NH_TMAX
      BMAX = NH_BMAX
      IMAX = NH_IMAX
      IUSE = NH_IUSE
      BUSE = NH_BUSE
      ISESSM = NH_ISESSM
!
      IF  REASON = BATCH C 
      THEN  USEFIELD == NH_BUSE C 
      ELSE  USEFIELD == NH_IUSE
!
      IF  DIRVSN = -2 AND  NH_DIRVSN < 8 START 
         DIRVSN = NH_DIRVSN & 3
         NH_DIRVSN = 255 IF  NH_DIRVSN < 4
      FINISH 

      DEFAULT STKKB=BLKSI*EPAGE SIZE
      STKKB=DEFAULT STKKB IF  STKKB<DEFAULT STKKB; ! 1 block minimum, which is what the LC connects
! see whether user is allowed to have more processes running
      TMAX=DEFAULT TMAX IF  TMAX=255
      BMAX=DEFAULT BMAX IF  BMAX=255
      IMAX=DEFAULT IMAX IF  IMAX=255
      IF  IUSE+BUSE>=TMAX OR   C 
         (REASON=BATCH AND  BUSE>=BMAX) OR   C 
         (REASON=INTER AND  IUSE>=IMAX) THEN  RESULT  = 104; ! user already has max processes
      USEFIELD=USEFIELD + 1
      IF  ISESSM=0 THEN  ISESSM=DEFAULT SESSLEN
      ! But not applicable if not an interactive session or if session
      ! close time is sooner.
      IF  REASON#INTER OR  0<COM_SECSTOCD//60<ISESSM+5  C 
      THEN  ISESSM=0


! create local stack file
      FN = 1
      CYCLE  LINVOC = 0, 1, 254
         FILE = USER . ".#STK" . ITOS(LINVOC)
         J = DCREATEF(FILE, FSYS, STKKB, 5, 1, DSTACKDA); ! tempfile and allocate
         EXIT  UNLESS  J = 16; ! already exists
      REPEAT 
      DERR2("UNIQUE ".FILE, 1, J) UNLESS  J = 0
      SUFFIX = ITOS(LINVOC)
      INVOC = LINVOC
      -> WORK FAIL UNLESS  J = 0

! create local controller stack file. Get size from bound of seg 0 in local segment table.
      LCSTKKB = (((INTEGER(0)&X'0003F000') ! X'FFF' + 1) >> 10) + 16 { first 4 pages for DGLA }
      FILE = USER . ".#LCSTK".SUFFIX
      J = DCREATEF(FILE, FSYS, LCSTKKB, 16+4+1, 2, D GLA DA); ! zero, temp & allocate
      -> WORK FAIL IF  16#J#0
! create T#LOAD file
      FILE = USER . ".T#LOAD".SUFFIX
      J = DCREATEF(FILE, FSYS, 12, 16+4+1, 3, DA); ! zero, temp and allocate
      -> WORK FAIL IF  16#J#0
! Create T#IT
      IF  REASON = INTER OR  REASON = NEWSTART OR  REASON = FORK START 
         FILE = USER . ".T#IT" . SUFFIX
         J = DCREATEF(FILE, FSYS, 4, 4+1, 4, DA); ! 1 page, temp
         -> WORK FAIL IF  0 # J # 16
      FINISH 
! Create #UINF file
      FILE = USER . ".#UINFI" . SUFFIX
      J = DCREATEF(FILE, FSYS, 4, (11<<24)+64+16+4+1, 5, DA)
      -> WORK FAIL IF  0#J#16
      SEG = 0
      GAP = 0
      FN = 2
      J = DCONNECTI(FILE, FSYS, 8+2+1, 0, SEG, GAP)
      -> WORK FAIL UNLESS  J = 0
      UINFAD = SEG << 18


      UINF==RECORD(UINFAD)
      UINF = 0; ! CLEAR IT OUT
      UINF_PROTOCOL = PROTOCOL
      UINF_USER=USER
      UINF_FSYS=FSYS
      UINF_ISUFF=INVOC; ! +'0' Subsys can cope with >=0 (ie not >= '0')
      UINF_REASON=REASON
      !
      UINF_REASON=INTER IF  UINF_REASON=NEWSTART; ! Subsys can't cope at Sep 81
      !
      UINF_BATCH ID=STREAM ID
      UINF_STARTCNSL=STARTCNSL
      UINF_SPOOLRFILE=SPOOLRFILE
      UINF_STREAM ID=STREAM ID
      UINF_DIDENT=-2; {and not required at all after Subsys has stopped looking at this field}
      UINF_SCARCITY=COM_RATION>>24
      UINF_PRE EMPT AT=PREEMPT AT
      UINF_SESSLEN=ISESSM
      UINF_DIRVSN = DIRVSN
      UINF_ITADDR <- ITADDR
      LENGTH(ITADDR)=19 IF  LENGTH(ITADDR)>19; ! that's all the room we've
                                               ! got in that part of the record
                                               ! format at present
      STRING(ADDR(UINF_ITADDR0))=ITADDR
      !
      ! Place known information about any pending partial close into UINF
      MOVE(16,ADDR(PENDG FCLOSING(0)),ADDR(UINF_FCLOSING(0)))
      UINF_CLO FES=PENDG CLO FES

! If part close in progress, set PART CLOSE word -1 to indicate to subsystem
! and to executive processes that COM_SECSTOCD relates to a partial close
! only (the subsystem will not want to put out its "Warning - close in nn 
! minutes" message at process start-up, for example).

      PENDG CLO TCPS=(¬(OPEN TO("", TCPPENDG, 1, 0)))
      UINF_PART CLOSE=-1 IF  PENDG CLO FES ! PENDG CLO NODES ! PENDG CLO FSYS ! PENDG CLO TCPS#0

! (Always get DIRECTOR from "SLOAD" disc).
      DIRSITE = 0
      IF  0<=DIRVSN<=3 AND  REASON # BATCH START 
         DIRSITE=X'200'+X'40'*DIRVSN+COM_SUPLVN<<24+SUPLVNSSTART
      FINISH 
!
      UNLESS  MONITORAD = 0 START 
         M == RECORD(MONITORAD)
         M_START(REASON) = M_START(REASON) + 1
!
         IF  REASON = INTER START 
            UNLESS  NH_LASTLOGON >> 17 = PACKDT >> 17 START ; ! first one today
               IF  M_COUNT < 999 START 
                  M_USER(M_COUNT) = USER
                  M_COUNT = M_COUNT + 1
               FINISH 
            FINISH 
         FINISH 
      FINISH 
!
! Here is the gen about the new FE arrangements.
! The logon reply must always arrive before the user process' connect comms
! stream request. We used to get the user process to send the reply if the
! case of successful startup, but with the new FE communication method, the
! process is unable to do this (it could have access to the enabled output
! file, but it cannot get at the cursor pointers to synchronise itself).
! So we are going to send a "successful" message here, before actually
! starting the new process. If the process eventually fails to start success-
! fully, a subsequent failure message, destined for the terminal, but routed
! via the enabled output file held by DIRECT, is acceptable to the FEP software,
! provided that the process has not yet done it's "connect comms stream(s)".
! The mechanism for outputting this message is via DACT 18 in routine PROCESS1.

      IF  REASON=INTER THEN  LOGON REPLY("Logged on",USER,0,STREAM ID, PROTOCOL, ITADDR)
          ! only for interactive terminal sessions

! start-process message to supervisor
      DACT IN SCHEDULE=1;              ! start interactive process
      IF  REASON=BATCH OR  (USER->("JOBR").REST AND   C 
         REASON#NEWSTART AND  C 
         REASON#INTER) THEN  DACT IN SCHEDULE=16; ! start batch process
      P=0
      P_DEST=X'00030000' ! DACT IN SCHEDULE
!  STRING(ADDR(P_P1))=USER,  RH byte of P2 = 'invocation no'
      P_P2=INVOC
      MOVE(7, ADDR(USER), ADDR(P_P1))
      P_P3 = D GLA DA + 4  {LSTACKDA}
      P_P4=DIRSITE
      P_P5=DSTACKDA
      P_P6=DGLA DA
      DOUT11I(P)
! replies from supervisor are:
!     0     OK
!     1     SYSTEM FULL
!     2     CANNOT START PROCESS
      STARTRESULT=P_P1
      PROCNO=P_P5
      IF  STARTRESULT=0 START 
         J = DISC USE COUNT(FSYS,+1)
         IF  REASON=INTER START 
            COM_RATION=COM_RATION+1
            !
            ! Increment use count for this FE
            FE NO=(STREAM ID>>16)&255
            FE USE COUNT(FE NO)=FE USE COUNT(FE NO) + 1
         FINISH 
!
         PRINTSTRING(REAS(REASON))
         PRINTSTRING("START: ")
         PRINTSTRING(USER)
         PRINTSTRING(" INVOC ")
         PRINTSTRING(SUFFIX)
         PRINTSTRING(" PROC")
         WRITE(PROCNO,2)
!
         IF  REASON=INTER START 
AITADR = ADDR(UINF_ITADDR0)
CYCLE  J = 0, 4, 16
      ITADR == INTEGER(AITADR + J)
      EXIT  IF  ITADR = 0
      SPACE
      PRHEX(ITADR)
REPEAT 
            PRINTSTRING(" FE"); WRITE(FE NO, 1)
         FINISH 
!
         NEWLINE
!
         IF  REASON = BATCH C 
         THEN  BATCH PROCESSES = BATCH PROCESSES + 1 C 
         ELSE  J = LISTMOD(USER, 0, 1)
!
         CUR = FREEHD
         FREEHD = PROCLIST(CUR)_LINK
         PROCLIST(CUR)_LINK = LIVEHD
         BACKHD = CUR IF  BACKHD = ENDLIST
         PROCLIST(CUR)_BLNK = ENDLIST
         PROCLIST(LIVEHD)_BLNK = CUR
         LIVEHD = CUR
         PROCLIST(CUR)_USER = USER

         SET ITADDR(ITADDR, J, CONSOLE, PROCLIST(CUR)_TCPNAME)
         PROCLIST(CUR)_NODENO = J
         PROCLIST(CUR)_CONSOLE1 = CONSOLE >> 8
         PROCLIST(CUR)_CONSOLE2 = CONSOLE & 255
         PROCLIST(CUR)_FSYS = FSYS
         PROCLIST(CUR)_INVOC = INVOC
         PROCLIST(CUR)_PROTOCOL = PROTOCOL
         PROCLIST(CUR)_REASON = REASON
         PROCLIST(CUR)_ID = STREAM ID; ! or batch id for batch
         PROCLIST(CUR)_PROCESS = PROCNO
         PROCLIST(CUR)_PREV WARN = 0
         PROCLIST(CUR)_LOGKEY = 0
         UINF_PSLOT = CUR

         IF  ISESSM > 0 START 
            J = COM_SECSFRMN // 60 + ISESSM
            J = ISESSM IF  J > 23*60+54
            ISESSM = J
         FINISH 
         PROCLIST(CUR)_SESSEND=ISESSM; ! mins from midnight

         J = 0
         J = 1 IF  REASON=INTER AND  FUNDSI<=0; ! liable to pre-emption
         PROCLIST(CUR)_PRE EMPT=J
!
         J = DDISCONNECTI(FILE, FSYS, 0)
         RESULT  = 0
      FINISH 

      J=DDISCONNECTI(FILE,FSYS,0)
      J = STARTRESULT + 100
      FILE = ""
      FN = 8
WORK FAIL:
      DERR2(FILE, FN, J)
      USEFIELD=USEFIELD - 1
      DESTROY TEMPS(USER,SUFFIX,FSYS)
      RESULT  = J; ! WORK FILE FAILURE
END ; ! STARTP

!-----------------------------------------------------------------------

ROUTINE  ATTOP(STRINGNAME  S)
STRING (255) T
      T="**OPER ".TOSTRING(7).TIME
      LENGTH(T)=LENGTH(T)-3
      S<-T.": ".S
END ; ! ATTOP

!-----------------------------------------------------------------------

ROUTINE  PROMPT(INTEGER  CNSL,STRING (23) TXT)
RECORD (PARMF)P
      WRSS("Prompt ", TXT) AND  RETURN  IF  CNSL = 0
      P_DEST= (CNSL & (-256)) ! 8
      P_SRCE=DIRECT SYNC1 DEST ! 19
      P_S<-TXT
      DPONI(P)
END ; ! PROMPT

!-----------------------------------------------------------------------

INTEGERFN  BROADMSG(STRING (255) S)
! This routine creates and connects the broadcast file, returning the
! startbyte and byte after last byte offsets of the text after placing in
! in the file. Result zero is an error.
!  (LH 16 bits=start, RH 16= end)>
INTEGER  FSYS,SEG,GAP,DIR ACR,J
      *LSS_(1);                        ! PSR
      *ST_J
      DIR ACR=(J>>20)&15
      ! Edinburgh Subsystem would like a NL in the broadcast file
      IF  LENGTH(S)<255 THEN  S=S."
"
      SEG=0; GAP=0
      FSYS=-1
      J = CREATE AND CONNECT("VOLUMS.BROADCAST", FSYS, 4, C 
            EEP8, 11, DIRACR << 4 ! 15, SEG, GAP)
      -> NOBR UNLESS  J = 0
      RESULT =STRING TO FILE(LENGTH(S),ADDR(S)+1,SEG<<18)
NOBR:
      DOPER2("BROADCAST FAIL")
      RESULT =0
END ; ! BROADMSG

!-----------------------------------------------------------------------

ROUTINE  SLASH83(STRING (255) S,T,INTEGER  FOR FSYS,SHUTDOWN)
! SENDS AN ASYNC TXT MSG TO ALL PROCESSES
! (EXCEPT EXECUTIVE PROCESSES), BUT IF S IS "ST",
! IT GIVES THE SPECIAL DIR MSG "ST".

! Parameter SHUTDOWN is zero for a broadcast message (called from XOPER,
! D/BROADCAST), and 1 for (all partial-type messages and special director
! messages relating to same) except for the 15-minute warning, before the
! PENDG words have been moved to the "initiated" words, when it has value 2 (call
! from routine WARN).


! "Part close" broadcast messages are the warning and shutdown messages
! required to close down one or more FEs or FSYSes. The SHUTDOWN parameter
! is non-zero when this routine is called from the shutdown warning sequence.
! The second parameter, T, is relevant only when a partial shutdown is
! required, and is the same as string S except that the word "Partial"
! preceeds the essential text.

INTEGER  DEST, DACT, ORD DACT, PROC, PTRS, J, FSYS, FE NO, NODE NO
INTEGER  ORD PTRS, PARTIAL PTRS, PARTIAL SHUTDOWN, CLO TCPS, OMIT
RECORD (PROCDATF)NAME  PROCE
RECORD (PARMF) P
STRING (MAXTCPNAME) TCPNAME
      WRSS("SLASH83/ ", S)
      RETURN  IF  S = ""
      P = 0
      ORD PTRS=0

      PARTIAL SHUTDOWN = 0
      CLO TCPS=(¬(OPEN TO("", TCPINITG, 1, 0)))
      IF  SHUTDOWN # 0 # (CLO FES ! CLO NODES ! CLO TCPS ! CLO FSYS) C 
      THEN  PARTIAL SHUTDOWN = 1

      IF  S = "ST" START 
         ORD DACT = DIRDACT
         P_P1 = M'ST ' ! (2<<24)
      FINISH  ELSE  START 
         ORD DACT = TXTDACT
         ORD PTRS = BROADMSG(S)
         RETURN  IF  ORD PTRS = 0
         IF  PARTIAL SHUTDOWN # 0 START 
            PARTIAL PTRS = BROADMSG(T)
            RETURN  IF  PARTIAL PTRS = 0
         FINISH 
      FINISH 

      P_SRCE = 31; ! Any replies to DIRLOG

      J = LIVEHD
      WHILE  J # ENDLIST CYCLE 
         PTRS = ORD PTRS
         DACT = ORD DACT
         OMIT = 0
         PROCE == PROCLIST(J)
         PROC = PROCE_PROCESS
         FE NO = (PROCE_ID >> 16) & 255
         NODE NO = PROCE_NODENO
         TCPNAME  <-  PROCE_TCPNAME
         FSYS = PROCE_FSYS
         IF   PROC > TOPEXEC+2 AND  PROCE_REASON # BATCH AND   C 
              (FOR FSYS < 0 OR  FSYS = FOR FSYS) START 
               ! In a partial shutdown:
               ! If the process is not on an FE or node which is closing and its
               ! index is not on a disc which is closing, then it receives
               ! the "Partial ..." message instead of the "(Normal) Close..."
               ! message.

               ! In addition, for the 15-minute warning, when the close is not
               ! "initiated", SHUTDOWN is set 2 in the call from rt WARN and we
               ! suppress the "Reconfiguration" message if it is not going to
               ! concern the user. In this case we have to test the PENDG variables.

               IF  SHUTDOWN=2 AND  C 
                  PENDG CLO FES & (1 << FE NO) = 0 AND   C 
                  PENDG CLO NODES & (1 << NODE NO) = 0 AND   C 
                  OPEN TO(TCPNAME, TCPPENDG, 1, 0) # 0 {i.e. TCP not closing}   AND  C 
                  BIT STATUS(PENDG FCLOSING,-1,FSYS) = 0 THEN  OMIT=1

               ! (Note PARTIAL SHUTDOWN should be ZERO if SHUTDOWN=2, because
               !  the PENDG variables are moved to the initiated variables
               !  only after the 15-minute warning. I guarantee that I won't
               !  understand ANY of this the next time I read it).

               IF  PARTIAL SHUTDOWN#0 ANDC 
                  CLO FES & (1 << FE NO) = 0 ANDC 
                  CLO NODES & (1 << NODE NO) = 0 ANDC 
                  OPEN TO(TCPNAME, TCPINITG, 1, 0) # 0 ANDC 
                  BIT STATUS(FCLOSING, -1, FSYS) = 0 C 
               START 
                  PTRS = PARTIAL PTRS
                  DACT = CLODACT
               FINISH 
               PRINTSTRING("broadcast ")
               WRSNT(PROCE_USER, FSYS, 5)

               IF  PTRS = ORD PTRS C 
               THEN  PRINTSTRING(" Ordinary ") C 
               ELSE  PRINTSTRING(" Partial  ")

               DEST = (COM_ASYNCDEST + PROC)<<16 + DACT
               PRHEX(DEST)
               IF  PROCE_REASON=INTER START 
                  WRSNT(" FE no", FE NO, 5)
                  WRSNT("  Node no", NODE NO, 5)
               FINISH 

               IF  OMIT#0 START 
                  WRS(" omitted") 
               FINISH  ELSE  START 
                  NEWLINE
                  P_DEST = X'3F0000' ! (PROC & 7); ! Service 3F sends message
                                                   ! to P_P6 after DACT secs delay
                                                   ! In this case 0-7 secs
                  P_P3 = PTRS
                  P_P6 = DEST
                  DPONI(P)
               FINISH 
         FINISH 
         J = PROCE_LINK
      REPEAT 
END ; ! SLASH83

!-----------------------------------------------------------------------

ROUTINE  KICK AT(INTEGER  SECONDS,DACT,EVERY OR AFTER,INFO)
! Param EVERY OR AFTER should be  1  for every SECONDS seconds, or CANCEL
!                             or  2  for after SECONDS second.
! If SECONDS is -1, then it's a "cancel" request.
RECORD (PARMF)P
      P = 0
      P_DEST = X'A0000' ! EVERY OR AFTER
      P_SRCE = 26; ! dummy dact in process1
      P_P1 = DIRECT SYNC1 DEST ! DACT
      P_P2 = SECONDS
      P_P3 = INFO        {returned in P_P1}
      DPONI(P)
END ; ! KICK AT

!-----------------------------------------------------------------------

ROUTINE  WARN(STRING (6) USER,INTEGER  INVOC,FSYS,MINS TO GO)
RECORD (PARMF)P
STRING (63) S,T, IN X MINS
INTEGER  J,K,M,PENDG CLOTCPS,SHUTPARAM
      PENDG CLOTCPS{set non-zero if any closing} = (¬(OPEN TO ("", TCPPENDG, 1, 0)))
      SHUTPARAM=1
      CYCLE  J=0,1,3
         M=WARN AT(J)
         IF  MINS TO GO=M START 
            IN X MINS = " in " . ITOS(M-1) . " minutes"
            S = "Close"
            IN X MINS = " imminent" AND  S = "Shutdown" IF  J = 3

            IF  USER = "" START 
               IF  J = 0 # (PENDG CLOFSYS ! PENDG CLONODES ! PENDG CLOTCPS ! PENDG CLOFES) C 
               THEN  S = "Closing" AND  SHUTPARAM=2

               ! SHUTPARAM is going to be used inSLASH83 to suppress this message
               ! if it doesn't concern the user.

               S = S . IN X MINS
               ATTOP(S)
               ! Prepare a second message preceeded by "Partial", in case
               ! it should be required for a partial closedown.
               T = "Partial close (Fsys "
               CYCLE  K = 0, 1, 99
                  IF  BIT STATUS(PENDG FCLOSING, -1, K) # 0 C 
                  THEN  T = T . ITOS(K) . ","
               REPEAT 
               CHARNO(T, LENGTH(T)) = ')'
               T = T . IN X MINS
               ATTOP(T)
               SLASH83(S, T, -1, SHUTPARAM)
            FINISH  ELSE  START 
               S = "End-of-session" . IN X MINS
               ATTOP(S)
               P_DEST = X'FFFF0016'
               K = TXTMESS(USER, P, 0, INVOC, LENGTH(S), ADDR(S)+1, FSYS, 0)
            FINISH 
         FINISH 
      REPEAT 
END ; ! WARN

!-----------------------------------------------------------------------

ROUTINE  CHECKWARN(RECORD (PROCDATF)NAME  P,INTEGER  NOW MINS 0000, C 
   SYS MINS TO GO)
! The point of this routine is as follows
!     1. To make sure that the 15, 5, 2 and 1-minute warnings are always
!        given (even though thie rt is being called only v. approx. at one
!        minute intervals.
!     2. To prevent proc-close warnings being given while system-close
!        warnings are in force (last 10 mins of sequence).
!     3. To enable a process to continue for at least 5 more mins if a system
!        close-time is withdrawn.
INTEGER  MINS, J, W
INTEGERNAME  LAST WARN
      LAST WARN == P_PREV WARN
      IF  P_SESSEND<NOW MINS 0000 C 
      THEN  P_SESSEND = NOW MINS 0000 + 10 AND  LAST WARN=0; ! minutes
      IF  P_SESSEND>=23*60+55 C 
      THEN  P_SESSEND=0010 AND  LAST WARN=0;             ! 0010 hrs
      MINS=P_SESSEND - NOW MINS 0000
      ! Cancel if we've got to last 10 mins of session
      IF  MINS>SYS MINS TO GO - 10 THEN  P_SESSEND=0 AND  RETURN 
      CYCLE  J = 0, 1, 3
         W = WARN AT(J)
         MINS = W IF  MINS < W < LAST WARN
      REPEAT 
      IF  LAST WARN=0 OR  LAST WARN>MINS START 
         WARN(P_USER,P_INVOC,P_FSYS,MINS)
         LAST WARN=MINS
      FINISH 
      IF  MINS=0 START 
         J = ASYNC MSG(P_USER, P_INVOC, DIRDACT, M'VST' ! (3<<24), 0)
         P_SESSEND=0
      FINISH 
END ; ! CHECKWARN

!-----------------------------------------------------------------------

ROUTINE  CLEAR ALL PARTIAL        { Nodes and TCPs remain closed by virtue     }
                                  { of the variables NODES CLOSED, TCPS CLOSED.}
                                  { the latter represented by result from OPENTO}
                                  { These must                                 }
INTEGER  J                        { be cleared manually, i.e. by D/OPEN NODE   }
      CLO FES=0; CLO FSYS=0       { after the event.                           }
      CLO NODES=0
      PENDG CLO FES=0; PENDG CLO FSYS=0
      PENDG CLO NODES=0
      J=OPEN TO("", TCPINITG!TCPPENDG, 2, 0)    {clear pendg & initd entries}
      J=BIT STATUS(FCLOSING,0,-1)
      J=BIT STATUS(PENDG FCLOSING,0,-1)
      DISPLAY VSNS
END ; ! CLEAR ALL PARTIAL

!-----------------------------------------------------------------------

ROUTINE  ADVISE EXEC(INTEGER  FSYS)
CONSTBYTEINTEGERARRAY  EXECDACTS(0:TOPEXEC)=26,58,27,58
INTEGER  I,J
RECORD (PARMF)P
      CYCLE  I=TOPEXEC,-1,0
         P=0
         P_DEST=X'FFFF0000' ! EXECDACTS(I)
         P_P1=FSYS
         IF  EXEC(I)="SPOOLR" START 
            P_P1=1; ! meaning 'FSYS'
            P_P2=1; ! closing now
            P_P3=FSYS
         FINISH 
         J=DPON3I(EXEC(I),P,0,SYNC1 TYPE,PON AND CONTINUE)
         ! 
         ! also an async message to set the Director variable FSYS WARN
         P=0
         P_DEST=X'FFFF0000' ! CLODACT
         P_P1=0
         J=DPON3I(EXEC(I),P,0,ASYNC TYPE,PON AND CONTINUE)
      REPEAT 
END ; ! ADVISE EXEC

!-----------------------------------------------------------------------

ROUTINE  STOP FSYS BATCH
!
! Sends the special Director CLOSE MSG to non-interactive jobs. Effect
! at the recipient process is to EMPTY DVM if the process has a non-zero
! usecount for any closing fsys, THEN (to stop the process if the counts
! have not all come to zero) ELSE ( to take no further action if all
! counts have come to zero)
INTEGER  DEST, PROC, J
RECORD (PROCDATF)NAME  PROCE
RECORD (PARMF)P
      P = 0
      P_P1 = M'ST ' ! (2<<24)

      J = LIVEHD
      WHILE  J # ENDLIST CYCLE 
         PROCE == PROCLIST(J)
         PROC = PROCE_PROCESS
         IF  PROC >= 5 AND  PROCE_REASON # INTER START 
             WRS3N("Fclosing: batch job ", PROCE_USER, " stopped", 0)
            DEST = (COM_ASYNCDEST + PROC)<<16 + CLODACT
            P_DEST = X'3F0000' ! (PROC & 7); ! Service 3F sends message
                                             ! to P_P6 after DACT secs delay
                                             ! In this case 0-7 secs
            P_P6 = DEST
            DPONI(P)
         FINISH 
         J = PROCE_LINK
      REPEAT 
END ; ! STOP FSYS BATCH

!-----------------------------------------------------------------------
OWNINTEGER  AUTO STATE=0

ROUTINE  AUTO CLOSE(INTEGER  TIM, DACT)
! Parameter TIM is the decimal number representing the close time
! e.g. 1500 to represent 15.00 hrs.

! Values of AUTO STATE have the following meanings
!     0   no auto-close currently set
!     1  close time set and at least 6 mins away
!     2  close time <6 mins away
!     3  final sequence. Leaves this state after "Shutdown imminent" warning
!     4  final sequence. Now give 83/ST
!     5  fianl sequence. Give 83/ST again
!     6  final sequence. Now check 3 processes left. Revert to state 0 if partial close.
!   > 7  final sequence. Now stop processes 1,2 and 3.

OWNINTEGER  TIMES CHECKED=0,PREVMINS TO GO=100000
INTEGER  RES, H, M, FSYS, CSAV, CUR, ENTAD, CLO TCPS, DAP
INTEGER  NOW MINS 0000,CLOSE MINS 0000,NEXT KICK,J,MINS TO GO
INTEGERARRAY  STATE(1 : 2)
RECORD (PARMF) PP
RECORD (PARMF)NAME  P
RECORD (PROCDATF)NAME  PROCE
RECORD (DDTF)NAME  DDT
STRING (3) HH,MM,SS
STRING (5)HHMM
SWITCH  FINAL SEQ(0:10)
SWITCH  AUTO(25:32)
!
!
OWNSTRING (15)LAST DAP STATE = ""
STRING (15)DAP STATE
!
CONSTSTRING (5)ARRAY  DAPTXT(0:3, 1:2) = C 
"OFF  ",
"ON   ",
"STORE",
"RUN  ",
"  OFF",
"   ON",
"STORE",
"  RUN"
!
!
      RETURN  UNLESS  0<=TIM<=2400
      NEXT KICK=0
      TIME -> HH . (".") . MM . (".") . SS
      NOW MINS 0000 = STOI(HH)*60 + STOI(MM)
      -> AUTO(DACT)

AUTO(26):      ! As 27, but executive processes not to be stopped.
               ! D/CLOSEUSERS hhmm
      ! SUPPRESS EXEC STOP=1
AUTO(27):      ! D/CLOSE n
      ! SUPPRESS EXEC STOP=0
      SUPPRESS EXEC STOP=27-DACT
      IF  TIM=0 START 
         SUPPRESS EXEC STOP=0
         COM_SECSTOCD=0
         AUTO STATE=0
         PREV MINS TO GO=100000
         NEW CLOSE TIME=-1
         PLACE("                ", 0, 1, 24, 0)
         RETURN 
      FINISH 

AUTO(28):      ! SET Close time
      AUTO STATE=1

      H = TIM // 100
      M = TIM - 100*H
      HHMM = ITOS(M)
      HHMM = "0" . HHMM IF  M < 10
      HHMM = ITOS(H) . "." . HHMM
      HHMM = "0" . HHMM IF  H < 10

      PLACE(HHMM, 0, 1, 35, 0)
      NEW CLOSE TIME=TIM
      CLOSE MINS 0000=H*60 + M
      MINS TO GO=CLOSE MINS 0000 - NOW MINS 0000
      IF  MINS TO GO<-1 THEN  MINS TO GO=MINS TO GO + 24*60
      COM_SECSTOCD=MINS TO GO*60 - 45
      -> KICK OUT

AUTO(29):      ! COMES BACK HERE EACH MINUTE
      NOW MINS 0000 = NOW MINS 0000 + 1
      MINS TO GO = COM_SECSTOCD // 60
      MINS TO GO = 24*60 IF  MINS TO GO = 0
!
!
      IF  5 <= COM_OCPTYPE <= 6 START 
         CYCLE  DAP = 1, 1, 2
            STATE(DAP) = COM_CDR(DAP)_DAP STATE
            STATE(DAP) = 1 IF  STATE(DAP) = 2; ! 'alloc' mapped to 'on'
            STATE(DAP) = 2 IF  STATE(DAP) > 3; ! 'store'
         REPEAT 
!
         DAP STATE = DAPTXT(STATE(1), 1) . " DAP " . DAPTXT(STATE(2), 2)
!
         UNLESS  DAP STATE = LAST DAP STATE START 
            LAST DAP STATE = DAP STATE
            PLACE(DAPSTATE, 0, 4, 25, 0)
         FINISH 
      FINISH 
!
! the cycle which follows is to deal with individual process closing (funds
! scheme).
      J = LIVEHD
      WHILE  J # ENDLIST CYCLE 
         IF  PROCLIST(J)_SESSEND # 0 C 
         THEN  CHECKWARN(PROCLIST(J), NOW MINS 0000, MINS TO GO) 
         J = PROCLIST(J)_LINK
      REPEAT 

      IF  AUTO STATE#1 THEN  RETURN 
      MINS TO GO=COM_SECSTOCD//60 + 1
      IF  MINS TO GO<=16 AND  PREV MINS TO GO>16 THEN  WARN("",0,0,16)

      PREV MINS TO GO=MINS TO GO
      IF  MINS TO GO<=7 START 
         NEXT KICK=10; ! seconds
         AUTO STATE=2

         ! Initiate the partial close sequence if required

         CLO FES=PENDG CLO FES
         CLO FSYS=PENDG CLO FSYS
         CLO NODES=PENDG CLO NODES
         J=OPEN TO("", 0, 4, 0)      {move state from pend->init}

         CYCLE  FSYS=99,-1,0
            IF  BIT STATUS(PENDG FCLOSING,-1,FSYS)#0 START 
               J=SET CLOSING BIT(FSYS);       ! set bit in disc table
               J=BIT STATUS(FCLOSING,1,FSYS); ! set bit in final array
               ! And we must tell the executive processes:
               ! VOLUMS:   DACT=26, P_P1=FSYS
               ! SPOOLR:   DACT=58, P_P1=FSYS
               ! MAILER:
               ADVISE EXEC(FSYS)
            FINISH 
         REPEAT 
         DISPLAY VSNS
      FINISH 
      -> KICK OUT

AUTO(30):      ! shorter kicks in final sequence
      -> FINAL SEQ(AUTO STATE)
FINAL SEQ(2):
      NEXT KICK=10
      MINS TO GO=COM_SECSTOCD//60+1
      IF  MINS TO GO=PREV MINS TO GO THEN  -> KICK OUT
      PREV MINS TO GO=MINS TO GO
      IF  MINS TO GO<=2 START 
         FES CLOSED=FES CLOSED ! CLO FES
         NODES CLOSED=NODES CLOSED ! CLO NODES
         CLO TCPS=(¬(OPEN TO("", TCPINITG, 1, 0))) {ie set if any initiated}
         J=OPEN TO("", 0, 5, 0)     {Set all TCPs closed which are to be closed}
         J=BIT STATUS(OPSTAT,0,-1) IF   C 
            PENDG CLO FSYS ! PENDG CLO NODES ! CLO TCPS ! PENDG CLO FES = 0; ! ie only for Full Close
         STOP FSYS BATCH IF  CLOFSYS # 0; ! special close message to
                                        ! non-interactive jobs if partial
                                        ! close/fsys closing
      FINISH 
      WARN("",0,0,MINS TO GO)
      DISPLAY VSNS
      IF  MINS TO GO<=1 THEN  AUTO STATE=3
      -> KICK OUT

FINAL SEQ(3):
      NEXT KICK=15;                    ! seconds
      AUTO STATE=4
      -> KICK OUT

AUTO(25):      ! D/CLOSEDOWN
      SUPPRESS EXEC STOP=0
      J=BIT STATUS(OPSTAT,0,-1)
      DISPLAY VSNS
      PLACE("     ", 0, 1, 35, 0); ! clear hh.mm time field on display
      CLEAR ALL PARTIAL

FINAL SEQ(4):
      SLASH83("ST","",-1,1)
      IF  COM_USERS<=TOPEXEC+2 THEN  -> SKIPSL
      NEXT KICK=15
      AUTO STATE=5
      -> KICK OUT

FINAL SEQ(5):
      IF  COM_USERS<=TOPEXEC+2 THEN  -> SKIPSL
      SLASH83("ST","",-1,1); ! repeat this for those who refuse to go because
                             ! they are awaiting a DISABLE reply.
      AUTO STATE=6
      NEXT KICK=15;          ! 5 secs wasn't enough to let a process stop prop-
                             ! erly before CLO FES is cleared.
      -> KICK OUT

FINAL SEQ(6):
SKIPSL:
      TIMES CHECKED=0
      CLO TCPS=(¬(OPEN TO("", TCPINITG, 1, 0)))
      CSAV=CLO FES ! CLO FSYS ! CLO NODES ! CLO TCPS
      IF  CSAV#0 START ; ! Partial close in progress

         ! See whether Closing FSYSes have in fact closed. If not, pon off delayed
         ! messages containing the DDT LVN entry, and when received back we just
         ! set the DDT fields (LVN and CONCOUNT) to "off-line"

         FOR  FSYS=99, -1, 0 CYCLE 
            IF  BIT STATUS(FCLOSING, -1, FSYS)#0 START 
               J=DDT ENTRY(ENTAD, FSYS)
               CONTINUE  IF  J#0
               DDT==RECORD(ENTAD)

               ! Top bit in DLVN means "Not available (not Fchecked)" when set.
               ! Nex bit means "FSYS closing" when set.

               IF  DDT_CONCOUNT#0 OR  DDT_DLVN>=0 {i.e. not yet "not available"} C 
                  THEN  KICK AT(90, 13 {DACT in rt PROCESS1}, AFTER, DDT_DLVN)
            FINISH 
         REPEAT 
      FINISH 

      CLEAR ALL PARTIAL

      IF  CSAV#0 START 
         AUTO CLOSE(0,27)
         NEXT KICK=0
         EMPTY DVM; ! just in case there are no processes stopping to get it
                    ! done in rt PROCESS STOPS.
         -> KICK OUT
      FINISH 
      NEXT KICK=1;                     ! seconds
      AUTO STATE=7
      IF  COM_USERS>TOPEXEC+2 THEN  -> KICK OUT
      ! else press on

FINAL SEQ(7):      ! Check that all user processes have stopped
      NEXT KICK=1
      IF  COM_USERS>TOPEXEC+2 AND  TIMES CHECKED<15 START 
         TIMES CHECKED=TIMES CHECKED + 1
         -> KICK OUT
      FINISH 
      IF  SUPPRESS EXEC STOP#0 THEN  -> SUPMON
      TIMES CHECKED = 0
      AUTO STATE = 8
      DISCONNECT FE(-1)

FINAL SEQ(8):      ! Check that FEPs have been disconnected
      NEXT KICK=1
      IF  FES FOUND#0 AND  TIMES CHECKED<15 START 
         TIMES CHECKED=TIMES CHECKED + 1
         -> KICK OUT
      FINISH 

SUPMON:
      P == RECORD(DIROUTP0)

      P = 0; ! discs
      P_DEST = X'200006'
      P_P1 = -1
      *OUT_11

      IF  COM_SFCK > 0 START ; ! drums, if there are any
         P = 0
         P_DEST = X'280004'
         DPONI(P)
      FINISH 

      P = 0; ! sup monitoring
      P_DEST = X'90001'
      *OUT_6

      P = 0
      P_DEST = X'390000'
      *OUT_6

      AUTO STATE=9
      IF  SUPPRESS EXEC STOP#0 THEN  AUTO CLOSE(0,27)
      -> KICK OUT

FINAL SEQ(9):     ! STOP THE EXECUTIVE PROCESSES

      CYCLE  J = 0, 1, TOPEXEC
          CUR=LIVEHD
          WHILE  CUR#ENDLIST CYCLE 
             PROCE==PROCLIST(CUR)
             IF  PROCE_USER=EXEC(J) AND  PROCE_REASON=OPERC START 
                PP = 0
                PP_DEST = X'FFFF0014'
                STRING(ADDR(PP_P1)) = "STOP"
                RES = DPON3I(EXEC(J), PP, PROCE_INVOC, SYNC1TYPE, C 
                   PON AND CONTINUE)
                IF  RES # 0 THEN  DOPER2("STOP ".EXEC(J)." FLAG ". C 
                   ITOS(RES))
             FINISH 
             CUR=PROCE_LINK
          REPEAT 
      REPEAT 
      NEXT KICK=1;                     ! seconds
      TIMES CHECKED=0
      AUTO STATE=10

FINAL SEQ(10):      ! Check that all processes have stopped
      NEXT KICK=1
      IF  COM_USERS>1 AND  TIMES CHECKED<15 START 
         TIMES CHECKED=TIMES CHECKED + 1
         -> KICK OUT
      FINISH 
      KILL("",0,0)
      PP = 0
      J = NEWPAGE CHAR(PP)
      RETURN 
FINAL SEQ(0):
FINAL SEQ(1):
KICK OUT:
      IF  NEXT KICK#0 THEN  KICK AT(NEXT KICK,30,AFTER,0)
END ; ! AUTO CLOSE

!-------------------------------------------------------------------------------

ROUTINE  POKE SPOOLR(INTEGER  ACT,NUM,SET)

! In case it isn't obvious, this routine is used only for advising to SPOOLR
! the changes to the pending partial closure statuses. After the partial
! closure becomes "initiated", the final situation is delivered to SPOOLR
! via the SPOOLR FES routine, called from AUTO CLOSE.


!       ACT = 26        D/CLOSE
!             61        D/CLOSE FSYS
!             70        D/CLOSE FE(P)
!             80        D/CLOSE NODE

!       SET =  0        unset partial close for FSYS/FE/NODE
!              1        set partial close   for FSYS/FE/NODE

! Here is the spec for ACT 58 messages to SPOOLR:

!       p1  =  0        FE
!              1        FSYS

!       P2  =  0        close in COM_SECSTOCD seconds. This message is sent
!                       (only) when operator does D/CLOSE hhmm, D/CLOSE FE hhmm ,
!                       D/CLOSE FSYS hhmm or D/CLOSE NODE hhmm.

!              1        close now. This message is sent when the partial close
!                       becomes irrevocable (7 mins before stated time). One
!                       message for each FSYS and for each FE, and one only if
!                       ANY node is closing.

!              2        withdraw close. As for P2=0, this message is sent only
!                       when operators withdraw a close (total or partial).

!       P3  =           the FE or FSYS number (-1 for D/CLOSE).
!                       Not relevant for Node closure.

! Here are some notes about partial close of a Node.
! We treat a Node closure as a closure of all FEs that we know about.
! As is the case for DIRECT itself, SPOOLR will require to be opened up
! "manually" when the node is put on-line again.

! In this routine, we want to tell SPOOLR what's changed, and in the case of
! changing the close-status of FEs or Nodes, the following constraints apply:

!  1.  Do not withdraw a CLOSE FE for a CLOSE NODE if a CLOSE FE is still
!      pending for that FE.
!  2.  Do not withdraw a CLOSE FE for a CLOSE FE if a CLOSE NODE is still
!      pending.
!  3.  Do not advise a CLOSE FE if a CLOSE NODE is pending.
!  4.  Do not advise a CLOSE FE for a CLOSE NODE if a CLOSE FE is pending for
!      that FE.
! But in practice, forget 3 & 4. It's not important if SPOOLR gets one twice.

INTEGER  J
RECORD  (PARMF)P
      RETURN  UNLESS  ACT=26 OR  ACT=61 OR  ACT=70 OR  ACT=80
      NUM=-1 IF  ACT=26

      IF  SET=0 { withdrawing } START 
         RETURN  IF  (ACT=80 AND  PENDG CLO FES#0  { constraint 1 above }) ORC 
                     (ACT=70 AND  PENDG CLO NODES#0 {           2 above })
         ! As a consequence of this "out", the operators may have to tell
         ! SPOOLR explicitly that the relevant FE(s) are up, later on.
      FINISH 

      { We do not worry about giving extra "closing" msgs to SPOOLR for
      { FEs which may already have been notified as closing.

      IF  ACT=80 START 
         FOR  J=TOP FENO, -1, 0 CYCLE 
            IF  FES FOUND&(1<<J)#0 THEN  POKE SPOOLR(70, J, SET)
         REPEAT 
         RETURN 
      FINISH 

      P=0
      P_DEST=X'FFFF0000' ! 58
      IF  ACT=61 OR  ACT=26 THEN  P_P1=1  { FSYS. Treat full close      }
                                          { (ACT 26) as full FSYS close.}
      IF  SET=0 THEN  P_P2=2; ! Withdraw close
      P_P3=NUM
      J=DPON3I("SPOOLR",P,0,SYNC1 TYPE,PON AND CONTINUE)
END ; ! POKE SPOOLR

!-------------------------------------------------------------------------------

routine  ertecheck(integer  cnsl, dfctrunksac, fsys1, fsys2)

!  Checks that the discs are on the specified dfcs ('dfc' values:
!  0 = A, 1 = B etc, 'trunk' is the appropriate trunk number).
!  Also checks that the dfc is on the specified sac.
!
!  Prints oper messages otherwise.

   record  (ddtf) name  ddt
   integer  i, flag, pt, ddtad, la, fsys
   integer  dfc, trunk, sac
   string  (7) s

   dfc = dfctrunksac >> 8
   trunk = (dfctrunksac >> 4) & 15
   sac = dfctrunksac & 15
!
   la = log action; ! save it
   log action = la!log; ! include MAINLOG
   flag = 0
   fsys = fsys1
   for  i = 1, 1, 2 cycle 
      s = "fsys ".itos(fsys)
      if  0#ddtentry(ddtad, fsys) then  c 
         flag = 1 and  oper(cnsl, s." missing!!") else  start 
         ddt == record(ddtad)
         pt = (ddt_pts>>4)&255
         if  pt&15#trunk then  c 
            oper(cnsl, "Put ".s." on DFC ".tostring(dfc+'A')) and  flag = 1
         if  pt>>4#sac then  c 
            oper(cnsl, "Put DFC ".tostring(dfc+'A')." on SAC " c 
            . tostring(sac+'0')) and  flag = 1
      finish 
      fsys = fsys2
      exit  if  fsys<0
   repeat 
   if  flag=0 then  oper(cnsl, "DFC ok") else  c 
      oper(cnsl, "Reconfig required!!!")
   log action = la; ! revert to original setting
end ; ! ertecheck
!
!-----------------------------------------------------------------------
!
ROUTINE  XOPER(INTEGER  CNSL, STRING (41)IS)
INTEGER  P,NPS,DEFAULTS,BITS2,SPACES THROWN,PENDG CLO TCPS,CLO TCPS
INTEGER  PARS FOUND
OWNINTEGER  TYPE
INTEGER  N1,N2,N3,N4
INTEGERARRAY  N(0:4)
STRING (41) S0,S1,S2,S3,S4,USER,FILE, W41
STRING (2)W2
STRING (8)W8
STRING (41)ARRAY  S(0:4)
!----------------------------------------------------------------------
SWITCH  OP(1:TOPM)
!--------- OWNS AND SWITCH FOR CARRYING OVER DATA WHERE TWO ------------
!--------- OR MORE ENTRIES ARE REQUIRED FOR THE COMMAND ----------------
CONSTINTEGER  TOPCONT = 13
SWITCH  CONTMSG(1:TOPCONT)
CONSTBYTEINTEGERARRAY  CONTP(1:TOPCONT) = 2,2,1,0,2,0,0,3,2,2,2,0,3
!     0  no check  (but only first param checked anyway)
!     1  length = 6
!     2  string 6.11
!     3  null or string 6.11
OWNINTEGER  FSYS1
OWNSTRING (41) USER1,FILE1
!-----------------------------------------------------------------------
OWNSTRING (255) BCASTM
OWNINTEGER  MSTATE=0,CONUSE=-1,ACT
INTEGER  I,J,K,L,MM,LEN
STRING (255) NISS
STRING (41) NIS
INTEGER  YY
RECORD (PARMF)QQ
INTEGER  NNAD
RECORD (HF)NAME  NH
RECORD (NNF)NAME  NN
RECORD (DIRCOMF)NAME  DIRCOM
!
SWITCH  SW71(0 : 9)
CONSTSTRING (8)ARRAY  DAPCOM(0 : 9) = C 
      "?", "LIMIT", "INTER", "LOBATCH0", "HIBATCH0", "LOBATCH1", "HIBATCH1",
      "BUSER0", "BUSER1", "IUSER"
!
CONSTINTEGERARRAY  DAP DEFAULT(1 : 6) = C 
      1, 600, 0, 3600, 0, 3600
!
CONSTSTRING (7)ARRAY  MNEMO(0:7) = "VOLUMS", "MAILER", "SUBSYS",
         "", "STUDENT", "", "SPOOLR", "FTRANS"
!
CONSTSTRING (6)ARRAY  DAY(1:7) = C 
            "Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur"
!
!-----------------------------------------------------------------------
!
ROUTINE  SHOW FES(STRING (6) USER, INTEGER  CNSL)
INTEGER  J 
STRING (7) S
      J=LIVEHD
      WHILE  J#ENDLIST CYCLE 
         IF  PROCLIST(J)_USER=USER START 
            IF  PROCLIST(J)_REASON=INTER THEN  S="FE ". C 
               ITOS((PROCLIST(J)_ ID>>16)&255) ELSE  S="None"
            OPER(CNSL,USER." proc ".ITOS(PROCLIST(J)_PROCESS)." ".S)
         FINISH 
         J=PROCLIST(J)_LINK
      REPEAT 
END ; ! SHOW FES

!------------------------------------------------------------------------------

ROUTINE  DISPLAY OP STAT(INTEGER  CNSL)
INTEGER  OPEN,CLOSED,J,FSYS,BIT,A,L,X
STRING (255) S,SS,SOTHER,SOPEN,SCLOSED
      OPEN=0; CLOSED=0
      SOPEN=""; SCLOSED=""
      A=ADDR(OPSTAT(0))
      CYCLE  FSYS=0,1,99
         SS = ITOS(FSYS)
         SS = " " . SS WHILE  LENGTH(SS) < 3
         *LDTB_101
         *LDA_A
         *LB_FSYS
         *LSS_(DR +B )
         *ST_BIT
         IF  BIT=0 START 
            CLOSED=CLOSED+1
            SCLOSED<-SCLOSED.SS
            LENGTH(SCLOSED)=250 IF  LENGTH(SCLOSED)>250
         FINISH  ELSE  START 
            OPEN=OPEN+1
            SOPEN<-SOPEN.SS
            LENGTH(SOPEN)=250 IF  LENGTH(SOPEN)>250
         FINISH 
      REPEAT 
      RETURN  IF  OPEN=0 OR  CLOSED=0
      IF  OPEN<CLOSED START 
         S="Open"
         SS=SOPEN
         SOTHER="closed"
      FINISH  ELSE  START 
         S="Closed"
         SS=SCLOSED
         SOTHER="open"
      FINISH 
      OPER(CNSL,S." to file systems: ")
      L=LENGTH(SS)
      J=1
      WHILE  J<LENGTH(SS) CYCLE 
         X=J+20
         IF  X>L THEN  X=L
         OPER(CNSL,FROMSTRING(SS,J,X))
         J=J+21
      REPEAT 
      OPER(CNSL,"All others ".SOTHER)
END ; ! DISPLAY OP STAT

!-----------------------------------------------------------------------

INTEGERFN  GIVE SECTS(STRING (31)USER, FILE, INTEGER  FSYS, CNSL)
STRING (255) S
INTEGER  J,DD,N
RECORDFORMAT  AF(INTEGER  SECTSI,NSECTS,LASTSECT,SP,  C 
      INTEGERARRAY  DA(0:255))
RECORD  (AF)A
      S=""
      J=DGETDA(USER,FILE,FSYS,ADDR(A))
      IF  J#0 THEN  RESULT =J
      N=A_NSECTS
      J=0
      WHILE  J<N CYCLE 
         DD=A_DA(J)
         S=S." ".HTOS(DD,4)
         J=J+1
      REPEAT 
      OPER(CNSL,S)
      RESULT =0
END ;                            ! GIVE SECTS

!-----------------------------------------------------------------------

ROUTINE  SPCAN(STRINGNAME  S)
INTEGER  CH, PREVIOUS, L, J
! remove leading, trailing and multiple spaces
! remove '10' from end
! remove D/ from start
      RETURN  IF  S = ""
      PREVIOUS = ' '
      L = 0
      CYCLE  J = 1, 1, LENGTH(S)
         CH = CHARNO(S, J)
         L = L + 1 AND  CHARNO(S, L) = CH UNLESS  CH = ' ' = PREVIOUS
         PREVIOUS = CH
      REPEAT 
      L = L - 1 IF  CH = ' '
      L = L - 1 IF  CHARNO(S, L) = 10
      L = 0 IF  L < 0
      LENGTH(S) = L
      IF  S->("D/").S START ; FINISH 
END ; ! SPCAN

!-----------------------------------------------------------------------

INTEGERFN  FF(STRING (39) S)
      IF  S->USER.(".").FILE START 
         IF  LENGTH(USER)=6 AND  0<LENGTH(FILE)<=11 THEN  RESULT =1
      FINISH 
      RESULT =0
END ;                            ! FF

!-----------------------------------------------------------------------

      WRSS("D/", IS)
      IF  MSTATE#0 AND  CNSL#CONUSE  START 
         ! REJECT MESSAGES FROM THER THAN CURRENTLY BUSY CNSL IF THE LATTER
         ! REQUIRES MORE MSGS TO COMPLETE COMMAD, INDICATED BY SETTING CONUSE
         ! EQUAL TO THE CONSOLE NUMBER
         OPER(CNSL,"PROCESS1 IS BUSY")
         -> REPROM
      FINISH 
      SPCAN(IS)
      NIS=IS
! ANALYSE COMMAND, HAVING 3 SHOTS AT THROWING AWAY SPACES IF UNSUCCESSFUL
      SPACES THROWN=0
      WHILE  SPACES THROWN<=3 CYCLE 
         CYCLE  J=1,1,4; S(J)="-1"; REPEAT ; ! SET FOR DEFAULT NUMERICS
         ! SEPARATE AND COUNT PARAMS
         J=0
         S(0)=IS
         J=J+1 WHILE  J<4 AND  S(J)->S(J).(" ").S(J+1)
         PARS FOUND=J
         CONUSE=CNSL
         S0=S(0)
         ! SET THE NUMERICS
         CYCLE  J=1,1,4
            N(J)=STOI(S(J))
         REPEAT 
         N1=N(1); N2=N(2); N3=N(3); N4=N(4)
         S1=S(1); S2=S(2); S3=S(3); S4=S(4)

         IF  MSTATE > 0 START ; ! check param(s) just read
            BITS2 = CONTP(MSTATE)
            IF  BITS2 > 0 START ; ! checking required
               IF  BITS2=1 AND  LENGTH(S0)#6 THEN  -> QQQ
               IF  BITS2=2 AND  FF(S0)=NO THEN  -> QQQ
               IF  BITS2=3 AND  LENGTH(S0)#0 AND  FF(S0)=NO THEN  ->QQQ
            FINISH 
            -> CONTMSG(MSTATE)
         FINISH 

         ! NOW LOOK FOR THE COMMAND
         CYCLE  YY=1,1,TOPM
            W41 = M(YY)
            W8 <- W41; ! first 8 characters ie 'FN-DDDD-'
            W41 -> (W8) . W41
            IF  W41 = S0 START ; ! apparently matches
               W2 <- W8; ! command number
               W8 -> (W2 . "-") . W8; ! parameter descriptor, format dddd-
               ACT = STOI(W2)
               NPS = 0; ! counts how many params allowed
               CYCLE  J = 1, 1, 3
                  BITS2 = CHARNO(W8, J) - '0'
                  EXIT  IF  BITS2 = 0; ! no more params allowed
                  NPS = NPS + 1
                  LEN = LENGTH(S(J))
                  -> QQQ1 IF  BITS2=1 AND  LEN#6
                  -> QQQ1 IF  BITS2=2 AND  N(J)=X'80308030'
                  -> QQQ1 IF  BITS2=3 AND  FF(S(J))=0
                  -> QQQ1 IF  BITS2=4 AND  LEN#4
                  -> QQQ1 IF  BITS2=5 AND  LEN#1
                  -> QQQ1 IF  BITS2=6 AND  LEN#6 AND  N(J)=X'80308030'
                  -> QQQ1 IF  BITS2 = 7 AND  (LEN = 0 OR  LEN > 31)
               REPEAT 
!
               DEFAULTS = 0
               IF  PARS FOUND < NPS START ; ! not enough params
                  -> QQQ1 IF  CHARNO(W8, 4) = '0'; ! defaults not allowed
                  DEFAULTS = 1
               FINISH 
!
               IF  PARS FOUND = NPS OR  DEFAULTS = 1 START 
                  IF  FCHECKPROCS > 0 START ; ! still doing FCHECKs
                     -> WAIT UNLESS  ACT = 12 OR  ACT = 32 OR  ACT = 45
                  FINISH 
                  -> OP(ACT)
               FINISH 
               ! TOO MANY PARAMS APPARENTLY. MAYBE WE GOT THE WRONG
               ! COMMAND SOMEHOW BY HAVING A SPACE SOMEWHERE. IT"S
               ! PROBABLY INVALID, BUT WE GO RUND AGAIN ANYHOS1.
            FINISH ;                ! FOUND
         REPEAT 
QQQ1:
            ! COMMAND NOT FOUND. MAYBE IT HAD A SPACE IN IT.
            IF  IS->IS.(" ").S4 THEN  IS=IS.S4
            SPACES THROWN=SPACES THROWN + 1
      REPEAT 
!-----------------------------------------------------------------------
OP(*):
QQQ:
      OPER(CNSL,NIS." ??")
      -> OUT
WAIT:
      OPER(CNSL, "Wait 'til FCHECKS done")
      -> OUT
PRERR:
      OPER(CNSL,DERRS(J))
      -> OUT
DONE:
      OPER(CNSL,"DONE")
OUT:
      CONUSE=-1
      MSTATE=0
REPROM:
      J = PR SRCE(0)
      IF  J > 0 START 
         CYCLE  I = 1, 1, J
            PROMPT(CNSL, "Direct:") AND  EXIT  IF  CNSL = PR SRCE(I)
         REPEAT 
      FINISH 

      DISPLAY VSNS
POUT:
      MSTATE = 0 IF  CNSL = 0
      RETURN 
OP(1):                                              ! NEWUSER
      J=DNEWUSER(S1,N2,N3)
      -> PRERR
OP(2):                                              ! DELUSER
      J=DDELUSER(S1,N2)
      -> PRERR
OP(3):                               ! ERTECHECK
      ERTECHECK(CNSL, N1, N2, N3)
      -> OUT
OP(4):                                              ! PRM_FILE (IE. PERMIT)
      J=DPERMISSIONI(USER,USER,"",FILE,N2,1,7); ! 1=set EEP, PRM=7=X+W+R
      -> PRERR
OP(5):                               ! OBEY
      PROMPT(CNSL, "Give OBEYFILE name:")
      MSTATE = 11
      -> POUT
CONTMSG(11):
      J = AUTOCOMM(S0, 5)
      -> PRERR
OP(6):                                               ! FSYS_USER(_FSYS)
      ! TO FIND OUT WHAT FSYS A USER IS ON
      J = HINDA(S1,N2,N3, 0);               ! N3 IRRELEVANT
      IF  J=0 START 
         OPER(CNSL,"FSYS ".ITOS(N2))
         J = LIVEHD
         WHILE  J # ENDLIST CYCLE 
            IF  PROCLIST(J)_USER = S1 C 
            THEN  OPER(CNSL, S1." proc ".ITOS(PROCLIST(J)_PROCESS))
            J = PROCLIST(J)_LINK
         REPEAT 
         J = 0
      FINISH 
      -> PRERR
OP(7):                                              ! CONSISTENCY CHECK
      -> QQQ UNLESS  0 <= N1 <= 99
      J = CCK(N1, 0, P)
      DOPER2("Fsys " . ITOS(N1) . " " . ITOS(P) . "% full")
      I = MAP XOP OWNS(8) IF  N1 = COM_SUPLVN
      -> PRERR
OP(8):                                              ! SNOS
      OPER(CNSL,"SLOADED ".ITOS(COM_SUPLVN)." DIRVSN IS ". C 
            ITOS(((COM_DIRSITE<<8>>8)-X'200')>>6))
      OPER(CNSL,"SNOS ".HTOS(COM_SYNC1 DEST,3)." ".  C 
         HTOS(COM_SYNC2 DEST,3). C 
         " ".HTOS(COM_ASYNC DEST,3))
      -> OUT
OP(9):                                              ! PRINT USERNAMES
      IF  AV(N1, 0)=0 THEN  -> QQQ
      J=GET USNAMES(I,-1,N1)
      -> PRERR
OP(10):                                              ! CLEAR FSYS
      -> QQQ IF  N1<0
      CLEAR FSYS(N1)
      J = DNEWUSER("VOLUMS",N1,8) 
      J = DNEWUSER("SPOOLR",N1,8)
      J = DNEWUSER("MAILER",N1,8)
      J = DNEWUSER("FTRANS", N1, 8)
      OPER(CNSL,"EXEC PROCS CREATED")
      -> PRERR
OP(11):                                             ! DDUMP
      J=DDUMPINDNO(N1,N2)
      -> PRERR
OP(12):                                       ! CLOSEDOWN
      AUTO CLOSE(0,25)
      -> OUT
OP(13):                                             ! BAD FSYS CYL TRK_FSYS_CYL_TRK
      -> QQQ IF  AV(N1, 0)=0
      J=CYL TRK CONVERT(I,N2,N3,K,0,N1); ! sets I to pgs per trk
      -> PRERR IF  J#0
      IF  ACT=68 START 
         OPER(CNSL,"Bitno=".ITOS(K)." (dec)")
      FINISH  ELSE  START 
         CYCLE  N4=0,1,I-1
            J=BAD PAGE(1,N1,K+N4)
         REPEAT 
      FINISH 
      -> OUT
OP(14):                                             ! CCK DONE_FSYS
      IF  N1<0 THEN  -> QQQ
      ADJUST DLVN BIT(N1, 0)
      I = LOGLINK(QQ, 8) IF  N1 = COM_SUPLVN
      -> DONE
OP(15):                                             ! CREATE_USER_NKB
      PROMPT(CNSL,"FILENAME FSYS NKB")
      MSTATE=1
      -> POUT
CONTMSG(1):
      -> QQQ IF  N1 < -1
      -> QQQ IF  N2 < 1
      J=DCREATEF(USER . "." . FILE,N1,N2,1,LEAVE,N3)
      -> PRERR
OP(16):                                             ! NEWSTART_user(_fsys_dirvsn)
      ! Request stream id from OPER adaptor
      IF  CNSL >> 16 # X'32' THEN  -> QQQ
      P = (CNSL >> 8) & 255
      QQ=0
      QQ_DEST=X'00320001' ! (P<<8)
      DOUT11I(QQ)
      J = 102
      -> PRERR IF  QQ_P1 = 0
      J=STARTP(S1,FILE,"",I,N2,P,NEWSTART,OP TYPE ! (P<<16) ! QQ_P1, N3, 0)
      -> PRERR
OP(17):                                             ! PASSOFF
      PASSU=""
      -> DONE
OP(18):                                             ! BASEF
      J=DSFI(S1,N2,0,0,ADDR(S2))
      OPER(CNSL,S2)
      -> PRERR
OP(19):                                             ! VSN
      OPER(CNSL,"VSN ".VSN)
      -> OUT
OP(20):                                             ! S_FILENAME - GIVE SECTION ADDRESSES
      J=GIVE SECTS(USER,FILE,N2,CNSL)
      -> PRERR
OP(21):                                             ! RENI
      J=DRENAME INDEX(S1,S2,N3)
      -> PRERR
OP(22):                                             ! PRG
OP(23):                                             ! UNPRG
      PROMPT(CNSL,"FILE FSYS")
      MSTATE=2
      -> POUT
CONTMSG(2):
      -> QQQ IF  N1 < -1
      USER1=USER; FILE1=FILE; FSYS1=N1
      PROMPT(CNSL,"LABEL SITE")
      MSTATE=3
      RETURN 
CONTMSG(3):
      CYCLE  J = 0, 1, 7; ! replace mnemonic for site by address
         N1 = X'300' + (J << 6) AND  EXIT  IF  S1 = MNEMO(J)
      REPEAT 
!
      -> QQQ UNLESS  0 < N1 <= X'4C0'
!
      IF  ACT=22 C 
      THEN  J=DPRG(USER1,FILE1,FSYS1,S0,N1) C 
      ELSE  J=DUNPRG(USER1,FILE1,FSYS1,S0,N1)
      -> PRERR
OP(24):                                             ! TRANSFER
      PROMPT(CNSL,"FILE1 FSYS1")
      MSTATE=9
      -> POUT
CONTMSG(9):
      -> QQQ UNLESS  N1>-1
      USER1=USER; FILE1=FILE; FSYS1=N1
      PROMPT(CNSL,"FILE2 FSYS2")
      MSTATE=10
      RETURN 
CONTMSG(10):
      -> QQQ UNLESS  N1>-1
      J=DTRANSFER(USER1,USER,FILE1,FILE,FSYS1,N1,1)
      -> PRERR
OP(25):                                             ! PRINT
      QQ_DEST=N1
      J=LOGLINK(QQ, 3)
      -> DONE
OP(26):                                             ! CLOSE
      N1 = NEW CLOSE TIME IF  N1 = -1
      -> QQQ IF  N1 < 0
      N2=N1
      -> SEVERAL CLOS
OP(27):                                             ! STOP
      -> QQQ UNLESS  IS = ""; ! so that D/STOP LP0 is not catastrophic!
      KICK AT(-1,29,1,0);                 ! cancel regular tick on DACT 29
      ! DISCONNECT CTL STREAMS(-1) - now done in routine DSTOP
      DSTOP(100)
      -> QQQ
OP(28):                                             ! DESTS
      OPER(CNSL,"Sync1 Sync2 Async")
      OPER(CNSL,HTOS(COM_SYNC1DEST,4)."  ". C 
                HTOS(COM_SYNC2DEST,4)."  ". C 
                HTOS(COM_ASYNCDEST,4))
      -> OUT
OP(29):                                             ! KILL_<user> [_procno]
      KILL(S1,N2,CNSL)
      -> OUT
OP(75):                                             ! SIGMON_user_fsys_sigmon
OP(39):                                             ! DIRMON_user_fsys_dirmon
OP(30):                                             ! ACR_user_fsys_acr
      J = HINDA(S1,N2,I,0)
      IF  J = 0 START 
         NH == RECORD(I)
         IF  ACT = 30 THEN  NH_ACR <- N3
         IF  ACT = 75 THEN  NH_SIGMON <- N3
         IF  ACT = 39 THEN  NH_DIRMON = N3
      FINISH 
      -> PRERR
OP(31):                                             ! START_USER(_FSYS_DIVSN)
!      %IF CNSL >> 16 # X'32' %THEN -> QQQ
      P = (CNSL >> 8) & 255
      J=STARTP(S1,FILE,"",I,N2,P,OPERC,0,N3,0); ! REASON = FROM OPER CONSOLE
      -> PRERR
OP(32):                                             ! MAIN LP
      J=LOGLINK(QQ,5);                 ! AND SPOOL THE PREVIOUS
      ! This own used to prevent a logfile from being started even for
      ! DIRVSN=0 (if D/MAIN LP typed early enough).
      ! Just to suppress BAD PARAM msg when done before CCK complete!):
      J=0 IF  STOP LOGFILE=0 AND  J=8
      STOP LOGFILE=1
      -> PRERR
OP(33):                                             ! SENDMSG_USER(_FSYS)
      -> QQQ IF  BCASTM = ""
      NISS = BCASTM
      ATTOP(NISS)
      QQ_DEST = X'FFFF0016'
      J = TXTMESS(S1, QQ, 0, 0, LENGTH(NISS), ADDR(NISS)+1, N2, 0)
      -> PRERR
OP(34):                                             ! SINT:
      -> QQQ UNLESS  0<=N2<=X'FFFF'
      J=ASYNC MSG(S1,0,INTDACT,0,(1<<24) ! N2)
      -> PRERR
OP(35):                                             ! REP_address_value
      IF  N1&3#0 OR  VAL(N1, 4, 1, 0)=0 START 
         OPER(CNSL,"INVALID ADDRESS")
         -> OUT
      FINISH 
      J=INTEGER(N1)
      OPER(CNSL,HTOS(N2,8)." REPS ".HTOS(J,8))
      INTEGER(N1)=N2
      -> DONE
OP(36):                               ! TEXT n
      OPER(CNSL, DERRS(N1))
      -> OUT
OP(37):                                             ! SET BASEF_USER_FSYS
      USER1=S1; FSYS1=N2
      IF  HINDA(USER1,FSYS1,J,0)#0 THEN  -> PRERR
      PROMPT(CNSL,"GIVE BASEFILE ID")
      MSTATE=8
      -> POUT
CONTMSG(8):
      J=DSFI(USER1,FSYS1,0,1,ADDR(S0))
      -> PRERR
OP(38):                                             ! PASS_ERCC99_AAAA
      PASSU=S1
      PASSW=S2
      -> DONE
! OP(39):  look near label OP(30)        ! DIRMON <N>
OP(40):                                             ! AUTOFILE(_0)
      IF  N1>0 THEN  -> QQQ
      IF  N1=0 START 
         J=AUTOCOMM("",2); ! disconnect the file
         -> PRERR
         FINISH 
      PROMPT(CNSL,"Give Autofile name:")
      MSTATE=5
      -> POUT
CONTMSG(5):
      J=AUTOCOMM(S0,1); ! disconnect and connect this file
      -> PRERR
OP(41):                                             ! TESTSTART_USER(_FSYS_DIRVSN)
      J = STARTP(S1, FILE, "", I, N2, 0, 3, 0, N3, 0); ! REASON 3 = TEST
      -> PRERR
OP(42):                                             ! CLEAR BAD PAGES LIST
OP(43):                                             ! BAD FSYS PAGE_FSYS_BITNO
OP(44):                                             ! GOOD FSYS PAGE_FSYS_BITNO
      J=BAD PAGE(ACT-42,N1,N2);         ! PARAMS ARE TYPE, FSYS, BITNO
      -> PRERR
OP(45):                                             ! CLOSE USERS
      IF  0<=N1<=2400 AND  LENGTH(S1)>2 START 
         ! e.g. D/CLOSE USERS 1700
         !      D/CLOSE USERS 940
         !
         ! not allowed to do CLOSE USERS if CLOSE FE or CLOSE FSYS has 
         ! been done.
         !
         IF  PENDG CLO FES#0 OR  PENDG CLO FSYS#0 THEN  -> QQQ
         AUTO CLOSE(N1,26)
         -> DONE
      FINISH 
      ! but D/CLOSE USERS <0 - 99>  means close users for that fsys.
OP(46):                                             ! OPEN USERS
OP46:                                               ! from OP(61) via OTHER CLOSES
      -> QQQ UNLESS  -1<=N1<=99
      INITIAL DELAY = 0
      J=BIT STATUS(OPSTAT,ACT-45,N1)
      DISPLAY OP STAT(CNSL)
      J=OPEN TO("", OPENG!CLOSG, 2, 0);         ! CANCEL "OPEN TO" LIST
      -> DONE
OP(47):                                             ! MSG_USER(_FSYS)
      USER1=S1; FSYS1=N2
      PROMPT(CNSL,"TYPE MESSAGE:")
      MSTATE=6
      -> POUT
CONTMSG(6):
      NISS = IS
      ATTOP(NISS)
      QQ_DEST = X'FFFF0016'
      J = TXTMESS(USER1, QQ, 0, 0, LENGTH(NISS), ADDR(NISS)+1, FSYS1, 0)
      -> PRERR
OP(48):                                             ! INT:_USER_ONE-CHAR
      J=ASYNC MSG(S1,0,INTDACT,0,(1<<24)!(BYTEINTEGER(ADDR(S2)+1)<<16))
      -> PRERR
OP(49):                                                                ! SETMSG
      BCASTM=""
      MSTATE=7
      CYCLE 
         PROMPT(CNSL,"("":"" TERMINATES)")
      -> POUT
CONTMSG(7):
         IF  IS=":" OR  LENGTH(BCASTM)=255 THEN  EXIT 
         BCASTM<-BCASTM." ".IS
         REPEAT 
      SPCAN(BCASTM)
      OPER(CNSL,BCASTM)
      -> OUT
OP(50):                                                                ! BROADCAST
      -> QQQ IF  BCASTM=""
      NISS=BCASTM
      ATTOP(NISS)
      SLASH83(NISS,"",N1,0)
      -> DONE
OP(51):                                       ! NNT
      J = FIND NNT ENTRY(S1, N2, NNAD, 0)
      -> PRERR UNLESS  J = 0

      NN == RECORD(NNAD)

      IF  LENGTH(NN_NAME) < 7 C 
      THEN  OPER(CNSL, "Name: ".NN_NAME) C 
      ELSE  OPER(CNSL, "Name: length".ITOS(LENGTH(NN_NAME)))

      OPER(CNSL, "KB: ".ITOS(NN_KB))
      OPER(CNSL, "INDNO: ".ITOS(NN_INDNO))
      -> OUT
OP(52):                                             ! OPEN TO_<usergroup>
      J=OPEN TO(S1, OPENG, 0, 0);                        ! PUT "S1" INTO THE LIST
      -> PRERR
OP(53):                                 ! XNNT
      J = FIND NNT ENTRY(S1, N2, NNAD, 0)
      -> PRERR UNLESS  J = 0

      NN == RECORD(NNAD)
      NN_NAME = ".NULL"
      -> DONE
OP(54):                                 ! LOGSPACE
      J=LOGLINK(QQ,6)
      -> PRERR
OP(55):                                 ! DELIVER
      IF  "-1" # S1 # "" START 
         -> QQQ UNLESS  LENGTH(S1) = 6
         N1 = -1
         J = HINDA(S1, N1, I, 0)
         -> PRERR UNLESS  J = 0
         NH == RECORD(I)
         IS = NH_DELIVERY
      FINISH  ELSE  START 
         PROMPT(CNSL,"Delivery:")
         MSTATE=4
         -> POUT
      FINISH 
CONTMSG(4):
      LENGTH(IS)=31 IF  LENGTH(IS)>31
      STRING(ADDR(QQ))=IS
      J=LOGLINK(QQ,7)
      -> DONE
OP(56):                                    ! USERS(_<usergroup>(_<N>))  or  USERS(_<N>)
      J=LISTMOD(S1,N1,N2)
      -> OUT
OP(57):              ! DISCONNECTFE
      IF  N1<0 THEN  -> QQQ
      DISCONNECT FE(N1)
      -> DONE
OP(58):                                 ! PROMPT ON
      J = PR SRCE(0)
      -> QQQ IF  J > 9
      PR SRCE(J + 1) = CNSL
      PR SRCE(0) = J + 1
      -> OUT
OP(59):                                 ! PROMPT OFF
      J = PR SRCE(0)
      -> QQQ IF  J = 0
      CYCLE  P = 1, 1, J
         IF  PR SRCE(P) = CNSL START 
            PR SRCE(P) = PR SRCE(J); ! CLOSE THE RANKS!
            PR SRCE(0) = J - 1
            -> DONE
         FINISH 
      REPEAT 
      -> QQQ
OP(60):                                 ! CONNECTFE
      IF  N1<0 THEN  -> QQQ
      CONNECT FE(N1)
      -> DONE
OP(61):           ! CLOSE FSYS_n (_time)
      IF  AV(N1,0)=0 THEN  -> QQQ
      -> SEVERAL CLOS
OP(62):        ! USECOUNT FSYS_n

      J = SHOW USE COUNT(N1,0, CNSL)
      -> OUT
OP(63):                                 ! FAIL
      J=1//0
      -> PRERR
OP(64):                                 ! SCARCITY
OP(65):                                 ! PRE EMPT AT
      J=COM_RATION>>24;       ! scarcity
      K=(COM_RATION>>16)&255; ! pre empt at
      IF  N1>=0 START 
         IF  ACT=64 THEN  J=N1 ELSE  K=N1
      FINISH 
      -> QQQ IF  J>255 OR  K>255
      COM_RATION=(COM_RATION<<16>>16) ! (J<<24) ! (K<<16)
      OPER(CNSL,"Interactive Users =".ITOS(COM_RATION&255))
      OPER(CNSL,"Scarcity at Users>=".ITOS(J))
      OPER(CNSL,"Pre-empt at Users>=".ITOS(K))
      -> OUT
OP(66):                                 ! SESSION LENGTH
      UNLESS  -1<=N1<=0 OR  5<=N1<=4*60 THEN  -> QQQ
      IF  N1>=0 THEN  DEFAULT SESSLEN=N1
      OPER(CNSL,"Default sess mins=".ITOS(DEFAULT SESSLEN))
      -> OUT
OP(67):                                             ! DIRPRINT n
      QQ_DEST = N1
      J = LOGLINK(QQ, 12)
      -> DONE
OP(68):                                 ! FSYS CYL TRK_FSYS_CYL_TRK
      -> OP(13)
OP(69):                                                          ! FSYS BITNO_FSYS_BITNO
      J=CYL TRK CONVERT(K,L,MM,N2,1,N1)
      -> PRERR IF  J#0
      OPER(CNSL,"Cyl/Tk/Pg=".ITOS(L)."/".ITOS(MM)."/".ITOS(N2)." (dec)")
      -> OUT
OP(70):                                             ! CLOSE FE(P)
      UNLESS  0<=N1<=TOP FE NO AND  FES FOUND & (1<<N1)#0 THEN  -> QQQ
      -> SEVERAL CLOS
OP(71):                                ! DAP
      DIRCOM == RECORD(SYSAD(DIRCOMKEY, -1))
      S2 = "" IF  S2 = "-1"
      CYCLE  J = 0, 1, 9
         -> SW71(J) IF  S1 = DAPCOM(J)
      REPEAT 
SW71(0):       ! ?
      OPER(CNSL, "BUSER0:".DIRCOM_DAP USER(0))
      OPER(CNSL, "BUSER1:".DIRCOM_DAP USER(1))
      OPER(CNSL, "IUSER: ".DIRCOM_DAP USER(2))
      OPER(CNSL, "BATCHUSER0: ".DIRCOM_DAP BATCH USER(0))
      OPER(CNSL, "BATCHUSER1: ".DIRCOM_DAP BATCH USER(1))
      OPER(CNSL, "   INTER    ".ITOS(DIRCOM_DAP INTEGER(2)))
      OPER(CNSL, "LO BATCH 0  ".ITOS(DIRCOM_DAP INTEGER(3)))
      OPER(CNSL, "HI BATCH 0  ".ITOS(DIRCOM_DAP INTEGER(4)))
      OPER(CNSL, "LO BATCH 1  ".ITOS(DIRCOM_DAP INTEGER(5)))
      OPER(CNSL, "HI BATCH 1  ".ITOS(DIRCOM_DAP INTEGER(6)))
      QQ = 0
      QQ_DEST = (31<<16) ! 10 {enquire for queued users}
      DOUT11I(QQ)
      J = QQ_P1
      J = 0 UNLESS  0 <= J <= 20
      OPER(CNSL, "(CLAIMQ) ".ITOS(J)." LIMIT ".ITOS(DIRCOM_DAP INTEGER(1)))
      -> OUT
!
SW71(7):       ! USER
SW71(8):
SW71(9):
      DIRCOM_DAP USER(J - 7) = S2 IF  S2 = "" OR  LENGTH(S2) = 6
      -> OUT
SW71(*):       ! Some integer
      I = DAP DEFAULT(J)
      DIRCOM_DAP INTEGER(J) = I IF  S2 = "" OR  STOI2(S2, I) = 0
      -> OUT
OP(72):                          ! CLOSE ?
      PENDG CLO TCPS=(¬(OPEN TO("", TCPPENDG, 1, 0)))
      IF  PENDG CLO FES ! PENDG CLO NODES ! PENDG CLO FSYS ! PENDG CLO TCPS#0 START 
         S3="Pending:"; S4="Partial close at "
         CLO TCPS=(¬(OPEN TO("", TCPINITG, 1, 0)))
         IF  CLO FES ! CLO NODES ! CLO FSYS ! CLO TCPS#0 THEN  S3="Initiated:"
         OPER(CNSL,S3)
      FINISH  ELSE  S4="Full close at "

{FSYSes}
      CYCLE  J=99,-1,0
         IF  BIT STATUS(PENDG FCLOSING,-1,J)#0 THEN   C 
            OPER(CNSL,"Closure of Fsys: ".ITOS(J))
         IF  BIT STATUS(OPSTAT, -1, -1)#0 {i.e. if ANY Fsys is open}  C 
            AND  BIT STATUS(OPSTAT, -1, J)=0  {and fsys J is closed}  C 
            THEN  OPER(CNSL, "FSYS closed: ".ITOS(J))
      REPEAT 
{FEs}
      CYCLE  J=TOP FE NO,-1,0
         IF  PENDG CLO FES & (1<<J)#0 THEN  OPER(CNSL,  C 
            "Closure of FE no. ".ITOS(J))
         IF  FES CLOSED & (1<<J)#0 THEN  OPER(CNSL,  C 
            "FE no. ".ITOS(J)." closed")
      REPEAT 
{Nodes}
      CYCLE  J=31,-1,0
         IF  PENDG CLO NODES & (1<<J)#0 THEN  OPER(CNSL,  C 
            "Closure of NODE no. ".ITOS(J))
         IF  NODES CLOSED& (1<<J)#0 THEN  OPER(CNSL,  C 
            "NODE no. ".ITOS(J)." closed")
      REPEAT 
{TCPs}
      J=OPEN TO("", 0, 6, CNSL);    ! TCP closures
      IF  NEW CLOSE TIME < 0 C 
      THEN  OPER(CNSL,"No close time set") C 
      ELSE  OPER(CNSL,S4.ITOS(NEW CLOSE TIME))
      -> OUT
OP(73):                                       ! FE USECOUNT (_feno)
      IF  N1>=0 START 
         N2=N1; N3=N1
      FINISH  ELSE  START 
         N2=0; N3=TOP FENO
      FINISH 
      CYCLE  J=N3,-1,N2
         IF  FES FOUND & (1<<J) # 0 THEN  OPER(CNSL,"FE no. ". C 
            ITOS(J).", count=".ITOS(FE USE COUNT(J))) C 
         ELSE  START 
            IF  N1>=0 THEN  -> QQQ
         FINISH 
         REPEAT 
      -> OUT
OP(74):
      EMPTY DVM
      -> DONE
! OP(75):             look near OP(30)
OP(76):                                     !CLOSE TO <USERGROUP>
      J = OPEN TO(S1, CLOSG, 0, 0)
      -> PRERR
OP(77):                                     ! FE_<username>
      SHOW FES(S1,CNSL)
      -> OUT
OP(78):                                     ! OPEN FE(P)_feno
      FES CLOSED=FES CLOSED & (¬(1<<N1))
      -> DONE
OP(79):                                     ! AUTOSLOAD ON/OFF
      IF  S1 = "ON" C 
      THEN  COM_IPLDEV = (1<<31) ! COM_IPLDEV C 
      ELSE  IF  S1 = "OFF" C 
      THEN  COM_IPLDEV = COM_IPLDEV << 1 >> 1 C 
      ELSE  IF  S1 = "?" START 
         S1 = "OFF"
         S1 = "ON" IF  COM_IPLDEV < 0
         OPER(CNSL, S1)
      FINISH  ELSE  -> QQQ
      -> DONE
OP(80):                                             ! CLOSE NODE
      UNLESS  0<N1<=31 THEN  -> QQQ {Node 0 is special, and we cannot close it
      -> SEVERAL CLOS
OP(81):                                             ! OPEN NODE
      NODES CLOSED=NODES CLOSED & (¬(1<<N1))
      -> DONE
OP(82):                                             ! CLOSE TCP
      IF  LENGTH(S1) < 2 THEN  -> QQQ

SEVERAL CLOS:
      !
      ! We get to this label from:
      !
      !                                  N1 N2
      !
      !         OP(26)    D/CLOSE           0
      !                   D/CLOSE           time
      !
      !         OP(61)    D/CLOSE FSYS   f  0
      !                   D/CLOSE FSYS   f  time
      !                   D/CLOSE FSYS   f  -1
      !
      !         OP(70)    D/CLOSE FE(P)  n  0
      !                   D/CLOSE FE(P)  n  time
      !                   D/CLOSE FE(P)  n  -1
      !
      !         OP(80)    D/CLOSE NODE   n  0
      !                   D/CLOSE NODE   n  time
      !                   D/CLOSE NODE   n  -1
      !
      !         OP(82)    D/CLOSE TCP   tcpname   0
      !                   D/CLOSE TCP   tcpname   time
      !                   D/CLOSE TCP   tcpname   -1
      !
      ! If close time not given, then we just close the FSYS/FE/Node/ TCP
      ! to new logons (not a partial close).
      N2 = NEW CLOSE TIME IF  N2 = -1
      -> QQQ UNLESS  -1 <= N2 <= 2400
!
      IF  N2<0 START 
         IF  ACT=26 THEN  -> QQQ
         IF  ACT=61 THEN  ACT=45 AND  -> OP46    {same as D/CLOSE USERS fsys}
         IF  ACT=70 THEN  FES CLOSED=FES CLOSED ! (1<<N1)
         IF  ACT=80 THEN  NODES CLOSED=NODES CLOSED ! (1<<N1)
         IF  ACT=82 THEN  J=OPEN TO(S1, TCPCLOSED, 0, 0) AND  -> PRERR
         -> DONE
      FINISH 

      CLO TCPS=(¬(OPEN TO("", TCPINITG, 1, 0)))
      IF  CLO FES ! CLO FSYS ! CLO NODES ! CLO TCPS#0 OR  2<=AUTO STATE<=6 THEN  -> QQQ
         {can't allow any alterations after state goes from}
         {"Pending" to "Initiated".                        }

      N3=1; ! N3 is used as a set/unset marker for the actions on the
            ! bit arrays for FSYSes/FEs. (It is possible to withdraw a
            ! CLOSE FSYS/FE by saying
            !           D/CLOSEFE fe-no 0      or
            !           D/CLOSEFSYS fsys 0     or
            !           D/CLOSENODE node-no 0  or
            !           D/CLOSETCP  tcpname 0
            !
            ! Similarly D/CLOSE 0   withdraws all CLOSE FE/FSYS settings.
            ! But note that all this can be done only before the "pending"
            ! status changes to "definite", about 16 mins before the
            ! appointed time).
            ! Note also that Nodes(TCPs) are opened by saying D/OPEN NODE(TCP), and these
            ! commands are subject to fewer checks than the others. This is
            ! because they are intended to be used AFTER the partial close is
            ! complete and the node(TCP) becomes available again. It is not primar-
            ! ily intended for withdrawing a partial close.

      IF  N2>=0 START ; ! N2 is the time, hhmm, from the command.
         IF  N2=0 THEN  N3=0           {withdrawing}
         IF  ACT=26 {D/CLOSE} OR  ACT=45 {D/CLOSEUSERS} START 
            ! D/CLOSE 0  or  D/CLOSEUSERS 0:   clear all settings
            ! D/CLOSE time, D/CLOSEUSERS time: also clear all settings
            CLEAR ALL PARTIAL
         FINISH  ELSE  IF  N3#0 START 
            BEGIN  {Also test that time begin set for partial close is at least 15 mins away}
            STRING (3) HH,MM,SS
            INTEGER  H, M, NOW MINS 0000, MINS TO GO, CLOSE MINS 0000
               TIME -> HH . (".") . MM . (".") . SS
               NOW MINS 0000 = STOI(HH)*60 + STOI(MM)
               H = N2 // 100
               M = N2 - 100*H
               CLOSE MINS 0000=H*60 + M
               MINS TO GO=CLOSE MINS 0000 - NOW MINS 0000
               IF  MINS TO GO<-1 THEN  MINS TO GO=MINS TO GO + 24*60
               IF  MINS TO GO < 15 THEN  J=9 {Must give 15 mins} ELSE  J=0
            END  {begin block}
            IF  J#0 THEN  -> PRERR
         FINISH 
      FINISH 
      IF  ACT=61 THEN  J=BIT STATUS(PENDG FCLOSING,N3,N1) AND  PENDG CLO FSYS=N3
      IF  ACT=70 {D/CLOSE FE} START 
         IF  N3=0 C 
         THEN  PENDG CLO FES=PENDG CLO FES & (¬(1<<N1)) C 
         AND      FES CLOSED=FES CLOSED    & (¬(1<<N1)) {Note 1} C 
         ELSE  PENDG CLO FES=PENDG CLO FES ! (1<<N1)

         ! Note 1 immediately above: This line is placed here to provide an
         ! emergency way of clearing bits in FES CLOSED. Normally this should
         ! get done at comms controller reply to Disconnect FE, but just in 
         ! case this doesn't get done for any reason, the route for clearing
         ! a bit would be:
         !       
         !       D/CLOSEFE  fe  time              followed by
         !       D/CLOSEFE  fe  0

      FINISH 

      IF  ACT=80 {D/CLOSE NODE} START 
         IF  N3=0 C 
         THEN  PENDG CLO NODES=PENDG CLO NODES & (¬(1<<N1)) C 
         ELSE  PENDG CLO NODES=PENDG CLO NODES ! (1<<N1)
      FINISH 

      IF  ACT=82 {D/CLOSE TCP} START 
         LENGTH(S1)=MAXTCPNAME IF  LENGTH(S1)>MAXTCPNAME
         J = 0; ! set
         J = 3 IF  N3 = 0; ! clear
         J = OPEN TO(S1, TCPPENDG, J, 0)
      FINISH 

      PENDG CLO TCPS=(¬(OPEN TO("", TCPPENDG, 1, 0)))
      AUTO CLOSE(N2,27) IF  N2>0 OR  (N2 ! PENDG CLO FSYS ! PENDG CLO FES ! C 
                            PENDG CLO NODES ! PENDG CLO TCPS)=0
      POKE SPOOLR(ACT,N1,N3)
      -> DONE
OP(83):                                             ! OPEN TCP
      -> QQQ IF  LENGTH(S1) < 2
      J=OPEN TO(S1, TCPCLOSED, 3, 0)
      -> PRERR
OP(87):                                             ! LS user key sno
      ! Special actions (perhaps a temporary D/ command, but a permanent
      ! mechanism will be required) to set an entry in the process list to
      ! tell DIRECT to pass a message to an existing process at LOGON.
!
      N4 = N2 >> 4
      N2 = N2 & 15
      -> QQQ UNLESS  0<=N2<=2 AND  N3>X'FFFF' AND  N4 < 256
!
BEGIN 
INTEGER  C
RECORD (PROCDATF)NAME  PROCE
      J=37 {user not found}
      C = LIVEHD
      WHILE  C # ENDLIST CYCLE 
         PROCE == PROCLIST(C)
         IF  PROCE_USER=S1 AND  PROCE_INVOC = N4 START 
            PROCE_LOGKEY=N2
            PROCE_LOGSNO=N3
            J=0
            EXIT 
         FINISH 
         C = PROCE_LINK
      REPEAT 
END  {begin block}
      -> PRERR
OP(88):                ! Site
      TYPE = -1
      MSTATE = 12
      PROMPT(CNSL, "Type: ")
      -> POUT
CONTMSG(12):
      TYPE = 0 IF  S0 = "SUBSYS"
      TYPE = 1 IF  S0 = "STUDENT"
      -> OP88A IF  TYPE < 0
!
      MSTATE = 13
      PROMPT(CNSL, "File: ")
      -> POUT
CONTMSG(13):
      TYPE = -1 UNLESS  S0 = "" OR  FF(S0) = 1
OP88A:
      DIRCOM == RECORD(SYSAD(DIRCOM KEY, -1))
      DIRCOM_DEFAULT SUBSYS = S0 IF  TYPE = 0
      DIRCOM_DEFAULT STUDENT = S0 IF  TYPE = 1
      OPER(CNSL, " SUBSYS: " . DIRCOM_DEFAULT SUBSYS)
      OPER(CNSL, "  count: " . ITOS(DIRCOM_SUBSYS SITE COUNT))
      OPER(CNSL, "STUDENT: " . DIRCOM_DEFAULT STUDENT)
      OPER(CNSL, "  count: " . ITOS(DIRCOM_STUDENT SITE COUNT))
      -> OUT
OP(89):                             ! DAY
      J = 1 + DDAYNUMBER
      J = 1 + J - 7*(J//7)
      OPER(CNSL, DAY(J) . "day")
      -> OUT
OP(90):                ! CLOSE PAD
      ACT=82
      -> op(82)
      -> QQQ UNLESS  LENGTH(S1) = 8
      S1 = "0000" . S1 . "??"
      ACT = 82  { pretend to be CLOSE TCP }
      -> SEVERAL CLOS
OP(91):                     ! CLOSE TIME
      -> QQQ UNLESS  -1 <= N1 <= 2400
      NEW CLOSE TIME = N1
      -> DONE
OP(92):                     ! CHECK FSYS
      -> QQQ UNLESS  0 <= N1 <= 99
      J = CCK(N1, 1, P)
      DOPER2("Fsys " . S1 . " " . ITOS(P) . "% full")
      -> PRERR
OP(93):                     ! OPEN PAD
      ACT=83
      -> op(83)
      -> QQQ UNLESS  LENGTH(S1) = 8
      S1 = "0000" . S1 . "??"
      J = OPEN TO(S1, TCPCLOSED, 3, 0)
      -> PRERR
END ; ! XOPER
!
!-----------------------------------------------------------------------
!
STRINGFN  SPECIAL CONCAT(STRING (255) A, B)
      RESULT  = A . TOSTRING(LENGTH(B)) . B {and let's hope it's not too long: shouldn't be}
END ; ! SPECIAL CONCAT
!
!-----------------------------------------------------------------------
!
ROUTINE  SANITARISE(STRINGNAME  S)
INTEGER  I
      FOR  I = 1, 1, LENGTH(S) CYCLE 
         CHARNO(S, I) = REC SEP ! 128 IF  CHARNO(S, I) = REC SEP
      REPEAT 
END ; ! SANITARISE
!
!-----------------------------------------------------------------------
!
INTEGERFN  CHECKSTART(STRING (255)USER, STRINGNAME  PASS, STRING (63) ITADDR,
      INTEGER  IDENT, PROTOCOL)
!
! PASS is used to return an error message
!
INTEGER  FSYSOPEN, FLAG, INDAD, SPECIALLYOPEN, FSYS, INVOC, FE NO, J
INTEGER  SUPFSYS, SUPINDAD
BYTEINTEGERNAME  PASSFAILS
INTEGER  BASEFILE, NODE NO, CONSOLE, K, DT
LONGINTEGER  LE
RECORD (HF)NAME  NH, SUPH
STRING (MAXTCPNAME) TCPNAME
STRING (255)PASSWORD, FULL USER
CONSTINTEGER  LIM = 100
      PASSWORD = PASS
      PASS = ""
!
      FE NO = (IDENT>>16) & 255
{FEs}
      IF  FES CLOSED & (1<<FE NO) # 0 THEN  RESULT  = 111

      SET ITADDR(ITADDR, NODE NO, CONSOLE, TCPNAME)
 {Nodes}
      IF  NODES CLOSED & (1<<NODE NO)#0 THEN  RESULT =112; ! Remember, if we want to introduce
                     ! a new message, there is INFO to think of, as well as
                     ! the user documentation.
{TCPs}
      RESULT =113 IF  OPEN TO(TCPNAME, TCPCLOSED, 1, 0)=0 {TCP closing}
      FSYSOPEN = BIT STATUS(OPSTAT,-1, -1); ! YES if any open, else NO
      FSYS = -1
      FLAG = 105; ! Invalid user
      FULL USER = USER
      IF  LENGTH(USER) > 6 THEN  LENGTH(USER) = 6
      IF  LENGTH(USER) = 6 START 
         FLAG = 110; ! User not found
         UCTRANSLATE(ADDR(USER)+1, 6)
         J = HINDA(USER, FSYS, INDAD, 0)
         IF  J = 0 START 
            BASEFILE = 0
            NH == RECORD(INDAD)
            PASSFAILS == NH_PASSFAILS
            LE = NH_DWSP
            K = NH_DWSPK
            BASEFILE = 1 UNLESS  NH_BASEFILE = ""
            SPECIALLYOPEN = NO
            J = OPEN TO(USER, OPENG!CLOSG, 1, 0)
                                  ! result < 0       if specially CLOSED TO user
                                  !        = 0 (OK)  if specially OPEN TO user
            IF  J < 0 THEN  RESULT  = 110 ELSE  C 
               IF  J = OK THEN  SPECIALLYOPEN = YES
            FSYSOPEN = BIT STATUS(OPSTAT,-1, FSYS)
            IF  FSYSOPEN = YES OR  SPECIALLYOPEN = YES START 
               -> TRYSTART IF  EQUSER(USER,PASSU)=YES AND  PASSWORD=PASSW
               RESULT  = 103 IF  PASSFAILS > LIM
!
               IF  NH_SURNAME = "#VIEWER" START 
   -> PASS FAIL IF  BASEFILE = 1
                  -> PASS OK IF  USER = "VIEWER"
                  -> PASS OK IF  USER = "LIBRAR"
                  -> PASS OK IF  USER = "HORTIH"
                  -> PASS OK IF  USER = "IMPORT"
                  -> PASS OK IF  USER = "DATALI"
                  -> PASS OK IF  USER = "CROPHE"
                  -> PASS OK IF  USER = "MINIUC"
                  -> PASS OK IF  USER = "CROPHE"
                  -> PASS OK IF  USER = "CROPHE"
                  -> PASS OK IF  USER = "CROPHE"
               FINISH 
               -> TRY START IF  USER = "REMOTE"
               -> TRY START IF  USER = "PRINTE"
!
               -> PASS OK IF  ENCRYPT(0, PASSWORD, LE, K, DT) = 0
               -> PASS FAIL IF  BASEFILE = 0; ! default basefile
               -> PASS FAIL UNLESS  CHARNO(USER, 4) = 'U'; ! not a student
               -> PASS FAIL IF  NH_SUPERVISOR = ""; ! no supervisor
               SUPFSYS = -1
               FLAG = HINDA(NH_SUPERVISOR, SUPFSYS, SUPINDAD, 0)
               IF  FLAG = 0 START 
                  SUPH == RECORD(SUPINDAD)
                  LE = SUPH_DWSP
                  K = SUPH_DWSPK
                  FLAG = ENCRYPT(0, PASSWORD, LE, K, DT)
               FINISH 
!
               IF  NH_OWNER # USER START 
                  J = HINDA(USER, FSYS, INDAD, 0)
                  RESULT  = 1000 + J UNLESS  J = 0
                  NH == RECORD(INDAD)
                  PASSFAILS == NH_PASSFAILS
               FINISH 
!
               -> PASS FAIL UNLESS  FLAG = 0
PASS OK:
               -> TRYSTART IF  SPECIALLYOPEN = YES
               FLAG = LISTMOD(USER, 0, 0)
               -> TRYSTARTIF  FLAG=0
               RESULT =FLAG
PASS FAIL:
               PASSFAILS = PASSFAILS+1 IF  PASSFAILS <= LIM
               DOPER2(USER." PASSWORD FAILURE") IF  PASSFAILS&7=0
            FINISH 
            FLAG = 103; ! INVALID PASSWORD
         FINISH 
      FINISH 
      FLAG = 107 IF  FSYSOPEN = NO; ! NO USER SERVICE
!
      IF  FLAG = 105 AND  FULL USER -> ("INFO") START 
         FLAG = 98 {Resources Scarce} IF  COM_RATION&255 > COM_RATION>>24
      FINISH 
!
      RESULT  = FLAG
TRYSTART:
      FLAG = 0

      ! If exactly one interactive process aleady exists for USER, look to see if its
      ! PROCLIST entry specifies that a message is simply to be given to the
      ! existing process.

      IF  NH_IUSE=1 START 
BEGIN 
INTEGER  C, J
RECORD (PARMF) P
RECORD (PROCDATF)NAME  PROCE
STRING (255) TEMP
      C = LIVEHD
      WHILE  C # ENDLIST CYCLE 
         PROCE == PROCLIST(C)
         IF  PROCE_USER=USER {%AND PROCE_REASON = INTER} AND  PROCE_LOGKEY#0 AND  PROCE_LOGSNO#0 START 

            ! If reason for process wanting logon message is that it has lost its
            ! console due to a comms break (LOGKEY=1), then set the key zero so that no
            ! further logon gets the same treatment. If reason is that process
            ! has designated itself as a multi-console process (LOGKEY=2) then
            ! let the proess list entry stand, for further logons.

            PROCE_LOGKEY=0 IF  PROCE_LOGKEY=1
            TEMP=SPECIAL CONCAT("", HTOS(COM_SECSFRMN, 8))
            TEMP=SPECIAL CONCAT(TEMP, HTOS(IDENT, 8))
            TEMP=SPECIAL CONCAT(TEMP, FULL USER)
            TEMP=SPECIAL CONCAT(TEMP, PASSWORD)
            TEMP=SPECIAL CONCAT(TEMP, ITADDR)
            TEMP=SPECIAL CONCAT(TEMP, HTOS(PROTOCOL, 8))
            SANITARISE(TEMP)
            P_DEST=PROCE_LOGSNO
            J=TXTMESS(PROCE_USER, P, 1 {sync}, PROCE_INVOC, LENGTH(TEMP)+1, ADDR(TEMP), PROCE_FSYS, 1 {sact=>PON AND CONTINUE})

            IF  J=0 THEN  FLAG=114 {"Connected"} ELSE  FLAG=107 {"No User Service"}
            EXIT 
         FINISH 
         C = PROCE_LINK
      REPEAT 

END  {begin block}
      FINISH 
      RESULT  = FLAG UNLESS  FLAG = 0
      ! Else continue with a normal start-up
      RESULT  = STARTP(USER, PASS, ITADDR, INVOC, FSYS, 0, INTER, IDENT, -2, PROTOCOL)
END ; ! CHECKSTART
!
!-------------------------------------------------------------------------------
!
EXTERNALINTEGERFN  COUNT PROCS IN(STRING (6) USERGROUP,
   INTEGERNAME  IPROCS)
INTEGER  N,CUR,LI
RECORD (PROCDATF)NAME  E
      LI=0;                            ! COUNT OF INTERACTIVE PROCESSES
      N=0
      CUR=LIVEHD
      WHILE  CUR#ENDLIST CYCLE 
         E==PROCLIST(CUR)
         IF  E_REASON#BATCH AND  E_PROCESS>TOPEXEC+2 START 
            IF  EQUSER(E_USER,USERGROUP)#0 THEN  N=N+1
            LI=LI+1
         FINISH 
         CUR=E_LINK
      REPEAT 
      IPROCS=LI
      RESULT =N
END ; ! COUNT PROCS IN
ROUTINESPEC  INPUT MESSAGE FROM FEP(RECORD (PARMF)NAME  P)

!-----------------------------------------------------------------------

EXTERNALROUTINE  PROCESS1(INTEGER  XA, XB)

! Params XA, XB are not used


! Process 1 Activities

! 19 Message from OPER in reply to prompt
! 20 Message from OPER
! 21 LOGLINK(1)
! 22 Log-on message from FEP
! 23 Process stopping
! 24 Start Batch job
! 25 FCHECK receives request for consistency check
! 26 Dummy (NO-OP)
! 27 For JOURNL, causes current logfile to be spooled
! 28 Reply from SPOOLR
! 29 Regular 57 sec kick, see AUTOCOMM("",0)
! 30 Autoclose
! 31 Print message in log, equiv to act 7, err msgs from PARSE COM
! 32 Used in DIRECTs closedown sequence
! 33 Reply to FE connect
! 34 Display VSNs
! 35 DIRECT receives FSYS CCK complete from FCHECK
! 36 NEWPAGE CHAR
! 37 FCHECK
! 38 LOGLINK(9)
! 39         10
! 40         11
! 41 spare, was used for IUPDATE and pre-emption
! 50-61, 62-73 for FEP this that and the other!


RECORDFORMAT  BRQF(INTEGER  DEST,SRCE,IDENT,BYTEINTEGER  FSYS,  C 
   STRING (11) SPOOLRFILE,STRING (6) SPARE)
RECORD (BRQF)NAME  BATCHRQ

RECORDFORMAT  SPOOF(INTEGER  VSN,FSYS,STRING (6) USER,SPARE1,  C 
   INTEGER  IDENT,KINSTRS,STRING (31)JOBDOCFILE, STRING (15)JOBNAME, C 
   INTEGER  PRIORITY, DECKS, DRIVES, OUTPUTLIMIT)
RECORD (SPOOF)NAME  SPOOH
!         PRIORITY IS A NUMBER IN THE RANGE 1 - 5
!              1 = VLOW
!              2 = LOW
!              3 = DEFAULT
!              4 = HIGH
!              5 = VHIGH

RECORD (PROCDATF)ARRAY  PROCL(0:255); ! 64 bytes*256 procs = 14 kbytes.

INTEGER  MAXPROCS, NSYS, BASE, CTODAY, CTIM, AD, PERCENT, K
INTEGER  IDENT, REPLY DEST, INVOC
INTEGER  DACT, I, J, FSYS, SEG, GAP, CCKFLAG
INTEGERNAME  TIME ON DISC, DATE ON DISC
INTEGERARRAY  A(0:99)
STRING (6)USER
STRING (11)SPOOLRFILE
STRING (42)S
RECORD (FHDRF)NAME  FILEH
RECORD (DDTF)NAME  DDT
RECORD  (PARMF)P
SWITCH  PR(0:73)
OWNSTRING (42) PROM=""
OWNINTEGER   TIMES ENTERED = 0; ! count to avoid infinite loops
STRINGNAME  SN
RECORD (DISCDATAF)DATA
! DO CONSISTENCY CHECK ONLY IF DIRVSN IS ZERO. THIS WAY WE CAN RESTART 
! WITH A DIFFERENT DIRVSN, NOT DOING CONSISTENCY CHECK, IF THE
! CONSISTENCY CHECK SHOULD FAIL.
      *MPSR_X'F840'; ! clear bits 5-7 in PSR which are left as ones
               ! by SIGNAL in case of restart-after-contingency
      TIMES ENTERED = TIMES ENTERED + 1
      IF  TIMES ENTERED = 1 START 
         *STLN_I
         PROC1 LNB = I; ! save for SIGNAL
      FINISH  ELSE  START 
         DOPER2("DIRECT restarted")
         DSTOP(98) IF  TIMES ENTERED > 2
         -> IN
      FINISH 
!
      PERCENT = -1
      CCKFLAG = 0
      *LSS_(3)
      *ST_J
      J = J ! X'00000800'
      *LSS_J
      *ST_(3); ! IC INTS MASKED IN SSR
!
      PROCLIST == ARRAY(ADDR(PROCL(0)), PROCLF)
      IF  PROCUSER = "FCHECK" START 
         ! Use the record array as a temporary text "file" for the fcheck.
         FILE1AD = ADDR(PROCL(0))
         CYCINIT(FILE1AD, 64*255)
         LOG ACTION = DT ! WRTOF
      FINISH  ELSE  START 
         ! Not FCHECK. Link up a mini process list.
         CYCLE  J = 0, 1, 7
            PROCL(J) = 0
            PROCL(J)_LINK = J + 1
         REPEAT 
         PROCL(7)_LINK = ENDLIST
         FREEHD == FREEHDI
         LIVEHD == LIVEHDI
         BACKHD == BACKHDI
         FES FOUND == FES FOUNDI
         FE USECOUNT == FE USECTI
!
         FILE1AD = ADDR(PROCL(8))
         CYCINIT(FILE1AD, 64*247)
         LOG ACTION = DT ! LOG ! WRTOF
      FINISH 
!
      GET AV FSYS2(1, NSYS, A)
!
      IF  PROCESS = 1 START 
         DOPER2(VSN)
         DOPER2("Workbase X" . HTOS(WORKBASE, 3))
         J = AUTOCOMM("", 4); ! say 'no autofile'
         ! formerly.. CONNECT CONTROL STREAMS(-1)
         J = COM_MAXPROCS
         J = 254 IF  J > 254
         COM_RATION = J << 24 ! J << 16; ! Set "scarcity" and "pre-empt-at" values to "maxusers"
         IF  COM_USERS = 1 AND  DDVSN&3 = 0 START 
            J = FBASE2(COM_SUPLVN, ADDR(DATA))
            AD = SYSAD(DATKEY, COM_SUPLVN)
            TIME ON DISC == INTEGER(AD)
            DATE ON DISC == INTEGER(AD+4)
            ! ALLOW 6 DAYS SINCE LAST CCK!
            CTODAY = PACKDT
            CTIM = CTODAY << 15 >> 15
            CTODAY = CTODAY >> 17

            J = 0
            J = 65 UNLESS  DATE ON DISC <= CTODAY <= DATE ON DISC+6
            J = 65 IF  DATE ON DISC = CTODAY AND  CTIM < TIME ON DISC
            DOPER2("DT SETTING ?") AND  -> IN UNLESS  J = 0
! Work out how many processes we can afford to start, at X'40' per process
! in the space between WORKBASE and the start of the file system (FBASE).
! Each process has 5 work files:
!        local controller stack 4 pages
!        director stack        51
!        uinf                   1
!        director gla           4
!        signal stack           4
!                              --
!                              64
            MAXPROCS=(DATA_START - WORKBASE)//64
            BASE = WORKBASE ! (COM_SUPLVN<<24);! PUT FSYS NO IN
            MAXPROCS = NSYS IF  NSYS < MAXPROCS
!
! START MAXPROCS PROCESSES
            I = X'200' ! DDVSN << 6 + SUPLVN S START
            J = 0
            WHILE  J < MAXPROCS CYCLE 
               P = 0
               P_DEST = X'30010'; ! start 'batch' process
               STRING(ADDR(P_P1)) = "FCHECK"
               P_P3 = BASE; ! LSTACK, 4 pages
               P_P4 = I; ! same director
               P_P5 = BASE + 4; ! DSTACK 52 pages
               P_P6 = BASE + 56; ! DGLA 4 pages
               DOUTI(P)
               -> IN UNLESS  P_P1 = 0
               P_DEST = (COM_SYNC1DEST + P_P5) << 16 ! 37
               DPONI(P)
               J=J+1
               BASE=BASE + 64
            REPEAT 
            FCHECKPROCS=MAXPROCS
         FINISH  ELSE  START ; ! finish fresh IPL and dirvsn 0
            J = MAP XOP OWNS(8); ! get LOGMAP address and do mappings
            IF  J=0 THEN  DOPER2("DIRECT restarted OK")
         FINISH 
         KICK AT(-1,29,1,0); ! cancel any previous
         KICK AT(57,29,EVERY,0); ! every 57 seconds on DACT 29
      ! finish process 1   else   kick to keep DIRECK awake
      !                           (cancel this in rt STOP FEPS)
      FINISH  ELSE  KICK AT(57,26,EVERY,0)
!--------------------------------------------------------------------------
IN:
                                    ! ****************************WAIT HERE FOR THINGS TO HAPPEN**************
      DPOFFI(P);                        ! SYNC1-TYPE
      DACT=P_DEST&127
      !TEMP - till SPOOLR leaves LP at head-of-form.
! GPC replies for NEWPAGE CHAR at closedown
      IF  DACT=3 OR  DACT=5 OR  DACT=2 THEN  DACT=36; ! NEWPAGE CHAR
      DACT=31 IF  DACT=7
      -> PR(DACT)

PR(*):
      PREC("FUNNY MSG TO PROC 1 ", P, 0)
      -> IN

PR(13):                                      ! Check that closing discs have
                                             ! gone, else gone them. P_P1 has
                                             ! DDT_DLVN. Message was PONned from
                                             ! rt AUTO CLOSE.
      FSYS=P_P1&255
      J=DDT ENTRY(I, FSYS)
      IF  J=0 START 
         DDT==RECORD(I)
         IF  DDT_CONCOUNT#0 OR  DDT_DLVN>=0 START 
            DDT_CONCOUNT=0
            DDT_DLVN=(DDT_DLVN<<2>>2) ! (1<<31)
            DOPER2("Disc ".ITOS(FSYS)." forced free")
         FINISH 
      FINISH 
      -> IN

PR(18):     ! Process start-up failure message - to be printed at terminal
            ! Activity is invoked from routine FAIL in DIRECTOR
      LOGON REPLY(STRING(ADDR(P_P3)), PROCLIST((P_DEST>>8)&255)_USER,
         P_P6, P_P2, PROCLIST((P_DEST>>8)&255)_PROTOCOL, "")
      -> IN

PR(19):     ! MESSAGE FROM OPER IN REPLY TO PROMPT
            ! SAVE UNTIL THERE'S A NEWLINE ON THE END.
      SN == STRING(ADDR(P_P1))
      PROM <- PROM . SN
      -> IN UNLESS  CHARNO(SN, LENGTH(SN)) = NL
      S=PROM
      LENGTH(S)=LENGTH(S)-1
      PROM=""
      XOPER(P_SRCE,S)
      -> IN

PR(20):     ! MESSAGE FROM OPER
      S<-STRING(ADDR(P_P1))
      XOPER(P_SRCE,S)
      -> IN

PR(21):     ! LOGFILE
      J=LOGLINK(P,1)
      -> IN


!----------------- Process stopping message to DIRECT --------------------------

PR(23):   ! PROCESS STOPPING
      USER<-STRING(ADDR(P_P3))
      ! P_P1 is Kinstructions used in session
      ! P_P2 is ISUFF
      ! P_P5 is REASON
      ! P_P6 is Pageturns used in session
     IF  USER#"FCHECK" THEN  PROCESS STOPS(USER,P_P2,P_P5,P_P1,P_P6)
      -> IN

PR(24):     ! START-BATCH-JOB FROM SPOOLR
      BATCHRQ==P
      IDENT=BATCHRQ_IDENT
      FSYS=BATCHRQ_FSYS
      SPOOLRFILE <- BATCHRQ_SPOOLRFILE
      S = "SPOOLR." . SPOOLRFILE
      INVOC=0
      SEG=0; GAP=0
      J=DCONNECTI(S,FSYS,1,0,SEG,GAP)
      IF  J#0 START 
         DOPERR("SPLR BATCHFILE", 2, J); ! connect SPOOLR's batchfile fails
         J=8
         -> BREPLY
      FINISH 
      SPOOH==RECORD(SEG<<18)
      USER<-SPOOH_USER
      J=DDISCONNECTI(S,FSYS,1)
      WRSN("STARTB-DIS", J) UNLESS  J = 0
      J=STARTP(USER,SPOOLRFILE,"",INVOC,FSYS,0,BATCH,IDENT,-2,0)
      UNLESS  J = 0 START 
         WRS("STARTB " . USER . " " . S . " " . DERRS(J))
         IF  J > 100 THEN  J = J - 100 ELSE  J = 6
      FINISH 
BREPLY:
      P_DEST=P_SRCE
      P_P1=IDENT
      P_P2=J; ! NB SPOOLR expects error flag to be in range 1-10
      P_P3=INVOC
      DPONI(P)
      -> IN


!----------------- FCHECK receives request for CONSISTENCY CHECK ---------------

PR(25):     ! CCK ON FSYS P_P1
      REPLY DEST=P_SRCE
      FSYS=P_P1
      DSTOP(100) IF  FSYS<0
      SYMBOLS(57, '-')
      NEWLINE
      CCKFLAG = CCK(FSYS, 0, PERCENT); ! DO THE CCK
      WRS("Fsys is " . ITOS(PERCENT) . "% full"); ! to get it into CCKMESS

      S = "VOLUMS.CCKMESS"

      J=DCREATEF(S,FSYS,256,32+17, LEAVE, SEG); ! ZERO THE FILE and cherish
      IF  J=0 OR  J=16 START 
         IF  J=0 START 
            J=DPERMISSIONI("VOLUMS","DIRECT","","CCKMESS",FSYS,1,7)
            IF  J#0 THEN  DOPERR(S,3,J); ! CCKMESS permission fail
         FINISH 
         SEG=0
         GAP=0
         J=DCONNECTI(S,FSYS,11,X'05F',SEG,GAP)
         IF  J=0 START 
            AD = SEG << 18
            FILEH==RECORD(AD)
            CYCINIT(AD, X'40000') UNLESS  FILEH_TXTRELST=32 AND  FILEH_MAXBYTES=X'40000'
            COPY TO FILE(FILE1AD, AD)
            FILEH_DATE = PACKDT
            J = DDISCONNECTI(S, FSYS, 1)
         FINISH  ELSE  DOPERR(S,2,J); ! connect VOLUMS.CCKMESS fails
      FINISH  ELSE  DOPERR(S,1,J); ! create VOLUMS.CCKMESS fails
!
      IF  COM_IPLDEV < 0 START ; ! we are in 'auto' mode
         IF  CCKFLAG # 0 AND  FSYS # COM_SUPLVN START 
            ADJUST DLVN BIT(FSYS, 1); ! make fsys unavailable again
            CCKFLAG = 0
         FINISH 
      FINISH 
!
      -> PR37

PR(26):     ! DUMMY, FOR CONTROL STREAM CONNECTED REPLY, ELAPSED INT REPLY
            ! and SET UP DEST routine.
      -> IN

PR(27):     ! for JOURNL to cause current logfile to be spooled.
      J=LOGLINK(P,4)
      -> IN

PR(28):        ! REPLIES FROM SPOOLR
      DOPERR("XOP28 SPOOLR REPLY", 0, P_P1); ! TEMP - WE THINK SHOULD NOT OCCUR
      -> IN
PR(29):
      IF  INITIAL DELAY > 0 START 
         INITIAL DELAY = INITIAL DELAY - 1
         IF  INITIAL DELAY = 0 START 
            J = BIT STATUS(OPSTAT, 1, -1)  { open all fsys's }
            J = AUTO COMM("", 3); ! connect the  file if poss
            WRSS("Auto-command file flag=", DERRS(J)) UNLESS  J = 0
            J = BIT STATUS(OPSTAT, 0, 100)  { close imaginary fsys 100}
         FINISH 
      FINISH 
!
      J=AUTO COMM("",0)
PR(30):    ! RESERVED FOR AUTO-CLOSE
      AUTO CLOSE(0,DACT)
      -> IN
PR(31):
      S <- STRING(ADDR(P_P1))
      WRS(S)
      -> IN
PR(32):
                                        ! TO CLOSEDOWN DIRECT,SEE PR(36)
      KICKAT(-1, 29, 1, 0); !cancel regular kick on act29
      DSTOP(100)
PR(33):     ! formerly REPLY TO FE CONNECT. Now spare (Aug 81)
      -> IN
PR(34):
DISPL:
      DISPLAY VSNS
      -> IN

!---------- DIRECT receives FSYS consistency check complete message from an FCHECK process ---------

PR(35):        ! FSYS  complete message from an FCHECK process
               ! This message requests another FSYS message for the FCHECK process
      IF  P_P1 >= 0 START ; ! this is the fsys, first time its -1
         CCK FLAG = CCK FLAG ! P_P2
         S = "Fsys "
         S = S . " " IF  P_P1 < 10
         S = S . ITOS(P_P1) . " "
         S = S . " " IF  P_P3 < 10
         S = S . ITOS(P_P3) . "% full"
         DOPER2(S)
      FINISH 
!
      IF  NSYS > 0 START 
         NSYS = NSYS - 1
         FSYS = A(NSYS)
      FINISH  ELSE  START 
         FSYS = -1; ! Stop  process message
         FCHECKPROCS = FCHECKPROCS - 1
      FINISH 
!
      REPLY DEST = P_SRCE ! 25; ! RH half of this word is already zero
      P = 0
      P_DEST = REPLY DEST
      P_P1 = FSYS
      DPONI(P)
!
      -> IN UNLESS  FCHECKPROCS = 0 = CCKFLAG; !------------------- CCK COMPLETE
!
      ! Chose DACT 2 or 8 in LOGLINK according to whether we want to
      ! go to a main logfile. If the call succeeds, we get an address
      ! to map the process list aray onto, so that it can be accessed
      ! by a subsequent invocation of DIRECT, should this one die for
      ! any reason.
!
      WRS("CCK COMPLETE")
!
      J = 8
      J = 2 IF  STOP LOGFILE = 0
!
      J = MAP XOP OWNS(J)
      CONNECT FE(-1) IF  J=0
      COPY TO FILE(FILE1AD, DIRLOGAD) UNLESS  DIRLOGAD = 0
!
      FILE FOR HOTTOP(0)
      J = BROADMSG(""); ! To create VOLUMS.BROADCAST
!
      WRSS("Director Version ", VSN)
      CYCLE  K = 0, 1, TOPEXEC
         J=STARTP(EXEC(K),S,"",I,COM_SUPLVN,0,OPERC,0,-1,0)
         DOPERR("START " . EXEC(K), 0, J) UNLESS  J = 0; ! start fails
      REPEAT 
!      XOPER(X'00320007', "NEWSTART REMOTE")
!      XOPER(X'00320007', "NEWSTART PRINTE")
!
      INITIAL DELAY = 2 IF  BIT STATUS(OPSTAT, -1, 100) = 1  { fsys 100 open }
      -> DISPL
PR(36):
      -> IN IF  NEWPAGE CHAR(P) = 0; ! keep NEWPAGECHAR going until it returns # result
      DSTOP(100)
PR(37):     !  Kick to request FSYSes
      FSYS = -1;                         ! No FSYS result
PR37:
      P = 0
      P_DEST = (COM_SYNC1 DEST + 1)<<16 ! 35; ! get another FSYS from process 1
      P_P1 = FSYS
      P_P2 = CCKFLAG
      P_P3 = PERCENT
      DPONI(P)
      -> IN
!
PR(38):
PR(39):
PR(40):
      J = LOGLINK(P, DACT-29); ! TO DACTs 9, 10, 11 in LOGLINK
PR(41):
      -> IN
!
! Note:     DACTs  50 - 61 reserved for FE software (ITP)
!                  ditto + X29 ACTIVITY ADDON reserved for FE software (X29)

PR(FEP INPUT MESS):
PR(FEP INPUT MESS + X29 ACTIVITY ADDON):
      INPUT MESSAGE FROM FEP(P)
      -> IN
PR(FEP OUTPUT REPLY MESS):
PR(FEP OUTPUT REPLY MESS + X29 ACTIVITY ADDON):
      WRS("Output message reply from FEP")
      -> IN
PR(FEP INPUT CONNECT REPLY):
PR(FEP OUTPUT ENABLE REPLY):
PR(FEP INPUT CONNECT):
PR(FEP OUTPUT CONNECT REPLY):
PR(FEP INPUT ENABLE REPLY):
PR(FEP INPUT DISABLE):
PR(FEP INPUT DISABLE REPLY):
PR(FEP OUTPUT DISABLE REPLY):
PR(FEP INPUT DISCONNECT REPLY):
PR(FEP OUTPUT DISCONNECT REPLY):

PR(FEP INPUT CONNECT REPLY + X29 ACTIVITY ADDON):
PR(FEP OUTPUT ENABLE REPLY + X29 ACTIVITY ADDON):
PR(FEP INPUT CONNECT + X29 ACTIVITY ADDON):
PR(FEP OUTPUT CONNECT REPLY + X29 ACTIVITY ADDON):
PR(FEP INPUT ENABLE REPLY + X29 ACTIVITY ADDON):
PR(FEP INPUT DISABLE + X29 ACTIVITY ADDON):
PR(FEP INPUT DISABLE REPLY + X29 ACTIVITY ADDON):
PR(FEP OUTPUT DISABLE REPLY + X29 ACTIVITY ADDON):
PR(FEP INPUT DISCONNECT REPLY + X29 ACTIVITY ADDON):
PR(FEP OUTPUT DISCONNECT REPLY + X29 ACTIVITY ADDON):
      OPEN FEP(P)
      DISPLAY VSNS IF  DACT = FEP INPUT CONNECT REPLY OR   C 
                       DACT = FEP INPUT DISCONNECT REPLY
      -> IN
END ; ! PROCESS1

!-----------------------------------------------------------------------

ROUTINE  FEP DOWN(INTEGER  FE)
INTEGER  I, PROTOCOL
RECORD  (PARMF)P
   FES FOUND = FES FOUND & (¬(1<<FE))
   FEPS(FE)_AVAILABLE = NO
   CYCLE  PROTOCOL = ITP, 1, X29
      P_DEST = DISABLE STREAM
      P_SRCE = FE<<8!FEP OUTPUT REPLY MESS
      P_P1 = FEPS(FE)_FEP DETAILS(PROTOCOL)_INPUT STREAM
      P_P2 = ABORT
      I = DPON3I("",P,0,0,PON AND CONTINUE)
      P_DEST = DISABLE STREAM
      P_SRCE = FE<<8!FEP OUTPUT REPLY MESS
      P_P1 = FEPS(FE)_FEP DETAILS(PROTOCOL)_OUTPUT STREAM
      P_P2 = ABORT
      I = DPON3I("",P,0,0,PON AND CONTINUE)
      P_DEST = DISCONNECT STREAM
      P_SRCE = FE<<8!FEP OUTPUT REPLY MESS
      P_P1 = FEPS(FE)_FEP DETAILS(PROTOCOL)_INPUT STREAM
      I = DPON3I("",P,0,0,PON AND CONTINUE)
      P_DEST = DISCONNECT STREAM
      P_SRCE = FE<<8!FEP OUTPUT REPLY MESS
      P_P1 = FEPS(FE)_FEP DETAILS(PROTOCOL)_OUTPUT STREAM
      I = DPON3I("",P,0,0,PON AND CONTINUE)
   REPEAT 
   DISPLAY VSNS
END ; ! FEP DOWN

!-----------------------------------------------------------------------

EXTERNALROUTINE  STOP FEPS
INTEGER  J
      FOR  J=0,1,TOP FE NO CYCLE 
         FEP DOWN(J) IF  FES FOUND&(1<<J)#0
      REPEAT 
      KICK AT(-1,26,1,0)
END ; ! STOP FEPS

!-----------------------------------------------------------------------

ROUTINE  MODE DATA(STRING (255) DATA, INTEGER  ID)

!   This routine is called by rt INPUT MESSAGE FROM FEP when mode data
!arrives from a TCP. The user's UINF file is connected and the data
!moved to the UINF record.

INTEGER  CUR,SEG,GAP,FSYS,J, PROTOCOL
STRING (18)FILE
RECORD (UINFF)NAME  UINF
RECORD (PARMF) P
RECORD (PROCDATF)NAME  PROCE
      ! We have to find the user for whom the data is destined from the
      ! process list.
      CUR=LIVEHD
      WHILE  CUR#ENDLIST CYCLE 
         PROCE==PROCLIST(CUR)
         !
         ! Check that the process has a GETMODE request outstanding.
         IF  ID=PROCE_ID START 
            IF  PROCE_GETMODE=0 START 
               DOPER2("Unwanted mode data")
               RETURN 
            FINISH  ELSE  PROCE_GETMODE=0
            !
            ! Connect the UINF file, insert the data and disconnect.
            FSYS=PROCE_FSYS
            FILE=PROCE_USER . ".#UINFI".ITOS(PROCE_INVOC)
            SEG=0; GAP=0
            J=DCONNECTI(FILE,FSYS,8+2+1,0,SEG,GAP)
            IF  J#0 START 
               DOPER2("UINF connect fail ".ITOS(J))
            FINISH  ELSE  START 
               UINF==RECORD(SEG<<18)
               ! Check data length against size of destination record.
               PROTOCOL = UINF_PROTOCOL
               J = 32 - PROTOCOL; ! SIZEOF(TESTR)
               IF  LENGTH(DATA)>J START 
                  DOPER2("Mode data too long")
                  LENGTH(DATA)=J
               FINISH 
               MOVE(LENGTH(DATA)+PROTOCOL,ADDR(DATA)+1-PROTOCOL,ADDR(UINF_TMODES))
               J=DDISCONNECTI(FILE,FSYS,0)
               IF  J#0 THEN  DOPER2("UINF discon fail ".ITOS(J))
            FINISH 
            ! Send message to user to allow him to run
            P=0
            P_DEST=(COM_SYNC2DEST+PROCE_PROCESS)<<16
            DPONI(P)
            RETURN 
         FINISH 
         CUR=PROCE_LINK
      REPEAT 
      DOPER2("Spurious mode data")
END ; ! MODE DATA

!-----------------------------------------------------------------------

ROUTINE  INPUT MESSAGE FROM FEP(RECORD (PARMF)NAME  P)
INTEGER  FE, CURSOR, NEWCURSOR, COUNT, ADD, BUFF LEN, I
INTEGER  STRM ID, CODE, PROTOCOL, J
BYTEINTEGER  TOTAL LENGTH, TYPE
STRING  (255) NAME, PASS, ITADDR, REPLY, TEMP
RECORD (PROCDATF)NAME  PJ
!
CONSTINTEGER  TOPIMF = 6
SWITCH  IMF(0 : TOPIMF)
!
!Messages between DIRECT and the FEP
!
!
!A.  IN to DIRECT
!                          "fn"
!                  +-----+-----+-----+-----+-----+--- - - +---- - - -+--- - - -+
!    1.  Logon     | LEN |  1  | Stream no | ct) | ITADDR | ct) NAME | ct) PASS|
!                  +-----+-----+-----+-----+-----+--- - - +---- - - -+--- - - -+
!
!                  +-----+-----+-----+-----+-----+------------- - - -----------+
!    2.  Mode data | LEN |  2  | Stream no | ct) | MODE DATA                   |
!                  +-----+-----+-----+-----+-----+------------- - - -----------+
!
!                  +-----+-----+-----+-----+-----+-----+------- - - - ---------+
!    3.  Rejected  | LEN |  3  | Stream no | FEP | ct) | rest of rejected msg  |
!        message   |     |     |           | flag|     |                       |
!                  |     |     |           | =fn |     |                       |
!                  +-----+-----+-----+-----+-----+-----+------- - - - ---------+
!
!                  +-----+-----+-----+-----+-----+------------- - - - ---------+
!    4.  Monitor   | LEN |  4  | Spare     | ct) | text (for MAINLOG)          |
!        message   +-----+-----+-----+-----+-----+------------- - - - ---------+
!
!                  +-----+-----+-----+-----+-----+------------- - - - ---------+
!    5.  Monitor   | LEN |  5  | Spare     | ct) | text (for OPER)             |
!        message   +-----+-----+-----+-----+-----+------------- - - - ---------+
!
!                  +-----+-----+-----+-----+-----+------------- - - - ---------+
!    6.  Kent use  | LEN |  6  | Stream no | ct) | Kent-defined                |
!                  +-----+-----+-----+-----+-----+------------- - - - ---------+
!
!
!  B. Out from DIRECT
!
!                  +-----+-----+-----+-----+-----+-----+-----+----- - - - -----+
!    1.  Logon     | LEN |  1  | Stream no |0=FEP|reply| ct) | text            |
!        reply     |     |     |           |flag |code |     |                 |
!                  +-----+-----+-----+-----+-----+-----+-----+----- - - - -----+
!
!                  +-----+-----+-----+-----+-----+-----+----------- - - - -----+
!    2   Setmode   | LEN |  2  | Stream no |0=FEP| ct) | TCP commands          |
!                  |     |     |           |flag |     |                       |
!                  +-----+-----+-----+-----+-----+-----+----------- - - - -----+
!
!Notes:  LEN       is the message length in total (i.e. including the LEN byte).
!
!        ct)       is the length of the text of bytes which follow(s) in the
!                  field, like in an IMP string.
!
!        FEP flag: must be set zero on output from DIRECT. When received by
!                  Direct in "rejected message" message, the FEP flag field is
!                  the "fn" (function) field of the rejected message, to enable
!                  re-transmission.
!                  
!-------------------------------------------------------------------------------
!
!
ROUTINE  CLEAN(STRINGNAME  S)
INTEGER  L, J, K, CH
      L = LENGTH(S)
      IF  L > 0 START 
         K = 0  { count good characters }
         CYCLE  J = 1, 1, L
            CH = CHARNO(S, J) & 127  { remove any top bit }
            IF  32 <= CH <= 126 START 
               K = K + 1
               CHARNO(S, K) = CH
            FINISH 
         REPEAT 
         LENGTH(S) = K
      FINISH 
END ; ! CLEAN
!
ROUTINE  GET(INTEGER  ADR, LEN)
INTEGER  L
      L = BUFF LEN - CURSOR
      IF  LEN > L START ; ! have to do it in 2 bits
         MOVE(L, ADD + CURSOR, ADR)
         MOVE(LEN - L, ADD, ADR + L)
         CURSOR = LEN - L
      FINISH  ELSE  START 
         MOVE(LEN, ADD + CURSOR, ADR)
         CURSOR = CURSOR + LEN
         CURSOR = 0 IF  CURSOR >= BUFF LEN
      FINISH 
      COUNT = COUNT + LEN
END ; ! GET

   FE = (P_DEST>>8)&255;                !GET FEP

   IF  FEPS(FE)_AVAILABLE = YES START 
      IF  P_P3 = X'01590000' START ;    !FEP DOWN!
         FEP DOWN(FE)
         RETURN 
      FINISH 
      IF  P_DEST&255 > FEP OUTPUT DISCONNECT REPLY THEN  PROTOCOL = X29 ELSE  PROTOCOL = ITP
      ADD = FEPS(FE)_FEP DETAILS(PROTOCOL)_IN BUFF CON ADDR
      CURSOR = FEPS(FE)_FEP DETAILS(PROTOCOL)_INPUT CURSOR
      BUFF LEN = FEPS(FE)_FEP DETAILS(PROTOCOL)_IN BUFF LENGTH
      NEW CURSOR = P_P2
      WHILE  CURSOR # NEW CURSOR CYCLE ;!UNTIL END OF MESSAGES
         COUNT = 0;                     !CHECK ON LENGTH OF EACH MESSAGE
         STRM ID = 0
         GET(ADDR(TOTAL LENGTH),1); ! first byte
         GET(ADDR(TYPE),1); ! second byte
         GET(ADDR(STRM ID)+2,2); ! bytes 3 and 4
      STRM ID = (14<<24) ! (FE<<16) ! STRM ID
      TYPE = 0 UNLESS  0 < TYPE <= TOPIMF
      -> IMF(TYPE)

IMF(0):
     DOPER2("Invalid FEP input")
     -> CHECK

IMF(1):                                  ! interactive logon
      GET(ADDR(ITADDR),1)
      GET(ADDR(ITADDR)+1,LENGTH(ITADDR))
CLEAN(ITADDR)
      GET(ADDR(NAME),1)
      GET(ADDR(NAME)+1,LENGTH(NAME))
CLEAN(NAME)
      UCTRANSLATE(ADDR(NAME)+1, LENGTH(NAME)) UNLESS  NAME = ""
      GET(ADDR(PASS),1)
      GET(ADDR(PASS)+1,LENGTH(PASS))
CLEAN(PASS)
      CODE=CHECKSTART(NAME, PASS, ITADDR, STRM ID, PROTOCOL)
      LOGON REPLY(PASS, NAME, CODE, STRM ID, PROTOCOL, ITADDR) IF  CODE # 0
      -> CHECK

IMF(2):                                  ! terminal characteristics (GETMODE)
      ! move data to user's area and give user a reply
      GET(ADDR(TEMP),1)
      GET(ADDR(TEMP)+1,LENGTH(TEMP))
      MODE DATA(TEMP,STRM ID)
      -> CHECK

IMF(3):                                  ! rejected message from FEP
      DOPER2("Rejected msg from FE")
      ! send it again - straight away
      GET(ADDR(REPLY)+1,TOTAL LENGTH - 3)
      LENGTH(REPLY)=TOTAL LENGTH - 3
      TYPE=CHARNO(REPLY,1); ! get original request function for re-issue
      REPLY=FROMSTRING(REPLY,2,LENGTH(REPLY))
      OUTPUT MESSAGE TO FEP( FEPS, C 
         FE,1,ADDR(REPLY),LENGTH(REPLY)+1,STRM ID,PROTOCOL)
      -> CHECK
IMF(4):                                  ! message for main log
IMF(5):                                  ! message for OPER
      GET(ADDR(TEMP),1)
      GET(ADDR(TEMP)+1,LENGTH(TEMP))
      IF  TYPE=4 START 
         I = LOG ACTION
         LOG ACTION = LOG
         WRS(TEMP)
         LOG ACTION = I
      FINISH  ELSE  DOPER2(TEMP)
      -> CHECK
IMF(6):                      ! KENT special
      GET(ADDR(NAME), 1)
      GET(ADDR(NAME)+1, LENGTH(NAME))
!
      J = LIVEHD
      WHILE  J # ENDLIST CYCLE 
         PJ == PROCLIST(J)
         IF  PJ_ID=STRMID AND  PJ_REASON=INTER AND  PJ_USER=NAME START 
            PJ_PREEMPT = 0
            PJ_SESSEND = COM_SECSFRMN // 60 + 4
            WRSS("Winding up session for ", NAME)
            {exit ?}
         FINISH 
         J = PJ_LINK
      REPEAT 
      -> CHECK
CHECK:
      WRS("INTERNAL LENGTH ?") AND  EXIT  C 
         UNLESS  COUNT = TOTAL LENGTH + 1; ! first byte is count of bytes
                                           ! which follow
   REPEAT 
   FEPS(FE)_FEP DETAILS(PROTOCOL)_INPUT CURSOR = NEW CURSOR
FINISH  ELSE  WRSN("MESSAGE FROM FE", FE)
END ; ! INPUT MESSAGE FROM FEP
ENDOFFILE