.TITLE F11ACT - REPORT FILE ACTIVITY .IDENT "0003" ; ; MODULE: FILES-11 ACTIVITY REPORTING TASK -- MAIN PROGRAM ; ; VERSION: 0003 ; ; AUTHOR: ANDY PUTNINS ; ; DATE: 20-NOV-78 ; ; MODIFICATIONS: ; 14-DEC-78 A. PUTNINS ADD FILE ATTRIBUTES LOGIC ; 6-APR-79 A. PUTNINS ADD DYNAMIC MEMORY LOGIC & RESTRUCTURE ; ; ; GLOBAL SYMBOL DEFINITIONS ; CMDLUN == 1 ;LUN FOR COMMAND INPUT TILUN == 2 ;LUN FOR ERROR MESSAGE OUTPUT TO TERMINAL TIEFN == 2 ;EVENT FLAG TO ERROR MESSAGE OUTPUT FILLUN == 3 ;LUN FOR READING FILE ATTRIBUTES FILEFN == 3 ;EVENT FLAG OUTLUN == 4 ;LUN FOR ACTIVITY REPORT OUTPUT ACPRIO == 220. ;ACP RUN PRIORITY ; ; MACRO TO CALL PRINT ROUTINE ; .MACRO FORMAT MSG,ARGBLK MOV MSG,R1 .IIF NB,ARGBLK, MOV ARGBLK,R2 CALL PUTLIN .ENDM ; ; MACRO TO JUST CALL $EDMSG WITHOUT PRINTING ; .MACRO FMT MSG,ARGBLK MOV MSG,R1 .IIF NB,ARGBLK, MOV ARGBLK,R2 CALL $EDMSG .ENDM .PAGE .SBTTL FILE DEFINITIONS .MCALL FSRSZ$,FDBDF$,FDAT$A,FDOP$A,NMBLK$ FSRSZ$ 2 ;RESERVE SPACE FOR 2 RECORD BUFFERS ; ; OUTPUT FILE: ; VARIABLE LENGTH RECORDS ; STANDARD CARRIAGE CONTROL ; RECORDS CAN CROSS BLOCK BOUNDARIES ; GET/PUT I/O ; SEQUENTIAL ACCESS ; MOVE MODE ; DATASET DESCRIPTOR CONTAINED IN CSI CONTROL BLOCK IN 'GETPRM' ; DEFAULT FILENAME - TI:F11STATS.LST ; EXISTING FILE TO BE OPENED FOR APPEND BY DEFAULT, ELSE FOR WRITE ; .PSECT IMPURE,RW,D OUTFDB:: FDBDF$ FDAT$A R.VAR,FD.CR FDOP$A OUTLUN,,DFNB,FO.APD DFNB: NMBLK$ F11STATS,LST,,TI .PAGE .SBTTL DIRECTIVE PARAMETER BLOCKS .MCALL QIOW$ .PSECT IMPURE,RW,D ; ; QIO TO WRITE ERROR MESSAGES TO TERMINAL ; QIOTI:: QIOW$ IO.WVB,TILUN,TIEFN,,,,<0,0,40> ; ; ACP QIO TO READ FILE ATTRIBUTES ; PARAMETER 1 - FILE ID POINTER ; PARAMETER 2 - FILE ATTRIBUTE LIST POINTER ; RATQIO: QIOW$ IO.RAT,FILLUN,FILEFN,,IOSB,,<0,.FATL> ; ; FILE ATTRIBUTE LIST ; .FATL: .BYTE -1,2 ;READ FILE OWNER UIC P.FOWN: .BLKW ;POINTER TO FILE OWNER BUFFER .BYTE -5,12 ;READ FILE NAME, TYPE AND VERSION P.FNAM: .BLKW ;POINTER TO FILENAME BUFFER .WORD 0 ;END OF LIST .PAGE .SBTTL MESSAGE STRINGS .PSECT PURE,RO,D .NLIST BEX HDRMSG: .ASCIZ '%F FILE SYSTEM STATUS %Y %2Z' DNLMSG: .ASCIZ '%N **** SOME DEVICES NOT LISTED ****' MSG1: .ASCIZ '%2N%2A%O:' NDVMSG: .ASCIZ ' **** NO SUCH DEVICE ****' NFIMSG: .ASCIZ ' **** NOT A FILE-STRUCTURED DEVICE ****' OFFMSG: .ASCIZ ' OFFLINE' FORMSG: .ASCIZ ' MOUNTED FOREIGN' TAPMSG: .ASCIZ ' LABELED TAPE MOUNTED' MSG2: .ASCII ' ACP: %RACP' .ASCII ' VOLUME LABEL: %12A' .ASCIZ ' TRANSACTION COUNT=%D' BFMSG: .ASCIZ '%N **** FCB POINTER ERROR ****%N' MSG3: .ASCII ' %29' .ASCII '(%12' .ASCII '%12' .ASCII '%12' .ASCIZ '"PRE-ACCESSED" DIRECTORIES=%D' FNLMSG: .ASCIZ '%N **** SOME FILES NOT LISTED ****%N' MSG4: .ASCIZ ' %12%18' OWNMSG: .ASCIZ ' %12<[%O,%O]%12>' NAMMSG: .ASCIZ '%18<%X%18>' ACPMSG: .ASCIZ '*%11S%12S%12S' FCMMSG: .ASCIZ '%12S*%11S%12S' SCMMSG: .ASCIZ '%12S%12S*%11S' DIRMSG: .ASCIZ '*' ASNMSG: .ASCIZ '%N **** ASSIGN LUN DIRECTIVE ERROR CODE=%D ****' QIOMSG: .ASCIZ '%N **** QIO DIRECTIVE ERROR CODE=%D ****' RA1MSG: .ASCIZ ' **** READ FILE ATTRIBUTES QIO ERROR CODE=%D, ' RA2MSG: .ASCIZ 'FILE ID=%D,%D ****' ENDMSG: .ASCII '%2N TOTAL FREE SPACE IN FCPCOM=%D. BYTES' .ASCII '%N LARGEST HOLE IN FCPCOM=%D. BYTES' .ASCII '%N TOTAL SIZE OF FCPCOM=%D. BYTES' .ASCII '%N FCPCOM UTILIZATION=%D%' .ASCIZ '%2N TABULATED DATA STRUCTURES USE %D. SCOM NODES.%N' FCSMSG: .ASCIZ '%NF11ACT -- **** STATISTICS FILE FCS ERROR CODE=%D,%D ****' .EVEN .LIST BEX .PAGE .SBTTL BUFFERS, GLOBAL DATA AND LISTHEADS .PSECT IMPURE,RW,D ; ; BUFFER TO HOLD SYSTEM DATE AND TIME. ALSO DOUBLES AS '$EDMSG' ARGUMENT BLOCK ; HDRBK: .BLKW 8. ; ; BUFFER TO HOLD FORMATTED OUTPUT STRINGS FROM '$EDMSG' ; OUTBUF:: .BLKB 256. .EVEN ; ; I/O STATUS BLOCK, ALSO USED FOR TEMPORARY STORAGE ; TEMP:: IOSB: .BLKW 2 ; ; FCPCOM AND SCOM NODE USAGE COUNTS - DO NOT SHUFFLE ; FCFREE:: .WORD 0 ;TOTAL FREE SPACE IN FCPCOM FCHOLE:: .WORD 0 ;SIZE OF LARGEST HOLE IN FCPCOM FCSIZE:: .WORD 0 ;TOTAL SIZE OF FCPCOM FCUTIL:: .WORD 0 ;PERCENT UTILIZATION OF FCPCOM SCALLO:: .WORD 0 ;COUNT OF SCOM NODES FOR ALL ACP'S ; ; FREE DYNAMIC MEMORY LISTHEAD ; HD.FRE: .BLKW 2 ; ; 'INSUFFICIENT DYNAMIC MEMORY' FLAG ; NSFMEM:: .WORD 0 ; ; STATISTICS BLOCK LISTHEAD, INITIALLY EMPTY ; LH.SB:: .WORD LH.SB,LH.SB .PAGE .SBTTL MAIN PROGRAM ; ; REPORT FILE ACTIVITY - MAIN PROGRAM. FIRST WE CALL 'GETPRM' TO PARSE THE ; COMMAND LINE AND SET UP A LIST OF DEVICES FOR WHICH THE USER WANTS FILE ; ACTIVITY STATISTICS. ROUTINE 'FILLSB' DOES THE WORK OF OBTAINING THESE ; STATS. FINALLY, WE FORMAT AND PRINT THEM USING SYSLIB ROUTINE '$EDMSG'. ; ; REGISTER USAGE: ; R0=SCRATCH ; R1=ADDR OF MESSAGE FORMAT STRING ; R2=ADDR OF MESSAGE PARAMETER BLOCK ; R3=LENGTH OF STATISTICS BLOCK ; R4=STATISTICS BLOCK LISTHEAD ; R5=CURRENT STATISTICS BLOCK ; .MCALL FINIT$,OPEN$,OPEN$W,GTIM$S,CLOSE$,EXIT$S,PRINT$ .PSECT F11ACT,RO,I .ENABL LSB START:: FINIT$ ;INITIALIZE FCS MOV #HD.FRE,R0 ;FREE MEMORY LISTHEAD ADDRESS CALL $INIDM ;INITIALIZE DYNAMIC MEMORY MOV #LH.SB,R0 ;ADDR OF STATISTICS BLOCK LISHEAD MOV R0,(R0) ;INITIALIZE LISTHEAD MOV R0,2(R0) ;... NXTCMD: CALL GETPRM ;GET DEVICE LIST BCC 10$ JMP EXIT ;EXIT IF ERROR OR END OF INPUT 10$: CALL FILLSB ;GATHER STATISTICS ; ; OPEN OUTPUT FILE. 'GETPRM' HAS SET UP 'INFDB' FOR APPEND OR CREATE, ; DEPENDING UPON THE USER'S COMMAND. ; OPEN$ #OUTFDB ;OPEN FILE AS PER USER'S COMMAND BCC 30$ ;CONTINUE IF OPEN OK CMPB #IE.NSF,OUTFDB+F.ERR ;NO SUCH FILE? BNE 20$ ;NO - WRITE ERROR MESSAGE OPEN$W #OUTFDB ;YES - CREATE IT BCC 30$ ;CONTINUE IF OK 20$: JMP FCSERR ;ELSE WRITE MESSAGE ; ; FORMAT STATISTICS AND WRITE THEM TO OUTPUT FILE ; 30$: GTIM$S #HDRBK ;GET DATE AND TIME FORMAT #HDRMSG,#HDRBK ;WRITE HEADING TST NSFMEM ;WAS THERE ENOUGH ROOM FOR ALL DEVICES? BEQ 40$ ;YES - CONTINUE FORMAT #DNLMSG ;NO - WRITE MESSAGE 40$: MOV #L.SB,R3 ;SIZE OF STATISTICS BLOCK MOV #LH.SB,R4 ;LISTHEAD ADDR MOV @R4,R5 ;R5=ADDR OF FIRST ENTRY CMP R4,R5 ;LIST EMPTY? BEQ 90$ ;YES - FORGET IT BR FRSTSB ;NO NEXTSB: CALL DELQ ;ADVANCE TO NEXT ENTRY & DELETE PREVIOUS BCS 90$ ;END OF LIST FRSTSB: MOV R5,R2 ;STATISTICS BLOCK IS ALSO ARG. BLOCK... ADD #P.DNAM,R2 ;... FOR '$EDMSG' MOV #OUTBUF,R0 ;SET UP BUFFER PTR FOR FMT MACROS FMT #MSG1 ;FORMAT DEVICE NAME BIT #F.NSDV,.SBFLG(R5) ;NO SUCH DEVICE? BEQ 50$ FMT #NDVMSG ;YES - MOVE MESSAGE TO BUFFER CALL FMTPUT ;AND WRITE IT BR NEXTSB ;TRY NEXT ENTRY 50$: BIT #F.NF11,.SBFLG(R5) ;FILES-11 DEVICE? BEQ 55$ ;YES - CONTINUE FMT #NFIMSG ;NO - MOVE MESSAGE TO BUFFER CALL FMTPUT ;AND WRITE IT BR NEXTSB 55$: BIT #F.OFF,.SBFLG(R5) ;OFFLINE? BEQ 60$ FMT #OFFMSG ;YES CALL FMTPUT BR NEXTSB 60$: BIT #F.FOR,.SBFLG(R5) ;MOUNTED AS FOREIGN? BEQ 70$ FMT #FORMSG CALL FMTPUT BR NEXTSB 70$: FMT #MSG2 ;MOVE VCB STATS TO BUFFER CALL FMTPUT ;AND WRITE THEM OUT BIT #F.BADF,.SBFLG(R5) ;FCB POINTER ERROR? BEQ 80$ FORMAT #BFMSG 80$: FORMAT #MSG3 ;WRITE FCB STATS CALL PUTFAT ;WRITE OUT FILE ATTRIBUTES BR NEXTSB ;LOOP FOR NEXT DEVICE IN LIST 90$: FORMAT #ENDMSG,#FCFREE ;PRINT MEMORY UTILIZATION STATS 998$: BIT #SW.SP,SWMASK ;ARE WE SUPPOSED TO SPOOL THE OUTPUT? BEQ 999$ ;NO PRINT$ #OUTFDB ;YES - SPOOL IT JMP NXTCMD ;AND LOOP FOR ANOTHER COMMAND 999$: CLOSE$ #OUTFDB ;CLOSE OUTPUT FILE JMP NXTCMD ;AND LOOP FCSERR: MOVB OUTFDB+F.ERR,R0 ;EXPAND ERROR CODES TO 1 WORD EACH MOV R0,TEMP MOVB OUTFDB+F.ERR+1,R0 MOV R0,TEMP+2 MOV #OUTBUF,R0 ;SET UP BUFFER POINTER FMT #FCSMSG,#TEMP ;MOVE ERROR MESSAGE TO BUFFER TIQIO #OUTBUF,R1 ;WRITE IT TO TERMINAL BR 998$ EXIT: EXIT$S .DSABL LSB .PAGE .SBTTL PUTFAT - FORMAT AND PRINT FILE ATTRIBUTES ; ; THIS ROUTINE USES THE FILE ID STORED IN EACH FILE ATTRIBUTE BLOCK TO ; OBTAIN SELECTED INFO ABOUT THE FILE FROM THE ACP, AND PRINT IT. ; SOME OF THESE FILES MAY NOT EXIST ANY LONGER AT THIS POINT, BECAUSE ; WE HAVE BEEN RUNNING AT NORMAL PRIORITY SINCE COLLECTING STATISTICS, SO ; WE CAN ANTICIPATE SOME FILE ACCESS ERRORS. TO PREVENT PRIVILEGE ; VIOLATIONS WHEN READING FILE ATTRIBUTES, RUN THIS TASK UNDER A UIC OF [1,1]. ; ; INPUTS: ; R5=ADDR OF CURRENT STATISTICS BLOCK ENTRY ; ; REGISTER USAGE: ; R0=ADDR OF NEXT BYTE IN OUTPUT BUFFER ; R1=ADDR OF INPUT STRING FOR $EDMSG ; R2=ADDR OF ARGUMENT BLOCK FOR $EDMSG ; R3=LENGTH OF FILE ATTRIBUTE BLOCK ; R4=ATTRIBUTE BLOCK LISTHEAD ; R5=ADDR OF CURRENT ATTRIBUTE BLOCK ; ; REGISTERS CLOBBERED: ; R0,R1,R2 ; .MCALL ALUN$S .ENABL LSB PUTFAT: PUSH BIT #F.NFAT,.SBFLG(R5) ;WAS THERE ROOM FOR ALL FILES? BEQ 10$ FORMAT #FNLMSG ;NO - WRITE MESSAGE 10$: MOV #L.AB,R3 ;LENGTH OF ATTRIBUTE BLOCK MOV R5,R4 ;GET ATTRIBUTE LISTHEAD ADD #LH.AB,R4 ;... CMP R4,@R4 ;LIST EMPTY? BNE 20$ ;NO JMP 999$ ;YES - RETURN TO CALLER 20$: FORMAT #MSG4 ;WRITE OUT HEADING ALUN$S #FILLUN,.DNAM(R5),.DUNIT(R5) ;ASSIGN LUN TO THIS DEVICE BCC 30$ ;ERROR? MOVB $DSW,R0 ;YES - EXPAND ERROR CODE TO WORD MOV R0,TEMP FORMAT #ASNMSG,#TEMP ;WRITE MESSAGE BR 999$ ;AND RETURN TO CALLER 30$: MOV @R4,R5 ;R5=ADDR OF FIRST ENTRY BR FRSTAB NEXTAB: CALL DELQ ;ADVANCE TO NEXT ENTRY & DELETE PREVIOUS BCS 999$ ;END OF LIST FRSTAB: MOV R5,P.FOWN ;SET UP POINTER TO FILE OWNER BUFFER ADD #.FOWN,P.FOWN MOV R5,P.FNAM ; AND POINTER TO FILENAME BUFFER ADD #.FNAM,P.FNAM MOV R5,RATQIO+Q.IOPL ;SET UP FILE ID PARAMETER FOR QIO ADD #.FID,RATQIO+Q.IOPL DIR$ #RATQIO ;READ FILE ATTRIBUTES BCC 40$ ;DIRECTIVE ERROR? MOVB $DSW,R0 ;YES - EXPAND ERROR CODE TO WORD MOV R0,TEMP FORMAT #QIOMSG,#TEMP ;WRITE MESSAGE BR NEXTAB ; AND TRY NEXT FILE 40$: CMPB #IS.SUC,IOSB ;ACP ERROR? BEQ 50$ MOVB IOSB,R0 ;YES - EXPAND ERROR CODE TO WORD MOV R0,TEMP MOV #OUTBUF,R0 ;POINT TO BUFFER FMT #RA1MSG,#TEMP ;MOVE ERROR CODE TO BUFFER MOV R5,R2 ;POINT TO FILE ID ADD #.FID,R2 FMT #RA2MSG ;MOVE FILE ID TO BUFFER CALL FMTPUT ;WRITE IT BR NEXTAB ;TRY NEXT FILE 50$: MOV #TEMP,R0 ;EXPAND 2 BYTE FILE OWNER TO MOVB .PROJ(R5),(R0)+ ; 4 BYTES (NO SIGN EXTEND) CLRB (R0)+ MOVB .PROG(R5),(R0)+ CLRB (R0) MOV #OUTBUF,R0 ;SET UP OUTPUT BUUFFER POINTER FMT #OWNMSG,#TEMP ;MOVE FILE OWNER FMT #NAMMSG,P.FNAM ; AND FILE NAME TO BUFFER BIT #F.ACP,.ABFLG(R5) ;IS THIS FCB ALLOCATED IN ACP INTERNAL AREA? BEQ 60$ ;NO FMT #ACPMSG ;YES - PUT MARK IN PROPER COLUMN BR 80$ 60$: BIT #F.FCM,.ABFLG(R5) ;FCB FROM FCPCOM? BEQ 70$ ;NO FMT #FCMMSG ;YES - MARK COLUMN BR 80$ 70$: FMT #SCMMSG ;MUST BE FROM SCOM 80$: BIT #F.DIR,.ABFLG(R5) ;THIS FCB IN DIRECTORY LRU LIST? BEQ 90$ ;NO FMT #DIRMSG ;YES - MARK COLUMN 90$: CALL FMTPUT ;WRITE OUT FINISHED LINE BR NEXTAB ;AND LOOP FOR NEXT FILE 999$: POP RETURN .DSABL LSB .PAGE .SBTTL PUTLIN & FMTPUT - MESSAGE OUTPUT ROUTINES ; ; THESE ROUTINES EITHER FORMAT AND PRINT MESSAGES, OR JUST PRINT ALREADY ; FORMATTED MESSAGES. FORMATTING IS DONE BY SYSLIB ROUTINE '$EDMSG'. ; WE JUMP TO THE FCS ERROR ROUTINE IF WE ENCOUNTER A PUT$ ERROR. ; ; PUTLIN IS CALLED FROM WITHIN A FORMAT MACRO. ; ; INPUTS: ; R1=ADDR OF '$EDMSG' ASCIZ MESSAGE STRING ; R2=ADDR OF '$EDMSG' ARGUMENT BLOCK ; ; OUTPUTS: ; R1=LENGTH OF FORMATTED STRING ; R2=ADDRESS OF NEXT PARAMETER IN ARGUMENT BLOCK ; STRING IS FORMATTED AND PRINTED ; .MCALL PUT$ PUTLIN: PUSH MOV #OUTBUF,R0 ;ADDR OF OUTPUT BUFFER CALL $EDMSG ;FORMAT THE MESSAGE PUT$ #OUTFDB,#OUTBUF,R1 ;WRITE IT (R1=LENGTH) POP BCS 10$ ;ERROR? RETURN ;NO - NORMAL RETURN 10$: TST (SP)+ ;YES - DISCARD RETURN ADDRESS JMP FCSERR ;WRITE FCS ERROR MESSAGE ; ; FMTPUT MUST BE CALLED EXPLICITLY AFTER ASSEMBLING A STRING IN 'OUTBUF' ; BY MEANS OF FMT MACROS. ; ; INPUTS: ; 'OUTBUF' CONTAINS ALREADY FORMATTED MESSAGE ; R0=ADDRESS OF NEXT FREE BYTE IN 'OUTBUF' ; ; OUTPUTS: ; R1=LENGTH OF STRING IN 'OUTBUF' ; STRING IS PRINTED ; FMTPUT: MOV R0,R1 ;COMPUTE LENGTH OF STRING IN OUTBUF SUB #OUTBUF,R1 PUT$ #OUTFDB,#OUTBUF,R1 ;WRITE IT BCS 10$ ;ERROR? RETURN ;NO - NORMAL RETURN 10$: TST (SP)+ ;YES - DISCARD RETURN ADDRESS JMP FCSERR ;WRITE FCS ERROR MESSAGE .PAGE .SBTTL ADDQ - CREATE ENTRY AT END OF QUEUE ; ; THIS ROUTINE ALLOCATES A VARIABLE-LENGTH BLOCK FROM DYNAMIC MEMORY AND ADDS ; IT TO THE END OF A QUEUE. IF THERE IS INSUFFICIENT DYNAMIC MEMORY, WE ; RETURN WITH CARRY SET. ; ; INPUTS: ; R3=SIZE OF BLOCK TO ALLOCATE ; R4=LISTHEAD ; ; OUTPUTS: ; R5=ADDR OF NEW ENTRY ; CARRY SET IF INSUFFICIENT DYNAMIC MEMORY ; ; ALL REGISTERS PRESERVED ; ADDQ:: PUSH ;SAVE VOLATILE REGISTERS MOV #HD.FRE,R0 ;SET UP FOR $RQCB MOV R3,R1 ;... CALL $RQCB ;GET A CHUNK OF DYNAMIC MEMORY BCS 20$ ;NO SPACE - RETURN WITH C SET MOV R0,R5 ;R5 = NEW ENTRY ADDR 10$: CLRB (R0)+ ;CLEAR ENTIRE ENTRY SOB R1,10$ ;... MOV 2(R4),R0 ;R0 = LAST ENTRY (PREVIOUS) ADDR MOV R4,@R5 ;SET UP FORWARD POINTER MOV R5,@R0 ;FORWARD POINTER IN PREVIOUS MOV R0,2(R5) ;BACK POINTER MOV R5,2(R4) ;BACK POINTER IN LISTHEAD CLC ;SET GOOD RETURN 20$: POP RETURN .PAGE .SBTTL DELQ - DELETE FIRST QUEUE ENTRY ; ; THIS ROUTINE REMOVES THE FIRST (CURRENT) ENTRY FROM A QUEUE, RETURNS IT ; TO DYNAMIC MEMORY, AND MAKES THE NEXT ENTRY THE CURRENT ONE. CARRY IS ; SET IF THE LAST ENTRY WAS REMOVED. ; ; INPUTS: ; R3=SIZE OF QUEUE ENTRY ; R4=ADDR OF LISTHEAD ; ; OUTPUTS: ; R5=ADDR OF NEXT QUEUE ENTRY, OR ADDR OF LISTHEAD IF NO MORE ; C SET IF LAST ENTRY HAS BEEN REMOVED ; ; ALL REGISTERS PRESERVED ; DELQ:: PUSH ;SAVE VOLATILE REGISTERS MOV @R4,R2 ;ADDR OF ENTRY TO BE DELETED MOV (R2),R5 ;ADVANCE TO SECOND ENTRY CMP R2,R4 ;LIST EMPTY? BEQ 10$ ;YES - DON'T DELETE LISTHEAD MOV R5,@2(R2) ;NO - REMOVE FIRST MOV 2(R2),2(R5) ;... MOV #HD.FRE,R0 ;SET UP FOR $RLCB MOV R3,R1 ;... CALL $RLCB ;RETURN PREVIOUS TO DYNAMIC MEMORY 10$: CMP R4,R5 ;IS QUEUE NOW EMPTY (CURRENT=LISTHEAD)? BEQ 20$ ;YES - RETURN WITH CARRY SET CLC ;NO - CLEAR CARRY BR 30$ 20$: SEC 30$: POP RETURN .END START