PROC FSEWORK; BEGIN # *** FSEWORK - WORKFILE ACCESS METHOD FOR FULL SCREEN EDITOR. * * COPYRIGHT CONTROL DATA SYSTEMS INC. 1992. # DEF LISTCON #0#; CONTROL EJECT; # UNIVERSAL DEFINITIONS # *IFCALL SINGLE,COMFSGL *IFCALL ONLY,COMFONL *IFCALL MULTI,COMFMLT *CALL COMFFSE CONTROL IFEQ MULTI,1; DEF RECALL(AA) #SMFRCL(AA)#; DEF DELAY #SMFDLY#; XREF PROC SMFRCL; XREF PROC SMFDLY; CONTROL FI; CONTROL IFEQ SINGLE,1; XREF PROC RECALL; DEF DELAY #RECALL(0)#; CONTROL FI; XREF # SYSTEM PROCS # BEGIN PROC WRITE; PROC REWRITE; PROC RPHR; PROC RPHRLS; PROC SKIPEI; PROC REWIND; PROC READ; PROC WRITER; PROC WRITEW; PROC REWRITR; CONTROL IFEQ SINGLE,1; PROC EVICT; CONTROL FI; END XREF # COMPASS PROCS # BEGIN PROC MOVEWD; PROC EXCHWD; FUNC MOVELN; FUNC LINESZ; END XREF BEGIN *CALL COMFXSB END XDEF BEGIN *CALL COMFXWK END # COMMON DATA # CONTROL IFEQ MULTI,1; XREF ARRAY RENTSTK [1:MAXREENT]; # SUBROUTINE STACK # BEGIN ITEM RSTK; END XREF ITEM RSTKPTR; XREF PROC WIOSTAT; CONTROL FI; *CALL COMFDS1 *CALL COMFVD2 *CALL COMFDS2 PAGE PROC INITFET(BUFF,LEN); # INITIALIZE FET # BEGIN # ** INITFET - INITIALIZE FILE ENVIRONMENT TABLE. * * INITFET IS SPECIALIZED FOR THE WORKFILE MANAGER. THE FET * IS ASSUMED TO BE MAPPED BY THE BASED ARRAY "FET". THE * LENGTH OF THE FET IS ASSUMED TO BE AT LEAST SEVEN WORDS, * AND RANDOM I/O FLAGS WILL BE SET. * * ENTRY BUFF - THE ARRAY PROVIDED AS A BUFFER. * LEN - INTEGER LENGTH OF BUFFER. * * EXIT FET - INITIALIZED FOR RANDOM I/O. * QUARTER - INITIALIZED AS APPROX LEN/4. # ARRAY BUFF;; ITEM LEN; QUARTER=(LEN+O"300")/4; FETNAM = WORKNAM; FETCOD = 0; FETDUN = TRUE; FETR = TRUE; FETW = FALSE; FETOUT = LOC(BUFF); FETLIM = LOC(BUFF) + LEN; FETL = 2; FETFIR = LOC(BUFF); FETIN = LOC(BUFF); END FUNC BUFAVAIL I; BEGIN # ** BUFAVAIL - FUNCTION TO COMPUTE BUFFER FREE SPACE. * * BUFAVAIL COMPUTES THE SPACE AVAILABLE IN THE CIRCULAR * BUFFER ASSIGNED TO THE FET MAPPED BY THE BASED ARRAY * "FET". * * ENTRY FET - FET. * * EXIT BUFAVAIL - WORDS OF SPACE STILL AVAILABLE FOR DATA. # IF FETIN LS FETOUT THEN BUFAVAIL=FETOUT-FETIN; ELSE BUFAVAIL=(FETLIM-FETFIR)-(FETIN-FETOUT); END FUNC BUFUSAGE I; BEGIN # ** BUFUSAGE - FUNCTION TO COMPUTE BUFFER UTILIZATION. * * BUFUSAGE IS THE COUTERPART TO BUFAVAIL, COMPUTING THE * NUMBER OF WORDS ALREADY USED IN THE CIRCULAR BUFFER * ASSIGNED TO THE FET MAPPED BY BASED ARRAY "FET". * * ENTRY FET - FET. * * EXIT BUFUSAGE - RESULT. # IF FETIN GQ FETOUT THEN BUFUSAGE=FETIN-FETOUT; ELSE BUFUSAGE=(FETLIM-FETFIR)-(FETOUT-FETIN); END FUNC LSTUSAGE I; BEGIN # ** LSTUSAGE - COMPUTE UTILIZATION OF READ LIST. * * LSTUSAGE COMPUTES THE NUMBER OF WORDS USED IN THE READ * LIST. * * ENTRY RDIN, RDOUT - BOUNDS FOR PORTION OF LIST USED. * LSTSIZE - SIZE OF LIST. * * EXIT LSTUSAGE - RESULT. # LSTUSAGE=RDIN-RDOUT; IF RDIN LS RDOUT THEN LSTUSAGE=LSTSIZE-1-RDOUT+RDIN; END FUNC LSTAVAIL I; BEGIN # ** LSTAVAIL - COMPUTE SPACE AVAILABLE IN READ LIST. * * LSTAVAIL COMPUTES THE NUMBER OF WORDS AVAILABLE FOR USE * IN THE RANDOM READ LIST. * * ENTRY - RDIN, RDOUT, LSTSIZE - CONTROL READ LIST. * * EXIT LSTAVAIL - RESULT. * * CALLS LSTUSAGE. # LSTAVAIL=LSTSIZE-1-LSTUSAGE; END PROC RDWBUF(LENGTH); BEGIN # ** RDWBUF - READ WORDS FROM BUFFER WITH NO I/O. * * RDWBUF FUNCTIONS AS A SPECIALIZED READW MACRO. A STANDARD * FET IS ASSUMED AND THE DATA TRANSFER WORKING BUFFER IS * ASSUMED TO BE MAPPED BY A STANDARD BASED ARRAY. MOST * IMPORTANT, THIS ROUTINE IS GUARANTEED TO NEVER CALL CIO. * THE CALLER IS RESPONSIBLE TO ASSURE THAT THE CIRCULAR * BUFFER HAS ENOUGH DATA TO SATISFY THE REQUEST. * * ENTRY LENGTH - AMMOUNT OF DATA NEEDED. * FET - FET AND CIRCULAR BUFFER HAVE ENOUGH DATA. * TOO - MAPPED TO USER'S WORKING BUFFER. * * EXIT FET - INDICATES DATA TRANSFERRED. * TOO - DATA IS TRANSFERRED. * * CALLS MOVEWD, TRAGIC, BUFUSAGE. * * USES FROM. # ITEM LENGTH, X1; IF BUFUSAGE LS LENGTH OR LENGTH LS 0 THEN BEGIN TRAGIC(" WORKFILE BUFFER EMPTY ON READ.$"); END P=FETOUT; IF FETIN GR FETOUT OR FETLIM-FETOUT GR LENGTH THEN BEGIN MOVEWD(LENGTH,FROM,TOO); FETOUT=FETOUT+LENGTH; END ELSE BEGIN X1=FETLIM-FETOUT; MOVEWD(X1,FROM,TOO); P=LOC(TOO)+X1; X1=LENGTH-X1; P=FETFIR; MOVEWD(X1,FROM,TOO); FETOUT=FETFIR+X1; END END PROC WTWBUF(LENGTH); BEGIN # ** WTWBUF - WRITE WORDS TO BUFFER WTH NO I/O. * * WTWBUF PERFORMS THE ROLE OF THE WRITEW MACRO, BUT IS * SPECIALIZED TO USE AN ASSUMED FET AND WORKING BUFFER, AND * IS GUARANTEED TO NEVER PERFORM ANY CIO CALLS. THE CALLER * IS RESPONSIBLE TO ASSURE THE CIRCULAR BUFFER HAS ROOM TO * ACCEPT THE DATA. * * ENTRY LENGTH - NUMBER OF WORDS. * FET - THE CIRCULAR BUFFER HAS ROOM. * FROM - MAPPED ONTO WORKING BUFFER. * * EXIT FET - THE CIRCULAR BUFFER HAS DATA. * * CALLS BUFAVAIL, TRAGIC, MOVEWD. * * USES TOO. # ITEM LENGTH, X1; IF BUFAVAIL LS LENGTH OR LENGTH LS 0 THEN BEGIN TRAGIC(" WORKFILE BUFFER FULL ON WRITE.$"); END P=FETIN; IF FETOUT GR FETIN OR FETLIM-FETIN GR LENGTH THEN BEGIN MOVEWD(LENGTH,FROM,TOO); FETIN=FETIN+LENGTH; END ELSE BEGIN X1=FETLIM-FETIN; MOVEWD(X1,FROM,TOO); P=LOC(FROM)+X1; X1=LENGTH-X1; P=FETFIR; MOVEWD(X1,FROM,TOO); FETIN=FETFIR+X1; END END PROC WAIT; IOBEGIN(WAIT) # ** WAIT - WAIT (RECALL) FOR I/O TO FINISH. * * WAIT DELAYS THIS TASK UNTIL THE FET IS FOUND TO INDICATE * COMPLETION OF A PREVIOUS I/O OPERATION. WAIT IS A NO-OP * IF THE FET ALREADY INDICATES COMPLETION. * * ENTRY FETDUN - SHOWS WHETHER DELAYS REALLY NEEDED. * * EXIT FETDUN - TRUE. * * CALLS RECALL. # IF NOT FETDUN THEN BEGIN RECALL(FET); END IOEND PROC DISOWNCACHE(PARM); BEGIN # ** DISOWNCACHE - RELEASE CACHE FRAME FROM OWNERSHIP. * * DISOWNCACHE IS USED TO RELEASE A PAGE FROM OWNERSHIP BY * A LEVEL OF THE TREE STRUCTURE. RELEASE OF A PAGE MAKES * IT ELIGIBLE TO BE FLUSHED OUT OF MEMORY (AND POSSIBLY * REWRITTEN BACK TO DISK) ANYTIME THE FLUSHCACHE ROUTINE * IS NEEDED. DISOWNCACHE ALSO SETS THE AGE FOR THE PAGE * SO THAT FLUSHCACHE CAN DO ANY REWRITES IN THE MANDATORY * ORDER OF RELEASE. THE GLOBAL AGE COUNTER IS INCREMENTED. * * ENTRY PARM - TREE LEVEL. * CACHEOWNED[PARM] - TRUE FOR NON-TRIVIAL OPERATON. * AGECOUNT - HIGHEST AGE YET ASSIGNED TO ANY PAGE. * * EXIT AGECOUNT - INCREMENTED. * CACHEOWNED[PARM] - FALSE. * CACHEAGE[PARM] - SETUP. # ITEM PARM; IF PARM LS 0 OR NOT CACHEOWNED[PARM] THEN RETURN; CACHEOWNED[PARM]=FALSE; AGECOUNT=AGECOUNT+1; CACHEAGE[PARM]=AGECOUNT; END # OF DISOWNCACHE # FUNC COUNTCACHE; BEGIN # ** COUNTCACHE - DETERMINE HOW MANY CACHE FRAMES ARE NOT USED. * * COUNTCACHE TELLS THE CALLER HOW MANY CACHE FRAMES ARE * NEITHER OWNED BY ANY TREE LEVEL NOR CONTAIN DATA, WHICH * HAS BEEN ALTERED IN THE CACHE BUT NOT YET ON DISK. * * ENTRY CACHEOWNED[ALL] - SETUP. * CACHEDIRTY[ALL] - SETUP. * * EXIT COUNTCACHE - RESULT. # ITEM I, J; J=0; FOR I=0 STEP 1 UNTIL MAXCACHE DO IF NOT (CACHEDIRTY[I] OR CACHEOWNED[I]) THEN J=J+1; COUNTCACHE=J; END # OF COUNTCACHE # PROC SEARCHCACHE(M); BEGIN # ** SEARCHCACHE - DETERMINE BEST CACHEFRAME TO TAKE OVER. * * SEARCHCACHE TESTS ALL CACHE FRAMES FOR THOSE WHICH ARE * AVAILABLE TO BE CLAIMED AND USED FOR DIFFERENT DATA THAN * PRESENTLY IN MEMORY. THE OLDEST AVAILABLE FRAME IS * CONSIDERED THE BEST CANDIDATE FOR RECYCLE. A FRAME * IS AVAILABLE IF IT IS NEITHER CURRENTLY OWNED BY ANY TREE * LEVEL NOR CONTAINS DATA ALTERED IN MEMORY BUT NOT ON DISK. * * ENTRY CACHEOWNED[ALL] - SETUP. * CACHEDIRTY[ALL] - SETUP. * CACHEAGE[ALL] - SETUP FOR UNOWNED, UNDIRTY FRAMES. * * EXIT M - CHOSEN FRAME. * * CALLS TRAGIC. # ITEM K,L,M; M=-1; K=999999999; FOR L=0 STEP 1 UNTIL MAXCACHE DO BEGIN IF CACHEAGE[L] LS K AND NOT (CACHEDIRTY[L] OR CACHEOWNED[L]) THEN BEGIN # SEARCH OLDEST AVAIL PAGE # M=L; K=CACHEAGE[L]; END END IF M LS 0 THEN TRAGIC(" ALL WORKFILE BUFFERS ARE FULL.$"); END # OF SEARCHCACHE # PROC ALLOCCACHE; BEGIN # ** ALLOCCACHE - LAY CLAIM TO A CACHE FRAME. * * ALLOCCACHE IS CALLED TO CLAIM A FRAME OF MEMORY IN THE * DISK CACHE, AND ATTACH IT TO A PARTICULAR TREE LEVEL. * THE DISK ADDRESS OF THE DISK DATA IS SAVED IN THE HEADER * WORD OF THE CACHE FRAME, SO IT CAN BE MATCHED FOR * PURPOSES OF AVOIDING DISK READS. * * ENTRY PL - CURRENT TREE LEVEL. * PADA[PL] - DISK ADDRESS ASSIGNED FOR REAL DATA. * * EXIT PACACHE[PL] - WHICH CACHE FRAME WAS SELECTED. * BFDA[PL] - DISK ADDRESS IS STORED IN CACHE FRAME. * * CALLS SEARCHCACHE. # ITEM TMP1; SEARCHCACHE(TMP1); PACACHE[PL]=TMP1; CACHEOWNED[TMP1]=TRUE; BFDA[TMP1]=PADA[PL]; END # OF ALLOCCACHE # PROC RECLAIMCACHE; BEGIN # ** RECLAIMCACHE - IDENTIFY CACHE FRAME WRONGLY RELEASED. * * RECLAIMCACHE IS USED TO MATCH A TREE LEVEL TO A CACHE * FRAME WHICH CONTAINS THE VIRTUAL DISK SECTOR FOR THE * TREE LEVEL, AND REESTABLISH OWNERSHIP. THIS IS DONE * BECAUSE THERE ARE SITUATIONS WHERE A PAGE MUST BE * TEMPORARILY RELEASED FROM OWNERSHIP SO IT CAN BE WRITTEN * TO DISK, BUT FORFEITURE OF OWNERSHIP IS NOT INTENDED * FOR ANY OTHER REASON. RECLAIMCACHE CAN BE CALLED AFTER * A SEQUENCE OF DISOWNCACHE AND FLUSHCACHE, AND THE TREE * LEVELS ARE THEN AS THEY WERE BUT DATA ALTERATIONS ARE * UP TO DATE ON DISK. REMEMBER THAT THE ORDER IN WHICH PAGES * CAN BE UPDATED TO DISK DEPENDS ON THE TREE HIERARCHY, SO * IT IS SOMETIMES NECESSARY TO GO THRU THIS SEQUENCE WITH ONE * TREE LEVEL IN ORDER TO ACHIEVE THE ACTUAL PURPOSE OF * WRITING A DIFFERENCE TREE LEVEL TO DISK. * * ENTRY PL - CURRENT TREE LEVEL. * PALVL - DEEPEST VALID TREE LEVEL. * CACHEOWNED[ALL], PACACHE[ALL], BFDA[ALL] - SETUP. * * EXIT CACHEOWNED[PL THRU PALVL] - TRUE. * * CALLS TRAGIC. * * USES TPL. # FOR TPL=PL STEP 1 UNTIL PALVL DO BEGIN IF CACHEOWNED[PACACHE[TPL]] THEN TEST TPL; IF BFDA[PACACHE[TPL]] NQ PADA[TPL] THEN BEGIN TRAGIC(" WORKFILE BUFFER CONTAINS WRONG TEXT.$"); END CACHEOWNED[PACACHE[TPL]]=TRUE; END END # OF RECLAIMCACHE # PAGE PROC MOVEIO; BEGIN # ** MOVEIO - INITIATE ANY CIO CALLS AS NEEDED. * * MOVEIO IS CALLED ANY TIME CIO CALLS ARE KNOWN TO BE * NECESSARY, AND CAN ALSO BE CALLED ANY TIME CIO CALLS MIGHT * BE DESIRABLE. MOVEIO BECOMES A NO-OP IF A CIO CALL IS * ALREADY IN PROGRESS, OR IF NO CIO CALL IS NEEDED. THE * CALLER IS RESPONSIBLE TO PREPARE THE FET AND CIRCULAR * BUFFER IN ALL WAYS EXCEPT THE CHOICE OF CIO FUNCTION CODE. * * WRITES ARE INITIATED WHEN THE IOWRITE FLAG HAS BEEN * PREPARED TO INDICATE THAT WRITES ARE NEEDED, THE FET IS NOT * PREVIOUSLY INTERLOCKED, AND THE CIRCULAR BUFFER CONTAINS * SOME DATA. READS ARE INITIATED WHEN THE IOREAD FLAG HAS * BEEN PREPARED TO INDICATE THAT READS ARE NEEDED, THE FET IS * NOT PREVIOUSLY INTERLOCKED, AND THE RDIN/RDOUT POINTERS * INDICATE THERE IS A NON-TRIVIAL LIST OF PRUS STILL AWAITING * READS. * * MOVEIO ALSO PERFORMS THE FUNCTION OF RECOGNIZING THAT ALL * DESIRED I/O HAS BEEN COMPLETED, SUCH AS ALL WRITEABLE DATA * REMOVED FROM CIRCULAR BUFFER OR ALL READABLE PRUS REMOVED * FROM READ LIST. UNDER THESE CONDITIONS, MOVEIO CLEARS THE * IOXXXX FLAGS SO THAT NO FURTHER DESIRE FOR CIO CALLS WILL * BE INDICATED, UNTIL AND UNLESS SOMEONE TURNS THE FLAGS ON * AGAIN. * * IN THE CASES WHERE THE CIRCULAR BUFFER CONTAINS DATA TO BE * WRITTEN, THE ACTUAL CIO FUNCTION CODE IS CHOSEN FROM FOUR * POSSIBILITIES BASED ON EXTENSION/ALTERATION OF THE FILE AND * NEED OR NON-NEED FOR END-OF-RECORD TO BE WRITTEN. WHEN THE * DISK IS TO BE READ, THE FUNCTION CODE IS ALWAYS "RPHRLS", * AKA "READ PRUS RANDOMLY BY LIST". * * NOTE THAT MOVEIO NEVER WAITS FOR ANY CIO TO COMPLETE, THUS * MOVEIO IS NOT A REENTRANT ROUTINE. * * ENTRY FETDUN - WHETHER ANY PREVIOUS I/O IS DONE. * IOACT - WHETHER I/O SHOULD BE DONE SOMETIME SOON. * IOWRITE - WHETHER I/O NEEDED IS A WRITE. * IOREAD - WHETHER I/O NEEDED IS A READ. * IOAPEND - WHETHER WRITE IS TO EXTEND FILE OR ALTER. * IOWRTEOR - WHETHER WRITE IS TO PRODUCE EOR. * FETIN, FETOUT - INDICATE WHETHER UNWRITTEN DATA IS * STILL IN THE BUFFER. * RDIN, RDOUT - INDICATE WHETHER THERE ARE PRUS * IDENTIFIED IN READ LIST AS DESIRABLE TO READ, * BUT NOT YET READ IN. * FETLA, FETLAW - CURRENT POSITION IN READ LIST. * RDLIST[ALL] - SETUP. * CIOCOUNT - ACCOUNTING ACCUMULATOR. * MAXDA, LASTDA - USED TO DETERMINE VALIDITY OF * WRITE PARAMETERS, WHEN CONSISTENCY CHECKING * IS ENABLED IN THE SOURCE CODE. * FETCRI - USED WITH MAXDA AND LASTDA FOR VALIDITY. * * EXIT FETDUN - FALSE IF A CIO CALL STARTED UP. * CIOCOUNT - INCREMENTED IF CIO CALLED. * IOACT, IOREAD, IOWRITE, IOAPEND - CLEARED IF AND * ONLY IF ALL MOVEIO DETERMINES THAT ALL * PREVIOUSLY REQUESTED I/O HAS COMPLETED. * * CALLS WRITE, REWRITE, WRITER, REWRITR, RPHRLS, TRAGIC. # ITEM I; SWITCH CALLWRITE WRTFUNC,RWRTFUNC,WRTRFUNC,RWRTRFUNC; IF IOACT AND FETDUN THEN BEGIN IF IOWRITE AND FETIN NQ FETOUT THEN # WRITE NOT DONE # BEGIN CIOCOUNT = CIOCOUNT + 1; I=1; IF IOAPEND THEN I=0; IF IOWRTEOR THEN I=I+2; IOWRTEOR=FALSE; GOTO CALLWRITE[I]; WRTFUNC: WRITE(FET,0); RETURN; RWRTFUNC: FETEP = TRUE; # SET ERROR PROCESSING BIT # REWRITE(FET,0); FETEP = FALSE; # CLEAR ERROR PROCESSING BIT # IF FETAT EQ O"22" THEN BEGIN # IF FATAL ERROR # TRAGIC(" WORKFILE BUFFER EMPTY ON REWRITE.$"); END RETURN; WRTRFUNC: WRITER(FET,0); RETURN; RWRTRFUNC: FETEP = TRUE; # SET ERROR PROCESSING BIT # REWRITR(FET,0); FETEP = FALSE; # CLEAR ERROR PROCESSING BIT # IF FETAT EQ O"22" THEN BEGIN # IF FATAL ERROR # TRAGIC(" WORKFILE BUFFER EMPTY ON REWRITER. $"); END RETURN; END ELSE IF IOREAD AND RDIN NQ RDOUT THEN # READ NOT DONE # BEGIN IF FETLA EQ LOC(RDLIST[RDLIM]) THEN FETLAW = LOC(RDLIST[0]); IF FETLA NQ LOC(RDLIST[RDIN]) THEN BEGIN CIOCOUNT = CIOCOUNT + 1; RPHRLS(FET,0); END END ELSE # READ AND WRITE DONE # BEGIN CONTROL IFGQ PARANOIA,5; IF IOWRITE THEN # CHECK WRITE CRI # BEGIN IF IOAPEND THEN BEGIN IF FETCRI NQ MAXDA+1 THEN BEGIN TRAGIC(" FILE SIZE INCORRECT.$"); END END ELSE BEGIN IF FETCRI NQ LASTDA+1 THEN BEGIN TRAGIC(" RANDOM ADDRESS INCORRECT.$"); END END END CONTROL FI; IOACT = FALSE; IOWRITE = FALSE; IOAPEND = FALSE; IOREAD = FALSE; END END END PAGE PROC PAUSEIO; IOBEGIN(PAUSEIO) # ** PAUSEIO - AWAIT AND/OR ENFORCE CIO COMPLETION. * * PAUSEIO IS CALLED FOR ONE OF THREE REASONS: (1) THE CALLER * HAS STARTED SOME I/O VIA MOVEIO AND NEEDS TO WAIT FOR IT TO * FINISH, OR (2) THE CALLER NEEDS SOME OLD I/O TO FINISH SO * THAT THE FET CAN BE USED TO INITIATE NEW DESIRED I/O, OR * (3) THE CALLER NEEDS SOME OLD I/O TO FINISH, TO FREE THE * CIRCULAR BUFFER AND READ LIST SO THOSE MEMORY RESOURCES CAN * BE MANAGED AND POSSIBLY RE-ASSIGNED. * * PAUSEIO THEREFORE GUARANTEES THAT ANY WRITEABLE DATA IN THE * CIRCULAR BUFFER REALLY GETS WRITTEN, AND THAT ANY INITIATED * READS ARE FINISHED WITH THE DATA DISCARDED AND THE CIRCULAR * BUFFER EMPTY. PAUSEIO IS ALLOWED TO HANDLE READS WHICH * HAVE BEEN REQUESTED BUT NOT YET INITIATED BY SIMPLE * CANCELLATION. THUS, PAUSEIO IS MEANINGFUL TO THE TYPE 1 * CALLER ONLY FOR THE WRITE DIRECTION - THE READ DIRECTION IS * A THROWAWAY WHILE THE WRITE DIRECTION HAS ASSURANCE OF * COMPLETION. * * SINCE THE TYPE 3 CALLER OFTEN DOES NOT KNOW HOW (OR IS NOT * ALLOWED) TO DETERMINE IF THE MEMORY RESOURCES ARE ALREADY * AVAILABLE FOR REASSIGNMENT, PAUSEIO CAN BE CALLED CASUALLY, * AND WILL NO-OP ITSELF IF THE DESIRED CONDITIONS ARE ALREADY * TRUE. * * PAUSEIO ALSO ZEROES OUT FETCHGUIDE. THIS IS USED TO CONTROL * THE QUANTITY OF DATA PREFETCHED AS A FUNCTION OF HOW MUCH * SUCCESS WE HAVE HAD IN UTILIZING PREFETCHED DATA IN RECENT * HISTORY. A PAUSEIO CALL IS REGARDED AS AN ADMISSION THAT * ANY CURRENT PREFETCHING HAS LOST ALL RELEVANCE. * * ENTRY IOREAD - WHETHER WE WERE PREVIOUSLY TRYING TO READ * SOMETHING WHICH MUST NOW BE THROWN AWAY. * IOWRITE - WHETHER WE HAD PREVIOUSLY REQUESTED THAT * SOMETHING BE WRITTEN, WHICH MUST NOW BE BROUGHT * TO FASTEST POSSIBLE COMPLETION. * RDIN, RDOUT - BRACKET ACTIVE SEGMENT OF READ LIST. * FETIN, FETOUT - BRACKET CIRCULAR BUFFER DATA. * * EXIT IOREAD, IOWRITE, IOACT - GUARANTEED FALSE. * FET - CIRCULAR BUFFER GUARANTEED EMPTY. * RDLIST[ALL], RDIN, RDOUT - GUARANTEED EMPTY. * FETCHGUIDE - ZEROED. * * CALLS WAIT, MOVEIO. # ITEM I, J, K; FETCHGUIDE=0; IF IOREAD THEN BEGIN WAIT; # FOR ACTIVE TRANSFER TO FINISH # FOR I = RDIN WHILE I NQ RDOUT DO # CLEAN OUT READLIST # BEGIN I = I - 1; IF I LS 0 THEN I = RDLIM - 1; RDLIST[I] = 0; END FETOUT = FETIN; # ABANDON CIRCULAR BUFFER CONTENT # IOACT = FALSE; IOREAD = FALSE; END ELSE IF IOWRITE THEN BEGIN WHYLE IOWRITE DO BEGIN MOVEIO; WAIT; END END IOEND PAGE PROC SETREAD((DA)); BEGIN # ** SETREAD - ADD DISK ADDRESS TO READ LIST. * * SETREAD PERFORMS FET INITIALIZATION AS NEEDED, TO ASSURE * THAT THE BUFFER AND FET ARE TURNED AROUND TO THE INPUT * DIRECTION, AND APPENDS THE SPECIFIED DISK ADDRESS ONTO THE * READ LIST. THE CALLER IS RESPONSIBLE TO ASSURE THAT * SETREAD WILL NOT ENCOUNTER A FULL READ LIST. THE CALLER IS * ALLOWED TO CALL SETREAD FOR AN ADDRESS ALREADY LISTED, IN * WHICH CASE SETREAD NO-OPS ITSELF. THE USER IS REQUIRED TO * PROVIDE AN IN-BOUNDS DISK ADDRESS, AND TO HAVE USED PAUSEIO * TO COMPLETE ANY PREVIOUS OUTPUT ACTIVITIES. * * ENTRY DA - SECTOR NUMBER FOR DESIRED PRU. * IOREAD - WHETHER FET ALREADY INITIALIZED FOR INPUT. * IOWRITE, MAXDA - WHEN CONSISTENCY CHECKING IS ENABLED, * THESE ARE USED TO VERIFY VALIDITY. * RDIN, RDOUT - BRACKET ACTIVE SEGMENT OF READ LIST. * RDLIST[ALL] - SETUP FOR EXISTING DISK ADDRESSES * AND WITH ZEROES IN EXCESS LIST AREAS. * * EXIT IOREAD, IOACT - TRUE. * FET AND CIRCULAR BUFFER - VALID FOR MOVEIO CALL. * RDIN, RDOUT - UPDATED FOR ENLARGED READ LIST. * RDLIST[NEW RDIN-1] - DISK ADDRESS STORED HERE. * * CALLS TRAGIC, INITFET. # ITEM DA; ITEM P; CONTROL IFGQ PARANOIA,5; IF DA LQ 0 OR DA GR MAXDA THEN BEGIN TRAGIC(" OUT OF BOUNDS PRU ADDRESS ON READ (1).$"); END IF IOWRITE THEN TRAGIC(" WORKFILE BUFFER FULL ON READ.$"); CONTROL FI; IF NOT IOREAD THEN BEGIN INITFET(DISK,DSKSIZ); RDIN = 0; RDOUT = 0; FETLAW = LOC(RDLIST[0]); IOACT = TRUE; IOREAD = TRUE; END FOR P = RDOUT WHILE RDLIST[P] NQ DA DO BEGIN IF P EQ RDIN THEN BEGIN RDLIST[RDIN] = DA; RDIN = RDIN + 1; IF RDIN EQ RDLIM THEN RDIN = 0; CONTROL IFGQ PARANOIA,5; IF RDIN EQ RDOUT THEN TRAGIC(" RPHRLS LIST BUFFER IS FULL.$"); CONTROL FI; END ELSE BEGIN P = P + 1; IF P EQ RDLIM THEN P = 0; END END END PAGE PROC PREFETCH((DD)); BEGIN # ** ARRANGE READ LIST FOR SEVERAL PREFETCHES. * * PREFETCH SHOULD BE CALLED ONCE PER SECTOR PROCESSED, BY * THOSE PROCESSES WHICH ACT UPON POTENTIALLY LARGE SERIES OF * DISK SECTORS IN EITHER THE FORWARD OR BACKWARD LOGICAL * ORDER OF TEXT IN THE FILE. THE LOGICAL ORDER CAN BE QUITE * DIFFERENT FROM THE PHYSICAL ORDER DUE TO THE TREE LINKAGES. * * PREFETCH DETERMINES SEVERAL DISK ADDRESSES WHOSE NEED IS * ANTICIPATED ON BEHALF OF THE CALLER, AND USES SETREAD TO * ARRANGE THAT SUCH SECTORS CAN BE READ IN THE LOGICAL ORDER. * IT IS THE CALLING PROCESSES RESPONSIBILITY TO OCCASIONALLY * CALL MOVEIO FOR ACTUAL INITIATION OF CIO. * * TO MINIMIZE OVERHEAD, PREFETCH CHOOSES A DEPTH OF * PREFETCHING BASED ON APPARENT RECENT SUCCESS IN UTILIZING * PREFETCHED DATA, AND SUCCESSIVELY UPDATES THE DEPTH OF * PREFETCH. THIS DEPTH INDICATOR MAY BE CLEARED BY PAUSEIO * IN RECOGNITION OF THE ABANDONMENT OF A READ, OR BY THE * CALLER WHEN A LOGICAL BREAK IN SEQUENTIAL PROCESSING IS * KNOWN TO OCCUR. * * TO MINIMIZE OVERHEAD, PREFETCH ATTEMPTS TO PRODUCE EITHER * ZERO OR SEVERAL NEW ENTRIES IN THE READ LIST, AND ATTEMPTS * TO NO-OP ITSELF WHEN IT IS RECOGNIZED THAT ONLY ONE OR A * FEW ENTRIES COULD BE PRODUCED. PREFETCH MUST KEEP THE * DEPTH OF PREFETCHING WITHIN THE LEGAL BOUNDS OF THE SETREAD * ROUTINE. * * THE DEPTH OF PREFETCHING IS LIMITED TO THE SMALLEST OF THE * FOLLOWING FACTORS - (1) THE FETCHGUIDE QUANTITY, WHICH * REPRESENTS RECENT SUCCESS, (2) THE LEGAL LIMITS IMPOSED BY * SETREAD, AND (3) ONE AND A HALF TIMES THE SIZE OF THE * CIRCULAR BUFFER. PREFETCHING IS SET UP ONLY WHEN THE * EXISTING QUEUE IS LESS THAN HALF THE DESIRED DEPTH. FOR * LARGER QUEUES, IT IS ANTICIPATED THE QUEUE WILL BE SHRUNK * BACK DOWN AT SOME FUTURE CALL TO PREFETCH, AT WHICH TIME * A LARGE BATCH OF QUEUE ENTIRES CAN BE PRODUCED. * * ONCE PREFETCH DECIDES TO SET UP A BATCH OF READ LIST QUEUE * ENTRIES, IT IDENTIFIES THE SECTORS TO BE READ AS ALL DATA * (BOTTOM) LEVEL PRUS, POINTED TO BY THE NEXT-TO-BOTTOM * DIRECTORY, LOGICALLY ADJACENT TO THE CURRENT DATA SECTOR IN * THE SPECIFIED DIRECTION. PREFETCH ALSO CAN QUEUE THE NEXT * LOGICALLY ADJACENT NEXT-TO-BOTTOM DIRECTORY, BY LOOKING * ANOTHER LEVEL TOWARDS THE ROOT OF THE TREE. * * ENTRY FETCHGUIDE - CURRENT GUIDELINE FOR DEPTH. * RDIN, RDOUT - DESCRIBE CURRENT READ LIST. * DSKSIZ - CIRCULAR BUFFER SIZE. * PALVL - DEEPEST TREE LEVEL CURRENTLY VALID. * PACACHE[ALL] - CACHE FRAMES AT EACH TREE LEVEL. * BFPRU[ALL] - DISK ADDRESSES FOR EACH FRAME. * PAFINAL[ALL] - LAST WORDS IN EACH SECTOR OF TREE. * IOWRITE - WHETHER FET INTERLOCKED FOR OUTPUT. * * EXIT RDLIST[ALL], RDIN, RDOUT - UPDATED. * FETCHGUIDE - INCREMENTED UNLESS DIRECTORY INCLUDED. * * CALLS LSTAVAIL, LSTUSAGE, MIN, MAX, SETREAD. * * USES P WITH RESTORATION. # ITEM DD,N; ITEM DSTOP; ITEM DONE B; ITEM TMPPRU; ITEM TMPPL,TMPD; ITEM I; IF FETCHGUIDE LS 0 OR IOWRITE THEN RETURN; FETCHGUIDE=FETCHGUIDE+2; N=LSTAVAIL; N=MIN(N-3,3*DSKSIZ/O"200"); N=MIN(N,FETCHGUIDE); IF LSTUSAGE LQ N/2 THEN BEGIN TMPPRU = P; DONE = FALSE; FOR TMPPL = PALVL-1 STEP -1 WHILE TMPPL GQ 0 AND NOT DONE DO BEGIN TMPD = PADISP[TMPPL] + DD; P = LOC(BFPRU[PACACHE[TMPPL]]); IF DD GR 0 THEN DSTOP = MIN(PAFINAL[TMPPL]+1,TMPD+N); ELSE DSTOP = MAX(0,TMPD-N); IF DSTOP EQ TMPD+N*DD THEN DONE = TRUE; ELSE FETCHGUIDE=-1; N=1; FOR TMPD = TMPD STEP DD WHILE TMPD NQ DSTOP DO BEGIN FOR I=0 STEP 1 UNTIL MAXCACHE DO BEGIN IF BFDA[I] EQ DIRDA[TMPD] THEN TEST TMPD; END SETREAD(DIRDA[TMPD]); END END P = TMPPRU; END END PAGE PROC READPA; IOBEGIN(READPA) # ** READPA - READ NEXT DEEPER TREE PATH SECTOR. * * READPA IS USED TO ACCESS THE SECTOR OF DIRECTORY OR DATA * FOR A SPECIFIED LEVEL OF THE TREE. THE HIGHER LEVELS OF * THE TREE MUST ALREADY BE PROPERLY FILLED IN. ANY RESIDUAL * SECTOR IN THE CURRENT TREE LEVEL MUST BE PROPERLY RELEASED * FROM OWNERSHIP, SO THAT READPA WILL NOT NEED TO CONCERN * ITSELF WITH THE UPDATING OF ALTERED DATA TO DISK. THE * CALLER DOES NOT NEED TO SETUP THE FET AND CIRCULAR BUFFER * FOR INPUT, AND READPA WILL TAKE CARE OF THAT IF NEEDED. * * WHEN POSSIBLE, READPA WILL ACCESS THE DESIRED DISK SECTOR * BY SIMPLY IDENTIFYING IT IN CACHE AND CLAIMING OWNERSHIP OF * THE RIGHT CACHE FRAME. WHEN THE CACHE DOES NOT PROVIDE THE * NECESSARY DATA, READPA WILL READ IT FROM DISK. IF THE FET * AND CIRCULAR BUFFER ARE PREVIOUSLY INTERLOCKED FOR PENDING * OUTPUT, READPA WILL ASSURE THAT THE UNFINISHED WRITES GET * DONE. IF THE FET IS ALREADY SETUP FOR INPUT BUT HAS THE * WRONG DISK ADDRESS COMING IN AT THE FRONT OF THE CIRCULAR * BUFFER, READPA WILL DISCARD SUCH INCORRECT INPUT AND * REINITIALIZE TO INPUT THE RIGHT DISK ADDRESS. IF THE FET * IS ALREADY SET UP FOR INPUT WITH THE RIGHT SECTOR IN OR * COMING INTO THE CIRCULAR BUFFER, READPA WILL MAKE USE OF * THAT CIO ACTIVITY. * * IF READPA READS THE SECTOR FROM DISK, IT CLEANS UP THE * RANDOM READ LIST AFTERWARDS, ATTEMPTING TO SHUFFLE THE * ACTIVE SEGMENT OF THE READ LIST TO MAXIMIZE THE REMAINING * LIST CAPACITY BEFORE WRAPAROUND. READPA ALSO CHECKS TO SEE * IF THE BUFFER IS SUFFICIENTLY EMPTY WITH A SUFFICIENTLY * FULL PREFETCH LIST SO THAT ANTICIPATORY READS SHOULD BE * STARTED. * * ENTRY TEMP - SECTOR NUMBER. * PL - CURRENT PATH LEVEL. * PACACHE[ALL] - CACHE FRAMES CURRENTLY OWNED. * CACHEOWNED[ALL] - SETUP. * BFDA[ALL] - IDENTIFY SECTOR ADDRESSES EACH FRAME. * BFHEAD[ALL] - HEADER WORDS FOR EACH CACHED SECTOR. * ACCTPRU - PRU TRANSFER COUNT. * IOWRITE - WHETHER FET ALREADY SET FOR WRITES. * RDIN, RDOUT - READ LIST BRACKETING. * RDLIST[ALL] - SETUP. * IOREAD - WHETHER SOME READ ALREADY SCHEDULED. * FETCHGUIDE - PREFETCH QUANTITY GUIDELINE. * QUARTER - APPROX ONE FOURTH BUFFER SIZE. * * EXIT PACACHE[PL] - INDICATES CACHE FRAME WITH SECTOR. * CACHEOWNED[PACACHE[PL]] - TRUE. * ANY OLD CACHE FRAMES MAY HAVE BEEN FLUSHED TO * DISK AND/OR REASSIGNED IN MEMORY. * PAHEAD[PL] - HEADER WORD. * IOWRITE - FALSE IF NOT CACHE HIT. * IOREAD - POSSIBLY CHANGED. * RDOUT - ADVANCED IF DISK READ DONE. * RDIN - RDIN AND RDOUT POSSIBLY RELOCATED IF READ * DONE AND CIO TERMINATED. * ACCTPRU - INCREMENTED IF NO CACHE HIT. * FETCHGUIDE - POSSIBLY NEW VALUE. * ANTICIPATORY CIO POSSIBLY CALLED. * * CALLS TRAGIC, COUNTCACHE, FLUSHCACHE, DISOWNCACHE, * WIOSTAT, PAUSEIO, ALLOCCACHE, SETREAD, MOVEIO, * DELAY, RECALL, RDWBUF, BUFUSAGE. * * USES P. # ITEM X1; CONTROL IFGQ PARANOIA,5; IF PADIRTY[PL] THEN TRAGIC(" ALTERED PRU WAS NOT REWRITTEN.$"); CONTROL FI; IF COUNTCACHE LQ 2 THEN FLUSHCACHE; # FIRST RELEASE CURRENTLY OWNED SECTOR IF ANY # DISOWNCACHE(PACACHE[PL]); FOR X1=0 STEP 1 UNTIL MAXCACHE DO BEGIN # TRY TO FIND IN CACHE # IF BFDA[X1] EQ TEMP THEN # GOT IT # BEGIN CONTROL IFGQ PARANOIA,5; IF CACHEOWNED[X1] THEN BEGIN TRAGIC(" WORKFILE BUFFER ALLOCATED TWICE.$"); END CONTROL FI; PACACHE[PL]=X1; CACHEOWNED[X1]=TRUE; PAHEAD[PL] = BFHEAD[X1]; CONTROL IFEQ MULTI,1; WIOSTAT(1); # ACCUMULATE CACHE HITS # CONTROL FI; IORET END END CONTROL IFEQ MULTI,1; WIOSTAT(2); # ACCUMULATE SECTORS READ # ACCTPRU=ACCTPRU+1; CONTROL FI; IF IOWRITE THEN # DO ANY PENDING WRITES # BEGIN PAUSEIO; CONTROL IFEQ MULTI,1; WIOSTAT(3); # ACCUMULATE FLUSHED WRITES # CONTROL FI; END CONTROL IFGQ PARANOIA,5; IF TEMP LQ 0 OR TEMP GR MAXDA THEN BEGIN TRAGIC(" OUT OF BOUNDS PRU ADDRESS ON READ (2).$"); END CONTROL FI; PADA[PL] = TEMP; ALLOCCACHE; IF IOREAD AND RDLIST[RDOUT] NQ TEMP THEN BEGIN PAUSEIO; CONTROL IFEQ MULTI,1; WIOSTAT(4); # ACCUMULATE READ REJECTS # CONTROL FI; END IF NOT IOREAD THEN BEGIN SETREAD(TEMP); FETCHGUIDE=2; END WHYLE FETIN EQ FETOUT DO BEGIN CONTROL IFEQ MULTI,1; WIOSTAT(10); # ACCUMULATE NOT ALREADY IN BUFFER # CONTROL FI; MOVEIO; CONTROL IFEQ MULTI,1; WAIT; CONTROL FI; CONTROL IFEQ SINGLE,1; IF NOT FETDUN THEN DELAY; CONTROL FI; END P = LOC(BFPRU[PACACHE[PL]]); RDWBUF(O"100"); PAHEAD[PL] = BFHEAD[PACACHE[PL]]; CONTROL IFGQ PARANOIA,5; IF TEMP NQ RDLIST[RDOUT] THEN BEGIN TRAGIC(" RANDOM READ LIST INCORRECT.$"); END IF PADA[PL] NQ TEMP THEN TRAGIC(" PRU CONTENT INCORRECT.$"); IF PADIRTY[PL] THEN BEGIN TRAGIC(" STATUS FLAGS SHOULD HAVE BEEN CLEARED.$"); END CONTROL FI; RDLIST[RDOUT] = 0; IF RDOUT EQ RDLIM-1 THEN RDOUT = 0; ELSE RDOUT = RDOUT + 1; IF FETDUN AND RDIN EQ RDOUT THEN BEGIN RDIN=0; RDOUT=0; FETCHGUIDE=ABS(FETCHGUIDE); END IF BUFUSAGE LQ QUARTER THEN MOVEIO; IOEND PAGE PROC ALLOC; BEGIN # ** ALLOC - ASSIGN DISK ADDRESS. * * ALLOC IS CALLED WHEN A NEW SECTOR MUST BE CREATED. THE * NEXT PRU OFF THE LOGICAL END OF THE FILE IS CHOSEN. NOTE * THAT THE LOGICAL END OF THE FILE REPRESENTS THOSE ADDRESSES * WHICH HAVE BEEN ALLOCATED. THE PHYSICAL END OF THE FILE * REPRESENTS THOSE ADDRESSES ACTUALLY WRITTEN ONTO THE FILE, * AND CAN BE A SMALLER NUMBER THAN THE LOGICAL END. * * ENTRY PL - CURRENT PATH LEVEL. * DANEXT - ALLOCATION THRESHOLD. * * EXIT PADA[PL] - SETUP. * DANEXT - INCREMENTED. * * CALLS TRAGIC. # CONTROL IFGQ PARANOIA,5; IF PADIRTY[PL] THEN TRAGIC(" OLD SECTOR MUST BE WRITTEN.$"); CONTROL FI; PADA[PL] = DANEXT; DANEXT = DANEXT + 1; END PROC DEALLOC; BEGIN # ** DEALLOC - DISCARD DISK SECTOR. * * DEALLOC IS USED TO DISCARD A DISK SECTOR WHICH CAN NO * LONGER BE USED. THIS CAN BE NEEDED WHEN A SECTOR HAS * OVERFLOWED AND HAS BEEN SPLIT INTO TWO NEW SECTORS. IF THE * ROOT SECTOR NEEDS TO BE SPLIT, A COMPLETE NEW TREE LEVEL IS * CREATED WITH NEW SECTORS, THE ROOT SECTOR IS MODIFIED TO * BECOME A DIRECTORY FOR THE NEW LEVEL, AND NO DEALLOCATION * IS NEEDED. IF A NON-ROOT SECTOR NEEDS TO BE SPLIT EXACTLY * AT (JUST AFTER) ITS EXISTING LAST FIELD, THEN THAT SECTOR * IS PRESERVED WITHOUT DEALLOCATION AND A NEW SECTOR IS * CREATED FOR THE OVERFLOW CONTENT. BUT IF A NON-ROOT SECTOR * MUST BE SPLIT SOMEWHERE IN THE MIDDLE OF ITS EXISTING * CONTENT, IT MUST BE DEALLOCATED AND BE REPLACED BY TWO * COMPLETELY NEW SECTORS. * * THE REASON FOR DEALLOCATION IS THAT NOTHING MUST BE ALLOWED * TO BE POINTED TO UNTIL AND UNLESS IT ALREADY EXISTS, AS * UPDATED TO DISK ALTHOUGH NOT NECESSARILY AS CACHED IN * MEMORY. SINCE A NON-ROOT SECTOR IS POINTED TO BE * HIGHER-LEVEL DIRECOTRIES, IT CANNOT BE TOTALLY REFORMATTED * IN PLACE, THEREFORE IT IS LEFT AS IS AND NEW SECTORS * REPLACE IT. THE DIRECTORIES WHICH POINT TO THE DISCARDED * SECTOR WILL EVENTUALLY BE UPDATED THEMSELVES. UNTIL THEN, * THOSE HIGHER DIRECTORIES MAY BE OUT OF DATE BUT AT LEAST * POINT TO A VALID DATA STRUCTURE. * * ENTRY PL - CURRENT TREE LEVEL. * * EXIT PADA[PL] - CLEARED AWAY. * * CALLS TRAGIC. # CONTROL IFGQ PARANOIA,5; IF PL EQ 0 OR PADA[PL] EQ 0 THEN BEGIN TRAGIC(" SECTOR CANNOT BE DEALLOCATED.$"); END CONTROL FI; PADA[PL] = 0; PADIRTY[PL] = FALSE; END PROC SETWRITE(PARM); BEGIN # ** SETWRITE - SCHEDULE EXTENSION/ALTERATION OF FILE. * * SETWRITE INITIALIZES THE FET FOR OUTPUT. IT SETS THE IOACT * FLAG TO INDICATE THAT I/O IS SCHEDULED, SETS IOWRITE TO * INDICATE THAT THE I/O SHALL BE AN OUTPUT FUNCTION, AND * SETS OR CLEARS IOAPEND ACCORDING TO WHETHER THE SECTOR TO * BE WRITTEN IS WITHIN THE CURRENT PHYSICAL BOUNDARIES OF THE * FILE OR REPRESENTS AN EXTENSION OF PHYSICAL SIZE. * * ENTRY PARM - DISK ADDRESS TO BE WRITTEN. * MAXDA - CURRENT PHYSICAL SIZE OF FILE. * * EXIT FET - INITIALIZED WITH EMPTY CIRCULAR BUFFER. * IOACT - TRUE. * IOWRITE - TRUE. * IOAPEND - TRUE OR FALSE BASED ON PARM AND MAXDA. * LASTDA - SET TO KEEP TRACK OF WHERE WE ARE IN FILE. * FETRR - SETUP. * * CALLS INITFET. # ITEM PARM; INITFET(DISK,DSKSIZ); IOACT = TRUE; IOWRITE = TRUE; IF PARM GR MAXDA THEN BEGIN IOAPEND = TRUE; FETRR = LOC(DUMB); END ELSE BEGIN IOAPEND = FALSE; FETRR = PARM; LASTDA = PARM - 1; END END PAGE PROC TRYWRITE(PARM,FLAG); BEGIN # ** TRYWRITE - TRANSMIT SECTOR TO CIRCULAR BUFFER. * * TRYWRITE ATTEMPTS TO TRANSMIT A SECTOR TO THE CIRCULAR * BUFFER. IT MAY FAIL TO DO SO IF THE CIRCULAR BUFFER ALREADY * CONTAINS SECTORS FOR WHICH THIS SECTOR IS NOT CONTIGUOUS. * THE CALLER SHOULD CALL PAUSEIO WHEN TRYWRITE FAILS, AND * THEN CALL THIS ROUTINE AGAIN. TRYWRITE CANNOT FAIL IF * PAUSEIO IS USED. * * ENTRY P - POINTS TO TEXT OF SECTOR. * PARM - DISK ADDRESS. * MAXDA - PHYSICAL FILE SIZE. * LASTDA - MOST RECENT DISK ADDRESS TRANSMITTED. * * * EXIT P - POSSIBLY DESTROYED. * MAXDA - INCREMENTED IF FILE EXTENDED. * LASTDA - INCREMENTED IF CONTIGUOUS EARLIER OUTPUT. * FET - GUARANTEED SETUP FOR OUTPUT WITH THIS SECTOR * OF TEXT IN CIRCULAR BUFFER. EXISTING FET SETUP * AND CIRCULAR BUFFER CONTENT, IF ANY, ARE NOT * DISTURBED WITH REGARD TO EARLIER OUTPUT. * FLAG - TRUE IF THIS SECTOR TRANSMITTED. FALSE IF * UNABLE TO TRANSMIT DUE TO DISCONTIGUITY WITH * EARLIER OUTPUT. * IOACT, IOWRITE - TRUE. * IOAPEND - SETUP. * * CALLS SETWRITE, WTWBUF. # ITEM PARM, FLAG B, ZERO = 0; # ZERO IS A CONSTANT # FLAG = TRUE; IF NOT IOWRITE THEN SETWRITE(PARM); IF IOAPEND THEN BEGIN IF MAXDA+1 LS PARM THEN # ADD PADDING SECTOR # BEGIN MAXDA = MAXDA + 1; P=LOC(ZERO); WTWBUF(O"100"); END ELSE IF MAXDA+1 EQ PARM THEN # ADD REAL SECTOR # BEGIN MAXDA = MAXDA+1; WTWBUF(O"100"); END ELSE BEGIN FLAG = FALSE; END END ELSE BEGIN IF LASTDA+1 EQ PARM AND PARM LQ MAXDA THEN BEGIN LASTDA = LASTDA+1; WTWBUF(O"100"); END ELSE BEGIN FLAG = FALSE; END END END PAGE PROC FLUSHCACHE; IOBEGIN(FLUSHCACHE) # ** FLUSHCACHE - UPDATE ALTERED CACHE FRAMES TO DISK. * * FLUSHCACHE IS OBLIGATED TO ASSURE THAT ALL ALTERED, UNOWNED * CACHE PAGES BECOME UNALTERED IN CACHE. THIS REQUIRES THAT * SUCH PAGES BE TRANSMITTED TO THE CIRCULAR BUFFER. THIS IN * TURN CAN BE DONE ONLY IN ORDER OF AGE AND IN ADDRESS ORDER. * * UNALTERED FRAMES DO NOT NEED TO BE UPDATED TO DISK. OWNED * FRAMES CANNOT YET BE UPDATED - IN THE AGE-RESTRICTED ORDER * OF UPDATE, OWNED PAGES CAN BE THOUGHT OF AS HAVING THE * WORST AGE CATEGORY. * * EACH SECTOR CAN BE TRANSMITTED TO THE CIRCULAR BUFFER IF * THE BUFFER AND FET ARE AVAILABLE FOR OUTPUT, AND IF THE * BUFFER IS EITHER EMPTY OR HAS ONE OR MORE SECTORS ALREADY IN * IT FOR WHICH THE NEW SECTOR IS CONTIGOUS IN ADDRESS. IF THE * FET IS ALREADY CLAIMED FOR INPUT PROCESSING, PAUSEIO CAN BE * CALLED TO STOP AND DISCARD SUCH INPUT. FOR SPANNED * ADDRESSES, WE MUST CALL PAUSEIO. THIS LOGIC IS PROVIDED BY * INSPECTING THE FLAG RETURNED BY TRYWRITE, AND CALLING * TRYWRITE A SECOND TIME WITH PAUSEIO, WHEN TRYWRITE FAILED. * * BECAUSE THE ALLOCATION OF NEW SECTORS AT THE LOGICAL END OF * THE FILE CAN RUN SEVERAL UNITS AHEAD OF THE ACTUAL * EXTENDING OF THE PHYSICAL END OF THE FILE, AND BECAUSE THE * AGE-DEPENDENT ORDERING MIGHT REQUIRE A HIGH ADDRESS SECTOR * TO BE WRITTEN BEFORE A LOWER ADDRESS SECTOR MAY BE LEGALLY * WRITTEN, A SITUATION CAN ARISE WHERE A SECTOR MUST BE * WRITTEN SEVERAL ADDRESSES BEYOND THE CURRENT PHYSICAL END * OF FILE. WHEN THIS OCCURS, FLUSHCACHE RECOGNIZES THE * SITUATION AND PADS THE PHYSICAL END OUT TO WITHIN ONE * SECTOR OF THE SECTOR THAT MUST BE NEXT WRITTEN. THE * INTERMEDIATE SECTORS THUS GET A DISK IMAGE WHICH IS NOT UP * TO DATE IN ANY WAY WHATSOEVER. THIS IS NOT A PROBLEM - THE * UP TO DATE FRAMES FOR SUCH PADDING WILL EVENTUALLY BE * RELEASED FOR ACTUAL DISK UPDATE. THE CONTENT OF PADDING * SECTORS IS INNOCUOUS, TO FOLLOW THE RULE THAT NOTHING CAN * BE POINTED TO ON DISK UNTIL THE OBJECT ITSELF IS UP TO DATE * ON DISK. * * ENTRY CACHEDIRTY[ALL] - SETUP. * CACHEAGE[ALL] - SETUP. * CACHEOWNED[ALL] - SETUP. * BFDA[ALL] - SETUP. * IOREAD - WHETHER OBSOLETE INPUT SCHEDULED. * IOACT - WHETHER ANY PREVIOUS I/O SCHEDULED. * FETDUN - FET INTERLOCK. * IOAPEND - WHETHER A PREVIOUS WRITE EXTENDS. * QUARTER - APPROX ONE FOURTH BUFFER CAPACITY. * ACCTPRU - SECTOR TRANSFER ACCUMULATOR. * MAXDA - FILE PHYSICAL BOUNDARY. * * EXIT ALL ALTERED AND UNOWNED CACHE FRAMES OUTPUT TO DISK * OR BUFFERED FOR OUTPUT. * IOREAD - FORCED FALSE IF ANY OUTPUT NEEDED. * IOACT, IOWRITE, IOAPEND - POSSIBLY TRUE. * CACHEDIRTY[ALL] - FALSE WHERE CACHEOWNED IS FALSE. * ACCTPRU - INCREMENTED FOR ANY WORK DONE. * MAXDA - INCREMENTED IF FILE EXTENDED. * * CALLS WIOSTAT, PUSHTEMP, POPTEMP, PAUSEIO, BUFAVAIL, * MOVEIO, WAIT, DELAY, TRYWRITE, TRAGIC. * * USES P, TEMP(RESTORED). # ITEM TMP1, TMP2, BOOL B; CONTROL IFEQ MULTI,1; WIOSTAT(5); # ACCUMULATE CACHE FLUSHES # CONTROL FI; PUSHTEMP; FLUSHLOOP: TMP1=999999999; TEMP=-1; FOR TMP2=0 STEP 1 UNTIL MAXCACHE DO BEGIN # SEARCH FOR OLDEST DIRTY # IF CACHEDIRTY[TMP2] AND CACHEAGE[TMP2] LS TMP1 AND NOT CACHEOWNED[TMP2] THEN BEGIN TEMP=TMP2; TMP1=CACHEAGE[TMP2]; END END IF TEMP LS 0 THEN # NO MORE FLUSH NEEDED # BEGIN POPTEMP; IORET END CACHEDIRTY[TEMP]=FALSE; IF BFDA[TEMP] EQ 0 THEN GOTO FLUSHLOOP; IF IOREAD THEN # COMPLETE ANY INPUT # BEGIN PAUSEIO; CONTROL IFEQ MULTI,1; WIOSTAT(6); # ACCUMULATE READ REJECTS # CONTROL FI; END WRITELOOP: WHYLE IOACT AND BUFAVAIL LS O"100" DO BEGIN # NEED ROOM IN BUFFER # MOVEIO; CONTROL IFEQ MULTI,1; WAIT; CONTROL FI; CONTROL IFEQ SINGLE,1; IF NOT FETDUN THEN DELAY; CONTROL FI; END CONTROL IFEQ MULTI,1; WIOSTAT(7); # ACCUMULATE SECTORS WRITTEN # ACCTPRU=ACCTPRU+1; CONTROL FI; P=LOC(BFPRU[TEMP]); TRYWRITE(BFDA[TEMP],BOOL); IF NOT BOOL THEN BEGIN # ONE FAILURE ALLOWED, NOT TWO # PAUSEIO; CONTROL IFEQ MULTI,1; WIOSTAT(8); # ACCUMULATE DISCONTIGUOUS WRITES # CONTROL FI; P=LOC(BFPRU[TEMP]); TRYWRITE(BFDA[TEMP],BOOL); IF NOT BOOL THEN TRAGIC(" UNABLE TO WRITE FROM BUFFER.$"); END # EITHER ADD MORE PADDING FOR THIS FLUSH OR FLUSH ANOTHER # IF IOAPEND AND MAXDA LS BFDA[TEMP] THEN BEGIN CONTROL IFEQ MULTI,1; WIOSTAT(9); # ACCUMULATE PADDING SECTORS # CONTROL FI; GOTO WRITELOOP; END IF BUFAVAIL LQ QUARTER THEN MOVEIO; GOTO FLUSHLOOP; IOEND # OF FLUSHCACHE # PROC CLOSEOUT; IOBEGIN(CLOSEOUT) # ** CLOSEOUT - FINISH UPDATE OF DISK. * * WORKFILE CLOSEOUT CONDITIONS HAVE BEEN DETECTED. UNDER * CLOSEOUT CONDITIONS, WE MUST WRITE NOT ONLY THE ZERO-LEVEL * SECTOR, BUT ALSO THE DATA SEGMENT (RIGHT AFTER ROOT SECTOR) * FOLLOWED BY EOR THEN ARRAY SEGMENT THEN A SECOND EOR. * * THE CLOSEOUT ROUTINE WRITES THE TWO BINARY SEGMENTS, BUT * REQUIRES THAT THE CALLER FIRST FLUSH ALL TREE STRUCTURE * SECTORS TO DISK OR AT LEAST TO THE CIRCULAR BUFFER. THE * CALLER SHOULD USE THE CLEAN ROUTINE WITH PL=0 TO MEET THAT * REQUIREMENT. THE CLOSEOUT ROUTINE DEPENDS ON THE RULE THAT * THE ROOT SECTOR OF THE TREE STRUCTURE MUST BE THE LAST * (CHRONOLOGICALLY) TO BE FREED FOR UPDATE TO DISK, AND * DEPENDS ON THE RULE THAT THE ROOT SECTOR IS AT DISK ADDRESS * 1 AND THEREFORE IS CONTIGUOUS WITH NO PREVIOUSLY RELEASED * SECTORS. THUS, CLOSEOUT EXPECTS THAT ALL SECTORS EXCEPT * THE ROOT HAVE BEEN ACTUALLY WRITTEN TO DISK, AND THE ROOT * SECTOR IS EXPECTED TO BE SCHEDULED FOR UPDATE BUT STILL IS * PRESENT IN THE CIRCULAR BUFFER. * * CLOSEOUT INTERCEPTS THE ISSUANCE OF CIO FUNCTION CODES FOR * THE UPDATE OF THE ROOT SECTOR, AND AFTER APPENDING THE * SCALAR DATA SEGMENT ONTO THE ROOT SECTOR IN THE CIRCULAR * BUFFER, CLOSEOUT CALLS PAUSEIO (AND THUS MOVEIO) SUCH THAT * ONE WRITE WITH END OF RECORD OCCURS. THEN CLOSEOUT * CONSTRUCTS A NEW CIRCULAR BUFFER IMAGE FOR THE ARRAY * SEGMENT, AND USES PAUSEIO A SECOND TIME TO WRITE ANOTHER * MULTIPLE SECTOR RECORD. * * ENTRY CLEAN HAS BEEN CALLED WITH PL=0. * IOAPEND - WHETHER FILE PRESENTLY EMPTY. * LASTDA, MAXDA - LOGICAL AND PHYSICAL FILE SIZES. * DATALEN, ARRAYLEN - LENGTHS OF BINARY SEGMENTS. * DATAPRUS, ARRAYPRUS - LENGTHS IN SECTORS. * * EXIT FILE COMPLETELY DRAINED TO DISK, FET INACTIVE. * LASTDA, MAXDA - ONE OF THESE TWO IS INCREMENTED. * * CALLS FLUSHCACHE, WTWBUF, PAUSEIO, INITFET. * * USES P, IOWRITE, IOWRTEOR, IOAPEND, IOACT. # FLUSHCACHE; # PUT SECTOR 1 (ROOT PRU) INTO BUFFER # IOWRTEOR=TRUE; # WRITE ROOT PRU PLUS DATA SEGMENT # P=LOC(DATASTART); WTWBUF(DATALEN); IF IOAPEND THEN MAXDA=MAXDA+DATAPRUS; ELSE LASTDA=LASTDA+DATAPRUS; PAUSEIO; INITFET(DISK,DSKSIZ); # NOW SET UP FOR ARRAYS # IOACT=TRUE; IOWRITE=TRUE; IF FETCRI GR MAXDA THEN BEGIN IOAPEND=TRUE; FETRR=LOC(DUMB); MAXDA=MAXDA+ARRAYPRUS; END ELSE BEGIN IOAPEND=FALSE; FETRR=FETCRI; LASTDA=LASTDA+ARRAYPRUS; END IOWRTEOR=TRUE; P=LOC(ARRAYSTART); WTWBUF(ARRAYLEN); PAUSEIO; IOEND # OF CLOSEOUT # PAGE PROC CLEAN; IOBEGIN(CLEAN) # ** CLEAN - DISCONNECT SECTOR FROM LOWEST TREE LEVEL. * * CLEAN IS CALLED WHENEVER A ROUTINE WISHES TO DISCARD THE * SECTOR PRESENTLY ASSOCIATED WITH A TREE LEVEL. THIS CAN BE * DONE ONLY IN THE REVERSE ORDER OF THE TREE HIERARCHY, IE, * THE BOTTOM LEVEL FIRST AND THE ROOT LEVEL LAST. THE EFFECT * OF CALLING CLEAN IS TO DISCONNECT THE SECTOR FROM THE TREE * STRUCTURE BY FREEING THE CACHE FRAME FROM OWNERSHIP, FIRST * DISCONNECTING ANY TREE LEVELS LOWER THAN THE CURRENT LEVEL. * * IF THE TREE LEVEL WAS PREVIOUSLY FLAGGED AS ALTERED, THE * FREED CACHE FRAME IS SIMILARLY FLAGGED. ALL HEADER WORD * DATA IS COPIED FROM THE TREE CONTROL VECTOR TO THE SECTOR * IMAGE IN CACHE. * * ENTRY PL - LEVEL TO BE FREED. * PALVL - DEEPEST ACTIVE TREE LEVEL. * PAHEAD[ALL] - HEADER WORDS IN TREE VECTOR. * PACACHE[ALL] - SETUP. * CACHEOWNED[ALL] - SETUP. * PADIRTY[ALL] - ALTERATION FLAG. * * EXIT THIS LEVEL AND ALL DEEPER LEVELS FREED. * CACHEOWNED[ANY] - FORCED TRUE FOR THOSE FREED. * CACHEDIRTY[ANY] - FORCED TRUE FOR THOSE FREED * WITH PADIRTY TRUE. * CACHEAGE[ANY] - INITIALIZED FOR THOSE FREED. * * CALLS DROPIT(INTERNAL), DISOWNCACHE. * * USES TPL. # PROC DROPIT; BEGIN IF CACHEOWNED[PACACHE[TPL]] THEN BFHEAD[PACACHE[TPL]]=PAHEAD[TPL]; DISOWNCACHE(PACACHE[TPL]); END # OF DROPIT # # START OF MAIN CODE # IF PADIRTY[PL] THEN BEGIN FOR TPL = PALVL STEP -1 UNTIL PL DO BEGIN IF PADIRTY[TPL] THEN BEGIN PADIRTY[TPL]=FALSE; CACHEDIRTY[PACACHE[TPL]]=TRUE; DROPIT; END ELSE DROPIT; END END ELSE BEGIN FOR TPL=PALVL STEP -1 UNTIL PL DO DROPIT; END IOEND PROC CLEANALL; IOBEGIN(CLEANALL) # ** CLEANALL - CLEAN ENTIRE TREE STRUCTURE AND DATA SEGEMENTS. * * CLEANALL IS USED TO INITIATE CLOSEOUT OF THE WORKFILE * MANAGER. THE ENTIRE TREE STRUCTURE IS CLEANED WITH ANY * ALTERED SECTORS WRITTEN TO DISK, AND THE SCALAR AND ARRAY * DATA SEGMENTS ARE UPDATED. * * ENTRY CACHEOWNED[ALL] - SETUP. * PACACHE[ALL] - SETUP. * PADIRTY[ALL] - SETUP. * PAHEAD[ALL] -SETUP. * PALVL - DEEPEST TREE LEVEL. * * EXIT PL - ZERO. * CACHEDIRTY[ALL] - FALSE. * FET AND CIRCULAR BUFFER - COMPLETE/EMPTY. * PADIRTY[ALL] - FALSE. * * CALLS CLEAN, CLOSEOUT, RECLAIMCACHE. # PADIRTY[0]=TRUE; PL=0; CLEAN; CLOSEOUT; RECLAIMCACHE; IOEND # OF CLEANALL # PAGE PROC BK; IOBEGIN(BK) # ** BK - BACK UP ONE LINE. * * BK IS ORGANIZED INTO THREE OPERATIONAL PHASES. THE FIRST * PHASE DETERMINES WHETHER THE CURRENT TREE PATH ALREADY * CONTAINS THE SECTOR WITH THE DESIRED LINE OF TEXT, AND IF * NOT, IT CLEANS AWAY AS MANY TREE LEVELS AS NECESSARY, FROM * THE BOTTOM TOWARDS THE ROOT, UNTIL WE ARE IN A DIRECTORY * WHOSE SPHERE OF INFLUENCE INCLUDES THE DESIRED LINE. * * THE SECOND PHASE ASSURES THAT WE HAVE A FULL DEPTH TREE * STRUCTURE CONTAINING THE RIGHT SECTOR AT THE DATA (BOTTOM) * LEVEL. IF THE FIRST PHASE WAS NON-TRIVIAL, THEN THE SECOND * PHASE WILL ALSO HAVE REAL WORK TO PERFORM, WORKING BACK * TOWARD DEEP TREE LEVELS, READING IN EACH SECTOR NEEDED. * THE SECOND PHASE IS A NO-OP IF THE FIRST PHASE WAS A NO-OP * AND IF THE TREE ALREADY EXISTS ALL THE WAY DOWN TO THE DATA * LEVEL. HOWEVER, THE FIRST PHASE COULD HAVE BEEN A NO-OP * FOR AN INCOMPLETE TREE PATH, IN WHICH CASE THE SECOND PHASE * MUST DEEPEN THE TREE PATH. * * THE THIRD PHASE SCANS THE DATA SECTOR TO FIND THE RIGHT * LINE IMAGE. THE INTERNAL LINE IMAGE FORMAT USES THE SIGN * BIT TO INDICATE WORDS WHICH ARE THE LAST WORDS OF LINES. * * ENTRY CURRENT - CURRENT LINE. * PALVL - DEEPEST TREE LEVEL. * ALL PATH AND CACHE CONTROLS SETUP. * P - WHERE TO PUT LINE OF TEXT. * DISP, CURNW - DESCRIBE WORD OFFSET AND LENGTH OF * PREVIOUS CURRENT LINE. * * EXIT CURRENT - DECREMENTED. * MVLNBUF - CONTAINS LINE OF TEXT. * PALVL - NEW VALUE FOR DEEPEST TREE LEVEL. * ALL PATH CONTROLS - MAY CONTROL DIFFERENT SECTORS * FOR DEEPER TREE LEVELS. * ALL CACHE CONTROLS - MAY CONTROL DIFFERENT SECTORS. * FET, CIRCULAR BUFFER, READ LIST - MAY CONTROL * A PREFETCH OPERATION. * CURNW - SIZE OF NEW LINE. * DISP - OFFSET OF NEW LINE. * * CALLS TRAGIC, CLEAN, PUSHTEMP, READPA, POPTEMP, PREFETCH, * MOVELN. * * USES TEMP(RESTORED), P, PL. # IF CURRENT LQ 0 THEN TRAGIC(" BAK BEFORE START OF FILE.$"); CURRENT = CURRENT - 1; FOR PL = PALVL WHILE PAFIRST[PL] GR CURRENT DO BEGIN CLEAN; PL = PL - 1; END P = LOC(BFPRU[PACACHE[PL]]); DISP = PADISP[PL] - 1; CONTROL IFGQ PARANOIA,5; IF DISP LQ 0 THEN TRAGIC(" DIRECTORY BLOCK POINTER TOO SMALL.$"); CONTROL FI; PADISP[PL] = DISP; IF PADIR[PL] THEN BEGIN FOR PL = PL WHILE PADIR[PL] DO BEGIN PL = PL + 1; PALVL = PL; PUSHTEMP; TEMP=DIRDA[DISP]; READPA; POPTEMP; PAFIRST[PL] = CURRENT - DIRNL[DISP] + 1; PALAST[PL] = CURRENT; P = LOC(BFPRU[PACACHE[PL]]); DISP = PAFINAL[PL]; PADISP[PL] = DISP; END PREFETCH(-1); END # NOTE THIS CODE REQUIRES INTERNAL CHARSET USAGE OF SIGN BIT # FOR DISP = DISP - 1 WHILE DISP NQ 0 AND B<0,1>DATWORD[DISP] EQ 0 DO DISP = DISP - 1; PADISP[PL] = DISP + 1; P = LOC(BFPRU[PACACHE[PALVL]]) + PADISP[PALVL]; CURNW = MOVELN(FROM,MVLNBUF); IOEND PAGE PROC FW; IOBEGIN(FW) # ** FW - ADVANCE ONE LINE. * * FW IS ORGANIZED INTO THREE OPERATIONAL PHASES. THE FIRST * PHASE DETERMINES WHETHER THE CURRENT TREE PATH ALREADY * CONTAINS THE SECTOR WITH THE DESIRED LINE OF TEXT, AND IF * NOT, IT CLEANS AWAY AS MANY TREE LEVELS AS NECESSARY, FROM * THE BOTTOM TOWARDS THE ROOT, UNTIL WE ARE IN A DIRECTORY * WHOSE SPHERE OF INFLUENCE INCLUDES THE DESIRED LINE. * * THE SECOND PHASE ASSURES THAT WE HAVE A FULL DEPTH TREE * STRUCTURE CONTAINING THE RIGHT SECTOR AT THE DATA (BOTTOM) * LEVEL. IF THE FIRST PHASE WAS NON-TRIVIAL, THEN THE SECOND * PHASE WILL ALSO HAVE REAL WORK TO PERFORM, WORKING BACK * TOWARD DEEP TREE LEVELS, READING IN EACH SECTOR NEEDED. * THE SECOND PHASE IS A NO-OP IF THE FIRST PHASE WAS A NO-OP * AND IF THE TREE ALREADY EXISTS ALL THE WAY DOWN TO THE DATA * LEVEL. HOWEVER, THE FIRST PHASE COULD HAVE BEEN A NO-OP * FOR AN INCOMPLETE TREE PATH, IN WHICH CASE THE SECOND PHASE * MUST DEEPEN THE TREE PATH. * * THE THIRD PHASE IDENTIFIES THE CORRECT LINE IMAGE WITHIN * THE DATA SECTOR. THE MECHANISM USED DEPENDS ON WHETHER THE * SECOND PHASE WAS ACTUALLY PERFORMED, SINCE FOR A NO-OP THE * DISP AND CURNW PARAMETERS FOR THE PREVIOUS CURRENT LINE * PROVIDE ALL INFORMATION REQUIRED TO ADVANCE ONE LINE. * * ENTRY CURRENT - CURRENT LINE. * PALVL - DEEPEST TREE LEVEL. * ALL PATH AND CACHE CONTROLS SETUP. * P - WHERE TO PUT LINE OF TEXT. * DISP, CURNW - DESCRIBE WORD OFFSET AND LENGTH OF * PREVIOUS CURRENT LINE. * * EXIT CURRENT - INCREMENTED. * MVLNBUF - CONTAINS LINE OF TEXT. * PALVL - NEW VALUE FOR DEEPEST TREE LEVEL. * ALL PATH CONTROLS - MAY CONTROL DIFFERENT SECTORS * FOR DEEPER TREE LEVELS. * ALL CACHE CONTROLS - MAY CONTROL DIFFERENT SECTORS. * FET, CIRCULAR BUFFER, READ LIST - MAY CONTROL * A PREFETCH OPERATION. * CURNW - SIZE OF NEW LINE. * DISP - OFFSET OF NEW LINE. * * CALLS TRAGIC, CLEAN, PUSHTEMP, READPA, POPTEMP, PREFETCH, * MOVELN. * * USES TEMP(RESTORED), P, PL. # IF CURRENT GQ PALAST[0] THEN TRAGIC(" FWD BEYOND END OF FILE.$"); CURRENT = CURRENT + 1; FOR PL = PALVL WHILE PALAST[PL] LS CURRENT DO BEGIN CLEAN; PL = PL - 1; END P = LOC(BFPRU[PACACHE[PL]]); DISP = PADISP[PL]; IF PADIR[PL] THEN BEGIN DISP = DISP + 1; CONTROL IFGQ PARANOIA,5; IF DISP GR PAFINAL[PL] THEN BEGIN TRAGIC(" DIRECTORY BLOCK POINTER TOO LARGE (1).$"); END CONTROL FI; PADISP[PL] = DISP; FOR PL = PL WHILE PADIR[PL] DO BEGIN PL = PL + 1; PALVL = PL; PUSHTEMP; TEMP=DIRDA[DISP]; READPA; POPTEMP; PAFIRST[PL] = CURRENT; PALAST[PL] = CURRENT + DIRNL[DISP] - 1; P = LOC(BFPRU[PACACHE[PL]]); DISP = 1; PADISP[PL] = 1; END PREFETCH(+1); END ELSE PADISP[PL] = DISP + CURNW; P = LOC(BFPRU[PACACHE[PALVL]]) + PADISP[PALVL]; CURNW = MOVELN(FROM,MVLNBUF); IOEND PAGE PROC POSR; IOBEGIN(POSR) # ** POSR - POSITION TO A RANDOMLY SELECTED LINE. * * POSR IS ORGANIZED INTO THREE OPERATIONAL PHASES. THE FIRST * PHASE DETERMINES WHETHER THE CURRENT TREE PATH ALREADY * CONTAINS THE SECTOR WITH THE DESIRED LINE OF TEXT, AND IF * NOT, IT CLEANS AWAY AS MANY TREE LEVELS AS NECESSARY, FROM * THE BOTTOM TOWARDS THE ROOT, UNTIL WE ARE IN A DIRECTORY * WHOSE SPHERE OF INFLUENCE INCLUDES THE DESIRED LINE. * * THE SECOND PHASE ASSURES THAT WE HAVE A FULL DEPTH TREE * STRUCTURE CONTAINING THE RIGHT SECTOR AT THE DATA (BOTTOM) * LEVEL. IF THE FIRST PHASE WAS NON-TRIVIAL, THEN THE SECOND * PHASE WILL ALSO HAVE REAL WORK TO PERFORM, WORKING BACK * TOWARD DEEP TREE LEVELS, READING IN EACH SECTOR NEEDED. * THE SECOND PHASE IS A NO-OP IF THE FIRST PHASE WAS A NO-OP * AND IF THE TREE ALREADY EXISTS ALL THE WAY DOWN TO THE DATA * LEVEL. HOWEVER, THE FIRST PHASE COULD HAVE BEEN A NO-OP * FOR AN INCOMPLETE TREE PATH, IN WHICH CASE THE SECOND PHASE * MUST DEEPEN THE TREE PATH. * * THE THIRD PHASE SCANS THE DATA SECTOR TO FIND THE RIGHT * LINE IMAGE. THE INTERNAL LINE IMAGE FORMAT USES THE SIGN * BIT TO INDICATE WORDS WHICH ARE THE LAST WORDS OF LINES. * * ENTRY NEWCURL - DESIRED LINE. * PALVL - DEEPEST TREE LEVEL. * ALL PATH AND CACHE CONTROLS SETUP. * P - WHERE TO PUT LINE OF TEXT. * DISP, CURNW - DESCRIBE WORD OFFSET AND LENGTH OF * PREVIOUS CURRENT LINE. * * EXIT CURRENT - MATCHES NEWCURL. * MVLNBUF - CONTAINS LINE OF TEXT. * PALVL - NEW VALUE FOR DEEPEST TREE LEVEL. * ALL PATH CONTROLS - MAY CONTROL DIFFERENT SECTORS * FOR DEEPER TREE LEVELS. * ALL CACHE CONTROLS - MAY CONTROL DIFFERENT SECTORS. * FET, CIRCULAR BUFFER, READ LIST - MAY CONTROL * A PREFETCH OPERATION. * CURNW - SIZE OF NEW LINE. * DISP - OFFSET OF NEW LINE. * * CALLS TRAGIC, CLEAN, PUSHTEMP, READPA, POPTEMP, PREFETCH, * MOVELN. * * USES TEMP(RESTORED), P, PL. # IF NEWCURL LS 0 OR NEWCURL GR PALAST[0] THEN BEGIN TRAGIC(" LINE NOT FOUND IN FILE.$"); END PUSHTEMP; CURRENT = NEWCURL; FOR PL=PALVL WHILE NOT(CURRENT GQ PAFIRST[PL] AND CURRENT LQ PALAST[PL]) DO BEGIN CLEAN; PL = PL - 1; END P = LOC(BFPRU[PACACHE[PL]]); FOR PL = PL WHILE PADIR[PL] DO BEGIN TEMP = PAFIRST[PL]; FOR DISP = 1 WHILE CURRENT GQ TEMP+DIRNL[DISP] DO BEGIN TEMP = TEMP + DIRNL[DISP]; DISP = DISP + 1; END CONTROL IFGQ PARANOIA,5; IF DISP GR PAFINAL[PL] THEN BEGIN TRAGIC(" DIRECTORY BLOCK POINTER TOO LARGE (2).$"); END CONTROL FI; PADISP[PL] = DISP; PL = PL + 1; PALVL = PL; PAFIRST[PL] = TEMP; PALAST[PL] = TEMP + DIRNL[DISP] - 1; PUSHTEMP; TEMP=DIRDA[DISP]; READPA; POPTEMP; P = LOC(BFPRU[PACACHE[PL]]); END TEMP = PAFIRST[PL]; FOR DISP = 1 WHILE CURRENT NQ TEMP DO BEGIN # NOTE THIS CODE REQUIRES INTERNAL CHARSET USAGE OF SIGN BIT # IF B<0,1>DATWORD[DISP] NQ 0 THEN TEMP = TEMP + 1; DISP = DISP + 1; END CONTROL IFGQ PARANOIA,5; IF DISP GR PAFINAL[PL] THEN BEGIN TRAGIC(" DIRECTORY BLOCK POINTER TOO LARGE (3).$"); END CONTROL FI; PADISP[PL] = DISP; P = LOC(BFPRU[PACACHE[PALVL]]) + PADISP[PALVL]; CURNW = MOVELN(FROM,MVLNBUF); POPTEMP; IOEND PAGE PROC FWD; IOBEGIN(FWD) # ** FWD - EXTERNAL INTERFACE FOR FORWARD MOVEMENT. * * FWD INTERFACES TO THE FW ROUTINE TO ADVANCE ONE LINE * FROM THE CURRENT POSITION. FWD ALSO CONTROLS THE BASE * ADDRESS FOR MVLNBUF AND ACCUMULATES STATISTICS. * * ENTRY P - WHERE TO PUT TEXT OR ZERO FOR INVISIBLE * POSITIONING FUNCTION. * CURRENT - CURRENT LINE. * * EXIT CURRENT - INCREMENTED. * LINEBUF - TEXT MOVE HERE IF BASE ADDRESS NONZERO. * * CALLS FW. * * USES P. # CONTROL IFEQ MULTI,1; WIOSTAT(0); # ACCUMULATE LINE ACCESES # CONTROL FI; P=LOC(LINEBUF); FW; P=0; IOEND PROC BAK; IOBEGIN(BAK) # ** FWD - EXTERNAL INTERFACE FOR BACKWARD MOVEMENT. * * BAK INTERFACES TO THE BK ROUTINE TO REGRESS ONE LINE * FROM THE CURRENT POSITION. BAK ALSO CONTROLS THE BASE * ADDRESS FOR MVLNBUF AND ACCUMULATES STATISTICS. * * ENTRY P - WHERE TO PUT TEXT OR ZERO FOR INVISIBLE * POSITIONING FUNCTION. * CURRENT - CURRENT LINE. * * EXIT CURRENT - DECREMENTED. * LINEBUF - TEXT MOVE HERE IF BASE ADDRESS NONZERO. * * CALLS BK. * * USES P. # CONTROL IFEQ MULTI,1; WIOSTAT(0); # ACCUMULATE LINE ACCESES # CONTROL FI; P=LOC(LINEBUF); BK; P=0; IOEND PROC POS; IOBEGIN(POS) # ** POS - EXTERNAL INTERFACE FOR RANDOM MOVEMENT. * * POS INTERFACES TO THE POSR ROUTINE TO GO TO ANY LINE * FROM THE CURRENT POSITION. POS ALSO CONTROLS THE BASE * ADDRESS FOR MVLNBUF AND ACCUMULATES STATISTICS. * * ENTRY P - WHERE TO PUT TEXT OR ZERO FOR INVISIBLE * POSITIONING FUNCTION. * NEWCURL - DESIRED LINE. * CURRENT - PREVIOUS CURRENT LINE. * * EXIT CURRENT - MATCHES NEWCURL. * LINEBUF - TEXT MOVE HERE IF BASE ADDRESS NONZERO. * * CALLS POSR. * * USES P. # CONTROL IFEQ MULTI,1; WIOSTAT(0); # ACCUMULATE LINE ACCESES # CONTROL FI; P=LOC(LINEBUF); IF NEWCURL EQ CURRENT-1 THEN BK; ELSE IF NEWCURL EQ CURRENT+1 THEN FW; ELSE IF NEWCURL NQ CURRENT THEN POSR; ELSE # JUST READOUT SAME LINE # BEGIN P = LOC(BFPRU[PACACHE[PALVL]]) + PADISP[PALVL]; DUMB = MOVELN(FROM,LINEBUF); END P=0; IOEND # OF POS # PAGE PROC SPLITTOP; IOBEGIN(SPLITTOP) # ** SPLITTOP - SPLIT ROOT SECTOR TO MAKE NEW LEVEL. * * SPLITTOP IS USED WHEN THE ROOT SECTOR OVERFLOWS. THE ROOT * SECTOR MUST ALWAYS EXIST AT DISK ADDRESS 1, THUS THE METHOD * USED TO SPLIT IT IS DIFFERENT FROM THAT USED FOR ANY OTHER * SECTOR. THE EXISTING CONTENTS OF THE ROOT SECTOR ARE * PLACED IN TWO NEW SECTORS, AND THE ROOT SECTOR IS * RECONSTRUCTED AS A DIRECTORY POINTING TO THESE TWO NEW * SECTORS. THUS THE OVERALL TREE STRUCTURE IS DEEPENED BY * ONE LEVEL. * * HOWEVER, SPLITTOP DOES NOT PROVIDE ALL THIS ALGORITHM. * SPLITTOP DOES PRODUCE A NEW TREE LEVEL WITH A REBUILT ROOT, * BUT STOPS SHORT OF FITTING THE OVERFLOWED TEXT INTO A PAIR * OF SECTORS. SPLITTOP PLACES THE PRE-OVERFLOW SECTOR * CONTENT INTO A SINGLE SECTOR AT THE NEW INTERMEDIATE TREE * LEVEL, AND ESTABLISHES THIS AS THE CURRENT TREE PATH LEVEL. * THE CALLER IS THEN RESPONSIBLE TO USE ONE OF THE OTHER * SECTOR SPLITTING ALGORITHMS TO GET THE TEXT ACTUALLY SPLIT * INTO TWO SECTORS. THESE OTHER ALGORITHMS WILL UPDATE THE * NEW ROOT TO POINT TO THE OVERFLOW SECTOR. * * ENTRY PL - MUST EQUAL ZERO. * PADEPTH[0] - INDICATE HIGH WATER MARK OF TREE DEPTH. * PALVL - CURRENT DEEPEST TREE LEVEL ON PATH. * ALL PATH CONTROLS - SETUP. * CACHE - AT LEAST ONE FRAME MUST BE AVAILABLE TO * BE CLAIMED. * * EXIT PL - ONE. * PALVL - INCREMENTED. * PADEPTH[0] - INCREMENETED. * ALL PATH CONTROLS - SHUFFLED TO DEEPER LEVELS. * CACHE - ONE FRAME ALLOCATED FOR NEW LEVEL 0. * ROOT SECTOR CACHE FRAME - BUILT AS DIRECTORY WITH * ONE ENTRY POINTING TO INTERMEDIATE SECTOR. * PATH LEVEL 1 - BUILT AS INTERMEDIATE TREE LEVEL * USING SAME CACHE FRAME AS FORMER ROOT LEVEL, * BUT ASSIGNED A NEWLY ALLOCATED DISK ADDRESS. * MARKED AS ALTERED SO NEW DISK ADDRESS CAN BE * EVENTUALLY WRITTEN TO DISK. * * CALLS TRAGIC, ALLOC, ALLOCCACHE, MOVEWD. * * USES P. # IF PADEPTH[0] GQ MAXDEPTH THEN TRAGIC(" FILE TOO LARGE.$"); PADEPTH[0] = PADEPTH[0] + 1; FOR PL = PALVL STEP -1 UNTIL 0 DO # SLIDE PATH UP # BEGIN PAFIRST[PL+1] = PAFIRST[PL]; PALAST[PL+1] = PALAST[PL]; PADISP[PL+1] = PADISP[PL]; PAHEAD[PL+1] = PAHEAD[PL]; PACACHE[PL+1] = PACACHE[PL]; END PL = 1; # STEP UP PL & PALVL # PALVL = PALVL + 1; PATOP[1] = FALSE; # FIX UP OLD TOP # PADIRTY[1] = FALSE; PADA[1] = 0; ALLOC; ALLOCCACHE; PADIRTY[1] = TRUE; P = LOC(BFPRU[PACACHE[0]]) + 1; P = LOC(BFPRU[PACACHE[1]]) + 1; MOVEWD(PAFINAL[0],FROM,TOO); PADATA[0] = FALSE; # BUILD NEW TOP # PADIR[0] = TRUE; PADIRTY[0] = TRUE; PAFINAL[0] = 1; P = LOC(BFPRU[PACACHE[0]]); DIRDA[1] = PADA[1]; DIRNL[1] = PALAST[1] - PAFIRST[1] + 1; PADISP[0] = 1; IOEND PAGE PROC SPLITAPEND; IOBEGIN(SPLITAPEND) # ** SPLITAPEND - SPLIT SECTOR JUST AFTER CURRENT CONTENT. * * SPLITAPEND IS USED TO OVERFLOW ANY NON-ROOT SECTOR UNDER * THE CONDITION THAT THE OVERFLOW TEXT BELONGS JUST AFTER ALL * OF THE TEXT ALREADY CONTAINED IN THE SECTOR. THUS, THE * SECTOR REMAINS JUST AS IT IS, AND A NEW SECTOR IS * CONSTRUCTED WHICH CONTAINS PRECISELY THE OVERFLOW TEXT. * SINCE NO EXISTING TEXT LEAVES THE SECTOR, THERE IS NO NEED * TO DISCARD IT AND BUILD TWO COMPLETELY NEW SECTORS. THUS, * SPLITAPEND IS MORE EFFICIENT THAN SPLITBEFORE OR * SPLITAFTER. * * THE NEWLY BUILT SECTOR BECOMES THE CURRENT SECTOR FOR THIS * LEVEL OF THE TREE. THE ORIGINAL SECTOR IS RELEASED FROM * OWNERSHIP. * * SPLITAPEND REQUIRES THAT AT LEAST ONE CACHE FRAME IS * AVAILABLE FOR ALLOCATION. * * SPLITAPEND FINISHES ITS PROCESSING BY SETTING UP PARAMETERS * FOR THE POSSIBLE CALLING OF ANY SPLIT ROUTINE FOR THE NEXT * HIGHER LEVEL. NOTE THAT ALL SPLIT ROUTINES ANTICIPATE THAT * THE CALLER WILL ITERATE SO LONG AS PARAMETERS INDICATE THAT * MORE SPLITTING NEEDS TO BE DONE. SINCE EACH SPLIT ROUTINE * DECREMENTS THE CURRENT TREE LEVEL, THE EFFECT IS TO WORK * FROM THE BOTTOM OF THE TREE TOWARDS THE ROOT, SPLITTING * UNTIL A LEVEL IS REACHED WHERE EVERYTHING FITS. * * ENTRY P - LOCATION OF OVERFLOW TEXT. * NWNEW - NUMBER OF WORDS IN OVERFLOW TEXT. * PL - CURRENT LEVEL IN TREE PATH. * ALL PATH CONTROLS SETUP. * ALL CACHE CONTROLS SETUP. * * EXIT PL - DECREMENTED. * SECTOR AT ORIGINAL TREE LEVEL REBUILT TO CONTAIN * OVERFLOW TEXT. * PREVIOUS SECTOR AT ORIGINAL TREE LEVEL RELEASED. * NWNEW, NEWBEFORE, NWAFTER - SET FOR ITERATION. * NEWDA, NEWNL - SET FOR ITERATION. * DISP, PADISP[NEW PL] - INCREMENTED FOR ITERATION. * * CALLS ALLOC, ALLOCCACHE, MOVEWD. * * USES P. # CLEAN; # CLEAN OLD BLOCK # ALLOC; # BUILD NEW BLOCK # ALLOCCACHE; P = LOC(BFPRU[PACACHE[PL]]) + 1; MOVEWD(NWNEW,NEW,TOO); PAFINAL[PL] = NWNEW; PADIRTY[PL] = TRUE; PADISP[PL] = 1; # FIX UP PATH # PAFIRST[PL] = FIRST; PALAST[PL] = LAST; NEWDA[0] = PADA[PL]; # BUILD NEW DIR ENTRY # NEWNL[0] = PALAST[PL] - PAFIRST[PL] + 1; NWNEW = 1; PL = PL - 1; # SET UP NEXT PASS # NWBEFORE = PADISP[PL]; NWAFTER = PAFINAL[PL] - PADISP[PL]; DISP = PADISP[PL] + 1; PADISP[PL] = DISP; IOEND PAGE PROC SPLITBEFORE; IOBEGIN(SPLITBEFORE) # ** SPLITBEFORE - SPLIT SECTOR JUST BEFORE INSERTION POINT. * * SPLITBEFORE IS USED TO OVERFLOW ANY NON-ROOT SECTOR UNDER * THE CONDITION THAT THE OVERFLOW TEXT MUST BE INSERTED * SOMEWHERE IN THE MIDDLE OF THE TEXT ALREADY CONTAINED IN * THE SECTOR, AND NEW TEXT WOULD NOT FIT WITHIN THE FIRST * SECTOR OF THE NEW PAIR. THUS, THE SECTOR IS SPLIT JUST IN * FRONT OF THE INSERTION POINT, AND THE SECOND SECTOR * RESULTING FROM THE SPLIT WILL START WITH THE OVERFLOW TEXT * AND CONTINUE WITH THE REMAINING TEXT SPLIT OUT OF THE * ORIGINAL SECTOR. * * SINCE SOME EXISTING TEXT MUST BE REMOVED FROM THE CURRENT * SECTOR, IT IS NOT POSSIBLE TO KEEP USING THE SAME SECTOR, * UNDER THE RULE THAT NOTHING CAN BE POINTED TO WITHOUT FIRST * EXISTING ON DISK. THEREFORE, TWO COMPLETELY NEW SECTORS * ARE CREATED AND THE ORIGINAL SECTOR BECOMES A FRAGMENT. * SINCE THE INSERTED TEXT GOES INTO THE SECOND SECTOR OF THE * NEW PAIR, THAT IS THE SECTOR WHICH IS LEFT AS THE CURRENT * SECTOR FOR THIS TREE PATH LEVEL. * * SPLITBEFORE REQUIRES THAT AT LEAST ONE CACHE FRAME IS * AVAILABLE FOR ALLOCATION. THE EXISTING CACHE FRAME FOR * THIS TREE LEVEL IS RECYCLED TO PROVIDE THE SECOND SECTOR OF * CAPACITY. THE EXISTING CACHE FRAME IS FIRST SUBJECTED TO * THE NORMAL "CLEAN" ROUTINE TO ASSURE THAT ANY PENDING * CHANGES ARE FLUSHED TO DISK. * * SPLITBEFORE FINISHES ITS PROCESSING BY SETTING UP PARAMETERS * FOR THE POSSIBLE CALLING OF ANY SPLIT ROUTINE FOR THE NEXT * HIGHER LEVEL. NOTE THAT ALL SPLIT ROUTINES ANTICIPATE THAT * THE CALLER WILL ITERATE SO LONG AS PARAMETERS INDICATE THAT * MORE SPLITTING NEEDS TO BE DONE. SINCE EACH SPLIT ROUTINE * DECREMENTS THE CURRENT TREE LEVEL, THE EFFECT IS TO WORK * FROM THE BOTTOM OF THE TREE TOWARDS THE ROOT, SPLITTING * UNTIL A LEVEL IS REACHED WHERE EVERYTHING FITS. * * ENTRY P - LOCATION OF OVERFLOW TEXT. * NWNEW - NUMBER OF WORDS IN OVERFLOW TEXT. * PL - CURRENT LEVEL IN TREE PATH. * ALL PATH CONTROLS SETUP. * ALL CACHE CONTROLS SETUP. * * EXIT PL - DECREMENTED. * SECTOR AT ORIGINAL TREE LEVEL REBUILT TO CONTAIN * OVERFLOW TEXT AND SPLIT-OFF ORGINAL TEXT. * PREVIOUS SECTOR AT ORIGINAL TREE LEVEL RELEASED. * NWNEW, NEWBEFORE, NWAFTER - SET FOR ITERATION. * NEWDA, NEWNL - SET FOR ITERATION. * DISP, PADISP[NEW PL] - INCREMENTED FOR ITERATION. * * CALLS ALLOC, ALLOCCACHE, MOVEWD, LAST. * * USES P. # P = LOC(BFPRU[PACACHE[PL]]) + 1; # SAVE OLD DATA # MOVEWD(PAFINAL[PL],FROM,OBF); DEALLOC; # FREE UP OLD BLOCK # ALLOC; # BUILD OFF BLOCK # P = LOC(BFPRU[PACACHE[PL]]) + 1; MOVEWD(NWBEFORE,OBF,TOO); PAFINAL[PL] = NWBEFORE; ODA = PADA[PL]; # REMEMBER THIS # ONL = FIRST - PAFIRST[PL]; PADIRTY[PL] = TRUE; # GET RID OF OFF BLOCK # CLEAN; ALLOC; # BUILD NEW BLOCK # ALLOCCACHE; P = LOC(BFPRU[PACACHE[PL]]) + 1; MOVEWD(NWNEW,NEW,TOO); P = LOC(OBF) + DISP-1; P = LOC(BFPRU[PACACHE[PL]]) + NWNEW + 1; MOVEWD(NWAFTER,FROM,TOO); PAFINAL[PL] = NWNEW + NWAFTER; PADIRTY[PL] = TRUE; PADISP[PL] = PADISP[PL] - NWBEFORE; # FIX UP PATH # PAFIRST[PL] == FIRST; PALAST[PL] = PALAST[PL] + NL; LAST = PALAST[PL]; NEWDA[0] = ODA; # BUILD NEW DIR ENTRIES # NEWNL[0] = ONL; NEWDA[1] = PADA[PL]; NEWNL[1] = PALAST[PL] - PAFIRST[PL] + 1; NWNEW = 2; PL = PL - 1; # SET UP NEXT PASS # NWBEFORE = PADISP[PL] - 1; NWAFTER = PAFINAL[PL] - PADISP[PL]; DISP = PADISP[PL] + 1; PADISP[PL] = DISP; IOEND PAGE PROC SPLITAFTER; IOBEGIN(SPLITAFTER) # ** SPLITAFTER - SPLIT SECTOR JUST AFTER INSERTION POINT. * * SPLITAFTER IS USED TO OVERFLOW ANY NON-ROOT SECTOR UNDER * THE CONDITION THAT THE OVERFLOW TEXT MUST BE INSERTED * SOMEWHERE IN THE MIDDLE OF THE TEXT ALREADY CONTAINED IN * THE SECTOR, AND NEW TEXT WOULD FIT WITHIN THE FIRST * SECTOR OF THE NEW PAIR. THUS, THE SECTOR IS SPLIT JUST * BEYOND THE INSERTED TEXT, AND THE SECOND SECTOR * RESULTING FROM THE SPLIT WILL START WITH THE REMAINING * TEXT SPLIT OUT OF THE ORGINAL SECTOR. * * SINCE SOME EXISTING TEXT MUST BE REMOVED FROM THE CURRENT * SECTOR, IT IS NOT POSSIBLE TO KEEP USING THE SAME SECTOR, * UNDER THE RULE THAT NOTHING CAN BE POINTED TO WITHOUT FIRST * EXISTING ON DISK. THEREFORE, TWO COMPLETELY NEW SECTORS * ARE CREATED AND THE ORIGINAL SECTOR BECOMES A FRAGMENT. * SINCE THE INSERTED TEXT GOES INTO THE FIRST SECTOR OF THE * NEW PAIR, THAT IS THE SECTOR WHICH IS LEFT AS THE CURRENT * SECTOR FOR THIS TREE PATH LEVEL. * * SPLITAFTER REQUIRES THAT AT LEAST ONE CACHE FRAME IS * AVAILABLE FOR ALLOCATION. THE EXISTING CACHE FRAME FOR * THIS TREE LEVEL IS RECYCLED TO PROVIDE THE SECOND SECTOR OF * CAPACITY. THE EXISTING CACHE FRAME IS FIRST SUBJECTED TO * THE NORMAL "CLEAN" ROUTINE TO ASSURE THAT ANY PENDING * CHANGES ARE FLUSHED TO DISK. * * SPLITAFTER FINISHES ITS PROCESSING BY SETTING UP PARAMETERS * FOR THE POSSIBLE CALLING OF ANY SPLIT ROUTINE FOR THE NEXT * HIGHER LEVEL. NOTE THAT ALL SPLIT ROUTINES ANTICIPATE THAT * THE CALLER WILL ITERATE SO LONG AS PARAMETERS INDICATE THAT * MORE SPLITTING NEEDS TO BE DONE. SINCE EACH SPLIT ROUTINE * DECREMENTS THE CURRENT TREE LEVEL, THE EFFECT IS TO WORK * FROM THE BOTTOM OF THE TREE TOWARDS THE ROOT, SPLITTING * UNTIL A LEVEL IS REACHED WHERE EVERYTHING FITS. * * ENTRY P - LOCATION OF OVERFLOW TEXT. * NWNEW - NUMBER OF WORDS IN OVERFLOW TEXT. * PL - CURRENT LEVEL IN TREE PATH. * ALL PATH CONTROLS SETUP. * ALL CACHE CONTROLS SETUP. * * EXIT PL - DECREMENTED. * SECTOR AT ORIGINAL TREE LEVEL REBUILT TO CONTAIN * OVERFLOW TEXT AND SPLIT-OFF ORGINAL TEXT. * PREVIOUS SECTOR AT ORIGINAL TREE LEVEL RELEASED. * NWNEW, NEWAFTER, NWAFTER - SET FOR ITERATION. * NEWDA, NEWNL - SET FOR ITERATION. * DISP, PADISP[NEW PL] - INCREMENTED FOR ITERATION. * * CALLS ALLOC, ALLOCCACHE, MOVEWD, CLEAN, DEALLOC. * * USES P, FIRST, LAST. # P = LOC(BFPRU[PACACHE[PL]]) + 1; # SAVE OLD DATA # MOVEWD(PAFINAL[PL],FROM,OBF); DEALLOC; # FREE UP OLD BLOCK # ALLOC; # BUILD OFF BLOCK # P = LOC(OBF) + DISP-1; P = LOC(BFPRU[PACACHE[PL]]) + 1; MOVEWD(NWAFTER,FROM,TOO); PAFINAL[PL] = NWAFTER; ODA = PADA[PL]; # REMEMBER THIS # ONL = PALAST[PL] + NL - LAST; PADIRTY[PL] = TRUE; # GET RID OF OFF BLOCK # CLEAN; ALLOC; # BUILD NEW BLOCK # ALLOCCACHE; P = LOC(BFPRU[PACACHE[PL]]) + 1; MOVEWD(NWBEFORE,OBF,TOO); P = LOC(BFPRU[PACACHE[PL]]) + NWBEFORE + 1; MOVEWD(NWNEW,NEW,TOO); PAFINAL[PL] = NWBEFORE + NWNEW; PADIRTY[PL] = TRUE; FIRST = PAFIRST[PL]; # FIX UP PATH # LAST == PALAST[PL]; LAST = LAST + NL; NEWDA[0] = PADA[PL]; # BUILD NEW DIR ENTRIES # NEWNL[0] = PALAST[PL] - PAFIRST[PL] + 1; NEWDA[1] = ODA; NEWNL[1] = ONL; NWNEW = 2; PL = PL - 1; # SET UP NEXT PASS # NWBEFORE = PADISP[PL] - 1; NWAFTER = PAFINAL[PL] - PADISP[PL]; DISP = PADISP[PL] + 1; IOEND PAGE PROC CHANGE; # CHANGE TREE # IOBEGIN(CHANGE) # ** CHANGE - APPLY CHANGES TO TREE STRUCTURE. * * CHANGE IS THE PRINCIPAL ALGORITHM USED TO MANIPULATE THE * TREE STRUCTURE. CHANGE IS DESIGNED TO BE CALLED BY THE * INS, REP, AND DEL ENTRY POINTS. THE ALGORITHM WORKS IN * THREE PHASES. THE FIRST PHASE TAKES CARE OF CASES WHERE AN * INSERTION OF A LINE OR THE REPLACEMENT OF A LINE WITH AN * ENLARGED VERSION WILL CAUSE THE CURRENT DATA SECTOR (THE * BOTTOM LEVEL OF THE TREE STRUCTURE) TO OUTGROW THE SECTOR * CAPACITY AND CAUSE SPLITTING INTO TWO SECTORS. THIS CAUSES * ADDITIONAL DIRECTORY ENTRIES TO BE REQUIRED IN THE HIGHER * TREE LEVELS, WHICH CAN IN TURN CAUSE ONE OR MORE DIRECTORY * LEVELS TO BE SPLIT INTO MULTIPLE SECTORS. IN THE EXTREME * CASE, THE ROOT LEVEL OF THE TREE MAY OVERFLOW, IN WHICH * CASE A NEW INTERMEDIATE LEVEL OF THE TREE MUST BE CREATED, * WITH THE ROOT SECTOR CONVERTED INTO A DIRECTORY FOR THE * INTERMEDIATE LEVEL. * * AFTER THE FIRST PHASE HAS EXHAUSTED ITS ITERATION, THE * SECOND PHASE TAKES OVER, IDENTIFYING ANY TREE LEVEL * STARTING WITH THE DATA LEVEL WHICH HAS BECOME EMPTY AS THE * RESULT OF DELETIONS. THE SECOND PHASE IS THUS MUTUALLY * EXCLUSIVE OF THE FIRST PHASE. * * THE THIRD PHASE IS A CLEANUP OPERATION. WHILE EACH OF THE * FIRST TWO PHASES MAY BE A NO-OP, (AND AT LEAST ONE OF THE * FIRST TWO PHASES WILL INDEED BE A NO-OP), THE THIRD PHASE * ALWAYS IS IN EFFECT. THE CLEANUP OPERATION CONSISTS OF THE * APPLICATION OF LEFT-OVER CHANGES. LEFTOVER CHANGES MAY * CONSTITUTE THE ENTIRE CHANGE IF NEITHER PRELIMINARY PHASE * WAS NEEDED, OR IF ONE OF THE FIRST TWO PHASES WAS USED, * THEN IT WILL HAVE LEFT RESIDUAL CHANGES TO BE APPLIED ONE * LEVEL CLOSER TO THE ROOT THAN THE LAST LEVEL AFFECTED BY * THE SPLITTING OR COLLAPSING. * * ACTUAL APPLICATION OF CHANGES, WHETHER PERFORMED BY THE * THIRD PHASE OF THE CHANGE ROUTINE, OR WITHIN ONE OF THE * SPLIT ROUTINES SELECTED BY THE FIRST PHASE OF THE CHANGE * ROUTINE, SIMPLY CONSIST OF OPENING UP A GAP AT THE RIGHT * MEMORY LOCATION, OF ZERO, POSITIVE, OR NEGATIVE LENGTH, AND * MOVING IN THE TEXT OF THE CHANGE, WHICH CAN CONSIST OF ZERO * OR MORE WORDS. * * THE CLEANUP PHASE ALSO INCREMENTS OR DECREMENTS THE COUNT * OF LINES BRACKETED BY EACH LEVEL OF THE TREE. * * ENTRY PALVL - DEEPEST LEVEL IN TREE STRUCTURE. * PL - CURRENT TREE LEVEL MUST EQUAL PALVL. * NL - CHANGE IN NUMBER OF LINES IN WORKFILE. * NWBEFORE - NUMBER OF WORDS BEFORE POINT OF CHANGE. * NWAFTER - NUMBER OF WORDS AFTER POINT OF CHANGE. * NWNEW - NUMBER OF WORDS IN CHANGED TEXT. * DISP - POINTS JUST AFTER CURRENT LINE. * LINEBUF - CONTAINS TEXT OF CHANGE. * ALL PATH CONTROLS - SETUP. * ALL CACHE CONTROLS - SETUP. * * EXIT PL - ZERO. * PALVL - INCREMENTED/DECREMENTED IF TREE DEPTH CHANGED. * DISP, CURNW - BRACKET NEW CURRENT LINE. * ALL PATH, CACHE CONTROLS - UPDATED. * FET AND CIRCULAR BUFFER USED IF NEEDED. * * CALLS COUNTCACHE, FLUSHCACHE, SPLITTOP, SPLITAPEND, * SPLITBEFORE, SPLITAFTER, RECLAIMCACHE, DEALLOC, CLEAN, * MOVEWD. * * USES P, P, P, FIRST, LAST, NWNEW, * NWBEFORE, NWAFTER, NL. # P = LOC(LINEBUF); # SET THINGS UP # FIRST = CURRENT + NL; LAST = FIRST; CURNW = NWNEW; FOR PL = PL WHILE NWBEFORE+NWNEW+NWAFTER GR 63 DO BEGIN # DO ALL SPLITS # IF COUNTCACHE LQ 2 THEN FLUSHCACHE; IF PL EQ 0 THEN SPLITTOP; IF NWBEFORE EQ PAFINAL[PL] THEN SPLITAPEND; ELSE IF NWBEFORE+NWNEW LQ 63 THEN SPLITAFTER; ELSE SPLITBEFORE; RECLAIMCACHE; P = LOC(NDIR); END FOR PL = PL WHILE NWBEFORE+NWNEW+NWAFTER EQ 0 DO BEGIN # DO ALL COLLAPSING # DEALLOC; CLEAN; # TO FREE UP CACHE FRAME # PALVL = PALVL - 1; PL = PL - 1; NWBEFORE = PADISP[PL] - 1; NWAFTER = PAFINAL[PL] - PADISP[PL]; DISP = PADISP[PL] + 1; END P = LOC(BFPRU[PACACHE[PL]]) + DISP; # SLIDE AFTER # P = LOC(BFPRU[PACACHE[PL]]) + NWBEFORE +NWNEW + 1; MOVEWD(NWAFTER,FROM,TOO); P = LOC(BFPRU[PACACHE[PL]]) + NWBEFORE + 1; # MOVE IN NEW # MOVEWD(NWNEW,NEW,TOO); PAFINAL[PL] = NWBEFORE + NWNEW + NWAFTER; PADIRTY[PL] = TRUE; PALAST[PL] = PALAST[PL] + NL; # FIX UP PATH # FOR PL = PL-1 STEP -1 UNTIL 0 DO # UPDATE HIGHER DIRS # BEGIN P = LOC(BFPRU[PACACHE[PL]]); DIRNL[PADISP[PL]] = DIRNL[PADISP[PL]] + NL; PADIRTY[PL] = TRUE; PALAST[PL] = PALAST[PL] + NL; END CURRENT = CURRENT + NL; # UPDATE CURRENT # IOEND PAGE PROC INS; IOBEGIN(INS) # ** INS - INTERFACE FOR INSERTION OF LINES. * * ENTRY CURRENT - CURRENT LINE. * LINEBUF - LINE IMAGE. * ALL PATH AND CACHE CONTROLS SETUP. * * EXIT CURRENT - INCREMENETED. * ALL PATH AND CACHE CONTROL UPDATED. * * CALLS LINESZ, CHANGE, WIOSTAT. * * USES PL, DISP, NL, NWBEFORE, NWNEW, NWAFTER, DISP. # CONTROL IFEQ MULTI,1; WIOSTAT(0); # ACCUMULATE LINE ACCESES # CONTROL FI; PL = PALVL; # STANDARD INITCHANGE # DISP = PADISP[PL]; NL = 1; NWBEFORE = DISP + CURNW - 1; NWNEW = LINESZ(LINEBUF); NWAFTER = PAFINAL[PL] - DISP - CURNW + 1; DISP = DISP + CURNW; PADISP[PL] = DISP; CHANGE; IOEND PROC REP; IOBEGIN(REP) # ** REP - INTERFACE FOR REPLACEMENT OF LINES. * * ENTRY CURRENT - CURRENT LINE. * LINEBUF - LINE IMAGE. * ALL PATH AND CACHE CONTROLS SETUP. * * EXIT CURRENT - UNCHANGED. * ALL PATH AND CACHE CONTROL UPDATED. * * CALLS LINESZ, CHANGE, WIOSTAT. * * USES PL, DISP, NL, NWBEFORE, NWNEW, NWAFTER, DISP. # CONTROL IFEQ MULTI,1; WIOSTAT(0); # ACCUMULATE LINE ACCESES # CONTROL FI; PL = PALVL; # STANDARD INITCHANGE # DISP = PADISP[PL]; NL = 0; NWBEFORE = DISP - 1; NWNEW = LINESZ(LINEBUF); NWAFTER = PAFINAL[PL] - DISP - CURNW + 1; DISP = DISP + CURNW; CHANGE; IOEND PROC DEL; IOBEGIN(DEL) # ** DEL - INTERFACE FOR DELETION OF LINES. * * ENTRY CURRENT - CURRENT LINE. * ALL PATH AND CACHE CONTROLS SETUP. * DELETCTL - POST-POSITIONING INCREMENT. * * EXIT CURRENT - INCREMENTED IF DELETCTL WAS ONE. * LINEBUF - NEW CURRENT LINE IS READ UP. * ALL PATH AND CACHE CONTROL UPDATED. * DELETCTL - FORCED ZERO IF DELETED LAST LINE. * * CALLS CHANGE, WIOSTAT, POSR. * * USES PL, DISP, NL, NWBEFORE, NWNEW, NWAFTER, DISP, * P, NEWCURL. # CONTROL IFEQ MULTI,1; WIOSTAT(0); # ACCUMULATE LINE ACCESES # CONTROL FI; PL = PALVL; # STANDARD INITCHANGE # DISP = PADISP[PL]; NL = -1; NWBEFORE = DISP - 1; NWNEW = 0; NWAFTER = PAFINAL[PL] - DISP - CURNW + 1; DISP = DISP + CURNW; CHANGE; IF PALAST[0] LQ CURRENT THEN DELETCTL=0; CURRENT=CURRENT+DELETCTL; NEWCURL=CURRENT; P=LOC(LINEBUF); POSR; P=0; IOEND PAGE PROC INIT; BEGIN # ** INIT - INITIALIZE MEMORY CELLS FOR WORKIO. * * ENTRY NO CONDITIONS. * * EXIT P -POINTS TO LIN ARRAY. * ALL ELEMENTS OF PATH ARRAY - NOMINAL. * ALL CACHE CONTROLS - NOMINAL. * READ LIST (RDLIST, RDIN, RDOUT) - EMPTY. * FLAGS (IOACT, IOREAD, IOWRITE) - FALSE. * ARRAYLEN, DATALEN, ARRAYPRUS, DATAPRUS - INITIALIZED. * SEGEMENTVER - ZEROED OUT. # P=LOC(LIN); FOR PL = 1 STEP 1 UNTIL MAXDEPTH DO # INIT PATH # BEGIN PAFIRST[PL] = 0; PALAST[PL] = 0; PADISP[PL] = 0; PAHEAD[PL] = 0; END FOR PL = 0 STEP 1 UNTIL MAXCACHE DO # INIT CACHE CTL # BEGIN CACHEOWNED[PL]=FALSE; CACHEDIRTY[PL]=FALSE; CACHEAGE[PL]=-1; BFHEAD[PL]=0; END FOR RDIN = 0 STEP 1 UNTIL RDLIM DO RDLIST[RDIN] = 0; IOACT = FALSE; # INIT IO # IOAPEND = FALSE; IOWRITE = FALSE; IOREAD = FALSE; CURNW = 0; SEGMENTVER=0; ARRAYLEN=LOC(ARRAYEND)-LOC(ARRAYSTART); ARRAYPRUS=(ARRAYLEN+O"77")/O"100"; DATALEN=LOC(DATAEND)-LOC(DATASTART); DATAPRUS=(DATALEN+O"77")/O"100"; END PAGE PROC RESUMIO; IOBEGIN(RESUMIO) # ** RESUMIO - RESUME EDITOR STATUS FROM LEFTOVER WORKFILE. * * RESUMIO ATTEMPTS TO VALIDATE A FILE AS CONTAINING THE TEXT * AND STATUS LEFT BY A PREVIOUS EDIT SESSION, OR BY A * SINGLE/MULTI USER TRANSITION. SINCE THE SCALAR AND ARRAY * DATA SEGMENTS CONTAIN NEARLY ALL EDITOR DATA, SUCCESSFUL * EXECUTION OF RESUMIO CHANGES EVERYTHING. * * THE FOLLOWING CONDITIONS ARE REQUIRED TO VALIDATE AN * APPARENT WORKFILE AS A LEGITIMATE BASIS FOR RESUMPTION -- * * 1. THE FIRST WORD MUST BE FORMATTED AS A ROOT SECTOR * HEADER WORD. THE DISK ADDRESS FIELD MUST BE ONE AND * THE TOP FLAG MUST BE ON AND THE DIRECTORY AND DATA * FLAGS MUST BE UNEQUAL. * * 2. THE FIRST RECORD OF THE FILE MUST BE OF LENGTH * 64 WORDS LONGER THAN THE SCALAR DATA SEGMENT, AND * THE SECOND RECORD OF THE FILE MUST EXACTLY MATCH * THE LENGTH OF THE ARRAY SEGMENT. * * 3. THE SEGMENTLEN1 AND SEGMENTLEN2 FIELDS OF THE SCALAR * SEGMENT MUST MATCH THE ABOVE LENGTHS, AND THE * SEGMENTVER FIELD MUST MATCH THE VERSION NUMBER * COMPILED INTO THIS PROGRAM. * * AFTER READING UP ALL EDITOR DATA, THE FOLLOWING WORKIO * CONTROLS ARE RE-INITIALIZED - THE PATH VECTOR IS REFRESHED * TO MATCH THE CURRENT LINE, THE SCHEDULING FLAGS (IOACT, * IOREAD, ETC.) ARE CLEARED, MAXDA AND DANEXT ARE BASED ON * FILE SIZE, AND SEGMENTVER IS CLEARED. THE WORKFILE IS THEN * IMMEDIATELY CHECKPOINTED SO THAT IT CANNOT BE RESUMED * AGAIN, THUS INTERLOCKING IT. * * ENTRY NO CONDITIONS. * * EXIT EVERYTHING IN THE WHOLE EDITOR IS CHANGED. * IORESUMED - SUCCESS/FAILURE INDICATOR. * * CALLS INIT, INITFET, BUFUSAGE, ALLOCCACHE, RDWBUF, READ, * WAIT, TRAGIC, SKIPEI, POSR, CLEANALL. # INIT; IORESUMED=FALSE; INITFET(DISK,DSKSIZ); REWIND(FET,0); # TRY TO READ PRU 1 # WAIT; READ(FET,0); WAIT; IF BUFUSAGE GR O"100" THEN BEGIN PL=0; PADA[0]=1; ALLOCCACHE; P=LOC(BFPRU[PACACHE[0]]); RDWBUF(O"100"); PAHEAD[0] = BFHEAD[PACACHE[0]]; END IF PADA[0] EQ 1 AND PATOP[0] AND BOOLDIFF(PADIR[0],PADATA[0]) THEN BEGIN # READ FIRST FOUR WORDS TO VERIFY SEGMENT DESCRIPTORS # P=LOC(DATASTART); NWNEW=BUFUSAGE; NWNEW=MIN(NWNEW,4); RDWBUF(NWNEW); IORESUMED=FALSE; IF SEGMENTVER NQ IOVERSION OR SEGMENTLEN1 NQ DATALEN OR SEGMENTLEN2 NQ ARRAYLEN THEN IORET # SEGMENT DESCRIPTORS VALID, READ REST OF SCALAR SEGMENT # P=LOC(DATASTART)+4; NWNEW=BUFUSAGE; NWNEW=MIN(DATALEN-4,NWNEW); RDWBUF(NWNEW); READ(FET,0); WAIT; # READ ARRAY SEGMENT # P=LOC(ARRAYSTART); NWNEW=BUFUSAGE; NWNEW=MIN(ARRAYLEN,NWNEW); RDWBUF(NWNEW); IF ABORTED THEN TRAGIC(ERRSTRING); SKIPEI(FET,0); WAIT; MAXDA=FETCRI-1; DANEXT=FETCRI; PADISP[0] = 1; # SET UP PATH[0] # PAFIRST[0] = 0; PALAST[0] = -1; P = LOC(BFPRU[PACACHE[0]]); IF PADIR[0] THEN BEGIN FOR DISP = 1 STEP 1 UNTIL PAFINAL[0] DO PALAST[0] = PALAST[0] + DIRNL[DISP]; END ELSE # NOTE INTERNAL CHARSET SIGN BIT USAGE # BEGIN FOR DISP = 1 STEP 1 UNTIL PAFINAL[0] DO IF B<0,1>DATWORD[DISP] NQ 0 THEN PALAST[0] = PALAST[0] + 1; END IORESUMED=TRUE; # RESET FLAGS READ WITH DATA # IOACT=FALSE; IOREAD=FALSE; IOWRITE=FALSE; IOAPEND=FALSE; PALVL = 0; # BUILD REST OF PATH # NEWCURL=0; P=LOC(LINEBUF); POSR; P=0; SEGMENTVER=0; # INTERLOCK FILE ONWESHIP # CLEANALL; END IOEND PAGE CONTROL IFEQ SINGLE,1; PROC INITIO; IOBEGIN(INITIO) # ** INITIO - FORMAT A NEW WORKFILE. * * INITIO IS USED TO BUILD A WORKFILE FROM SCRATCH OR TO * DISCARD OLD WORKFILE CONTENT AND REFRESH IT. * * ENTRY NO CONDITIONS. * * EXIT FILE INITIALIZED. * ALL PATH CONTROLS NOMINAL. * ALL CACHE CONTROL NOMINAL. * MAXDA - ZERO * DANEXT - FIRST ALLOCATABLE DISK ADDRESS RIGHT AFTER * ROOT SECTOR, AND SCALAR AND ARRAY DATA SEGMENTS. * IORESUMED - TRUE TO SHOW WORKIO ACTIVE. * SEGMENTVER - ZERO IN MEMORY AND ON DISK TO INTERLOCK. * * CALLS INITFET, EVICT, WAIT, INIT, ALLOCCACHE, CLEANALL. # INITFET(DISK,DSKSIZ); EVICT(FET,0); WAIT; INIT; MAXDA = 0; # SET MAXDA AND DANEXT # DANEXT = 2+DATAPRUS+ARRAYPRUS; PAHEAD[0] = 0; # BUILD TOP BLOCK # PADATA[0] = TRUE; PATOP[0] = TRUE; PADIRTY[0] = TRUE; PADA[0] = 1; PAFINAL[0] = 1; PL=0; ALLOCCACHE; P = LOC(BFPRU[PACACHE[0]]); DATWORD[1] = NULLIN; CURNW = 1; PADEPTH[0] = 0; PADISP[0] = 1; # SET UP PATH # PAFIRST[0] = 0; PALAST[0] = 0; PALVL = 0; CURRENT = 0; IORESUMED=TRUE; # SHOW WORKIO ALIVE # SEGMENTVER = 0; # INTERLOCK FILE OWNERSHIP # CLEANALL; IOEND CONTROL FI; PAGE PROC CHECKIO; IOBEGIN(CHECKIO) # ** CHECKIO - CHECKPOINT WORKFILE. * * CHECKIO IS USED TO ASSURE THAT ALL MEMORY DATA IS FULLY * WRITTEN TO DISK. THIS CAN BE USED TO PREPARE FOR END OF * JOB STEP OR FOR TRANSITION BETWEEN SINGLE AND MULTIPLE USER * VERSIONS OF THE EDITOR. BY CHECKPOINTING THE WORKFILE, IT * BECOMES POSSIBLE TO EXECUTE THE RESUMIO ROUTINE AND FULLY * RESTORE THE EDITOR STATUS. * * ENTRY IORESUMED - SHOWS WHETHER WORKFILE MANAGER EVER ACTIVE. * CURRENT - CURRENT LINE ORDINAL. * * EXIT SEGMENTVER, SEGMENTLEN1, SEGMENTLEN2 - SETUP. * CACHE FLUSHED, FET AND CIRCULAR BUFFER COMPLETE. * * CALLS CLEANALL. # IF NOT IORESUMED THEN IORET # WORKIO NEVER ALIVE # SAVECURL=CURRENT; SEGMENTVER = IOVERSION; # SHOW VALID WORKFILE FORMAT # SEGMENTLEN1 = DATALEN; SEGMENTLEN2 = ARRAYLEN; CLEANALL; # WRITE OUT WHOLE PATH # IOEND END TERM