objnam JOBMON.LIT ; Created 9-June-86, Last modified 29-Feb-88
; by Irv Bromberg, Medic/OS Consultants, Toronto, CANADA
radix 10
vedit=23
vminor=4
vmajor=3
vsub=0
vwho=1

if eq,1
Edit history:
29-Feb-88  3.4(23)  Don't touch if terminal driver has TD$LDL!TD$NUL set
                   (don't need to check for PSEUDO or NULL drivers anymore)
19-Feb-88  3.3(22)  Don't touch if terminal does not have echo enabled.
          3.2(21)  Don't care if job not at monitor level, if meets other
                   criteria.  Always force ^C before logoff command, causes
                   input buffer to be flushed and terminal to beep.
13-Mar-87  3.1(20)  added prompt for logoff command on next input line

Syntax: JOBMON {minutes}
       >logoff command ; here logoff command is entered at ">" prompt

A typical example:

       JOBMON 10       ; 10-minute timeout
       >LOGOFF

however the system operator has the option of setting any convenient
logoff command (lowercase is supported), maximum length 80 characters.
Leading whitespace in the logoff command is ignored. The default logoff
command (if a blank line is entered at the ">" prompt) is:

                     LOGOFF ; forced off by JOBMON

Monitors all jobs on system for inactivity.  Any job sitting inactive for
the specified number of minutes (default=10 minutes) will be forced to
LOGOFF, provided:

- it is logged into an account
- it is attached to a non-PSEUDO, non-FAKE terminal
- its terminal driver is neither PSEUDO nor NULL
- its terminal status is in line-input mode with echo enabled
 (i.e. T$DAT T$IMI T$LCL and T$ECS terminal status bits all OFF)

Where a job has been inactive and meets all of the above criteria JOBMON
will first force a Control-C to cause the input buffer to be flushed and the
job's terminal to beep, and then will force the specified or default logoff
command to the job.

JOBMON is re-entrant and re-usable, and may be run logged-out.

The minutes before timeout must be at least 1;  if omitted or
less than 1 the default timeout (10 minutes) will be used.

endc

search SYS
J.ALC=1         ; undefined as of AMOS/L 1.3
search SYSSYM
search TRM

JCB=A0
Self=A1
Buffer=A2
IDV=A2
TDV=A2
Table=A3
WRK=A4
TCB=A5
Atemp=A6

Jobs=D0         ; number of jobs in job table
Char=D1
CrtCmd=D1
Number=D1
Timeout=D2      ; minutes before logoff
Count=D3
JobNum=D4       ; current job number * workspace ItemSize
Dtemp=D6
IDVNAM=D7
TDVNAM=D7

; Workspace format - for each job in job table:
OFFCMD=82               ; size of space reserved for logoff command @WRK
ofini
ofdef   CPUTIM,4        ; job's last observed CPU time
ofdef   Idle,2          ; #minutes of idle inactivity
ofsiz   ItemSize        ; size of a workspace element

DftTime=10      ; default to 10-minute timeout

NULL=0
ETX=3
BEL=7
CR=13

TD$NUL=^H40

       phdr    -2,0,PH$REE!PH$REU      ; re-entrant/usable, run logged-out

       mov     JOBCUR,Self

CntTbl: ; first check jobs table to find out how much memory worksp we need
       mov     JOBTBL,Table            ; index the jobs table
       clr     Jobs                    ; pre-clear
       br      20$                     ; enter at end of loop
10$:    inc     Jobs                    ; found one job, count it
20$:    cmp     (Table)+,#-1            ; end of table?
       bne     10$                     ; no, loop back and count some more

       mov     #DftTime,Timeout        ; preset default value
       lin                             ; end of line?
       beq     GetWRK
       gtdec                           ; get number of minutes for timeout
       tst     Number
       beq     GetWRK                  ; must be at least 1
       mov     Number,Timeout          ; set specified timeout

GetWRK: ; now get all the workspace we need as impure area
       mov     Jobs,Dtemp              ; calculate workspace size required
       mul     Dtemp,#ItemSize         ; multiply by size per element
       add     #OFFCMD,Dtemp           ; add space for logoff command
       push    Dtemp
       push
       getmem  @SP
       jne     Abort
       pop     WRK
       pop

GetCMD: ; first input the logoff command into the workspace
       mov     JOBTRM(Self),TCB        ; get our own TCB
       orw     #T$ILC,T.STS(TCB)       ; allow lowercase input
       ttyi
       asciz   ">"                     ; prompt for logoff command input
       even
       kbd                             ; set pointer to logoff command
       byp                             ; skip leading whitespace
       lin                             ; empty line?
       bne     5$                      ; no, process the entry
       lea     Buffer,LOGOFF           ; yes, take the default
5$:     mov     WRK,Atemp               ; point at storage for logoff command
       movw    #OFFCMD-1,Dtemp         ; set maximum size
10$:    movb    (Buffer)+,Char          ; get next character of logoff command
       movb    Char,(Atemp)+           ; save next char of logoff command
       cmpb    Char,#CR                ; terminate on CR
       dbeq    Dtemp,10$               ; loop till done or full
; no need to terminate the line since WRK area cleared by GETMEM already

; The first time through the loop we initialize at the WRK items

Loop:   mov     JOBTBL,Table            ; check all jobs again
       clr     JobNum                  ; pre-clear WRK offset register

ChkTbl: mov     (Table)+,Dtemp          ; job assigned here?
       bpl     ChkJob                  ; >0 =not end of table

DidPass:sleep   #600000                 ; -1=end of table, sleep for 1 minute
       tstb    JOBSTS(Self)            ; were we killed?
       bpl     Loop                    ; no, go back again

Abort:  typecr  <%Aborted>              ; aborted by Ctrl-C
       exit

ChkJob: jeq     NxtJob                  ; 0=job not assigned here
       mov     Dtemp,JCB               ; index this Job Control Block
       cmp     JCB,Self                ; exempt ourself
       jeq     NxtJob                  ; (could never log self off)
       tstw    JOBUSR(JCB)             ; currently logged-in at all?
       jeq     NotIdle                 ; no, exempt if not logged-in

ChkJSTS:movw    JOBSTS(JCB),Dtemp       ; is it at monitor level?
       andw    #^C<J.TIW!J.MON!J.ALC>,Dtemp  ; anything besides TIW/MON on?
       jne     NotIdle                 ; yes, can't touch this guy

ChkATT: mov     JOBTRM(JCB),Dtemp       ; is job attached?
       beq     NotIdle                 ; no, unattached jobs are exempt
       mov     Dtemp,TCB               ; index Terminal Control Block

ChkIDV: mov     T.IDV(TCB),IDV          ; check that IDV not PSEUDO or FAKE
       mov     -4(IDV),IDVNAM          ; get this guy's IDV name
       lea     Atemp,IDVs              ; index list of exempt interface dvrs
10$:    mov     (Atemp)+,Dtemp          ; get next exempt IDV name
       beq     ChkTDV                  ; not an exempt IDV
       cmp     IDVNAM,Dtemp            ; is it an exempt IDV?
       bne     10$                     ; not this IDV, go check next
       br      NotIdle                 ; yes, this job is exempt

ChkTDV: mov     T.TDV(TCB),TDV          ; check that TDV not PSEUDO or NULL
       movw    TD.TYP(TDV),Dtemp       ; check for TDV LCL or NULL bits
       andw    #<TD$LCL!TD$NUL>,Dtemp
       bne     NotIdle

ChkTSTS:movw    T.STS(TCB),Dtemp        ; is it in echo line input mode?
       andw    #ECS!LCL!DAT!IMI,Dtemp  ; check echo suppress and data mode
       bne     NotIdle

ChkTime:mov     JOBCPU(JCB),Dtemp       ; has CPU time changed?
       cmp     Dtemp,OFFCMD+CPUTIM(WRK)[~JobNum] ; don't care what it is/was
       bne     NotIdle                 ; only whether or not it has changed
       decw    OFFCMD+Idle(WRK)[~JobNum] ; countdown the minutes
       bgt     NxtJob                  ; and continue on to next job

Kill:   ; found an idle job, force him off
       movb    #ETX,Char               ; force ^C first to flush input buffer
       trmicp                          ; and kill if not at monitor level
       mov     WRK,Buffer              ; recall pointer to logoff command
More:   movb    (Buffer)+,Char          ; get next char (terminate at NULL)
       beq     NotIdle
       trmicp                          ; force the character as input
       br      More

NotIdle:mov     JOBCPU(JCB),OFFCMD+CPUTIM(WRK)[~JobNum]  ; save CPU time
       movw    Timeout,OFFCMD+Idle(WRK)[~JobNum] ; give him full timeout again
NxtJob: addw    #ItemSize,JobNum        ; adjust WRK offset to next job
       jmp     ChkTbl                  ; go back for more

IDVs:   rad50   "PSEUDO"        ; list of exempted interface drivers
       rad50   "FAKE"          ; <-- as published in AMUS.LOG
       rad50   "FLiP"          ; AlphaBASE FLiP driver
       lword   0

LOGOFF: ascii   "LOGOFF ; forced off by JOBMON" ; send LOGOFF command+comment
       byte    CR,NULL         ; CR, NULL to terminate
       even

       end