-- StreamsC.Mesa Edited by Sandman on July 1, 1980 8:38 AM -- Copyright Xerox Corporation 1979, 1980 DIRECTORY AltoDefs USING [BytesPerPage], AltoFileDefs USING [eofDA, fillinDA, vDA], DiskDefs USING [ CB, CBZ, CBptr, CBZptr, DDC, DoDiskCommand, GetCB, InitializeCBstorage, RetryableDiskError, VirtualDA], InlineDefs USING [COPY], NucleusOps USING [], SegmentDefs USING [FileHandle, GetFileLength, UpdateFileLength], StreamDefs USING [ CleanupDiskStream, DiskHandle, GetIndex, SetIndex, StreamError, StreamHandle, StreamIndex], StreamScan USING [BDHandle, lBD, lSSD, restart, Handle, Descriptor], Storage USING [Node, Free]; StreamsC: PROGRAM IMPORTS DiskDefs, InlineDefs, SegmentDefs, Storage, StreamDefs EXPORTS NucleusOps, StreamScan SHARES DiskDefs, StreamDefs, StreamScan = BEGIN OPEN StreamDefs, StreamScan; CBptr: TYPE = DiskDefs.CBptr; CBZptr: TYPE = DiskDefs.CBZptr; pSSD: TYPE = POINTER TO StreamScan.Descriptor; Init: PUBLIC PROCEDURE [ stream: StreamHandle, bufTable: POINTER TO ARRAY OF POINTER, nBufs: CARDINAL] RETURNS [Handle] = -- Creates a Scan Stream Descriptor (SSD) in preparation for scanning -- the file corresponding to the stream ks. bufTable is a table of pointers -- to page-size buffers, and nBufs is the number of buffers (there must -- be at least one). -- The SSD is allocated from the zone from which ks was allocated. -- Subsequent calls to GetBuffer return pointers to buffers -- containing successive pages of the file, starting with the page at which -- the stream was positioned initially. -- No other operations on the stream should be performed while the scan -- is in progress (i.e., before Finish is called). -- Note: the da, pageNumber, and numChars cells in the SSD are public -- and refer to the page most recently returned by GetBuffer. BEGIN ssd: pSSD; WITH ks: stream SELECT FROM Disk => BEGIN bd: BDHandle; i: CARDINAL; index: StreamIndex ← GetIndex[@ks]; lenCBZ: CARDINAL = SIZE[DiskDefs.CBZ] + (SIZE[DiskDefs.CB] + SIZE[DiskDefs.CBptr])*(nBufs + 2); index.byte ← 0; SetIndex[@ks, index]; CleanupDiskStream[@ks]; ssd ← Storage.Node[lSSD + lenCBZ + (nBufs + 1)*lBD]; ssd.ks ← @ks; SegmentDefs.GetFileLength[ks.file, @ssd.hintLastFA]; ssd.cbz ← LOOPHOLE[ssd, POINTER] + lSSD; ssd.nCB ← nBufs + 2; ssd.nextPage ← restart; ssd.bd ← bd ← LOOPHOLE[ssd.cbz, POINTER] + lenCBZ; FOR i IN [0..nBufs) DO bd↑ ← [nextBD: bd + lBD, buffer: bufTable[i], full: FALSE]; bd ← bd.nextBD; ENDLOOP; bd↑ ← [nextBD: ssd.bd, buffer: ks.buffer.word, full: TRUE]; END; ENDCASE => ERROR StreamError[stream, StreamType]; RETURN[ssd] END; GetBuffer: PUBLIC PROCEDURE [ssd: Handle] RETURNS [POINTER] = -- Returns a pointer to a buffer containing the next page of the file -- being scanned, or zero if end-of-file has been reached. -- This pointer remains valid only until the next call on GetBuffer. BEGIN pssd: pSSD = LOOPHOLE[ssd]; cbz: CBZptr = pssd.cbz; ks: DiskHandle = pssd.ks; nextDA: AltoFileDefs.vDA ← AltoFileDefs.fillinDA; pssd.bd.full ← FALSE; IF pssd.nextPage = restart THEN BEGIN DiskDefs.InitializeCBstorage[cbz, pssd.nCB, pssd.ks.page + 1, clear]; cbz.cleanup ← ScanCleanupCb; cbz.info ← pssd; pssd.nextPage ← cbz.currentPage; nextDA ← ks.das[next]; pssd.thisCB ← DiskDefs.GetCB[cbz, clear]; pssd.nextCB ← DiskDefs.GetCB[cbz, clear]; END; -- The current BD is empty and is ready to have a new command initiated -- for it. The next BD is either empty (because no command for it has -- ever been issued) or is in the process of being read into and has not -- yet been cleaned up. In the latter case, the call to GetCB at the -- bottom of the loop will cause it to be cleaned up and marked full. -- This depends on the number of CBs being equal to the number of BDs plus one -- (the +1 is because we always keep one CB in our pocket). UNTIL pssd.bd.full DO IF ks.das[next] = AltoFileDefs.eofDA THEN BEGIN IF nextDA # AltoFileDefs.eofDA THEN BEGIN pssd.hintLastFA ← [da: ks.das[current], page: ks.page, byte: ks.char]; SegmentDefs.UpdateFileLength[ks.file, @pssd.hintLastFA]; RETURN[NIL] END; -- The following can happen only if the stream was already positioned -- at end-of-file when the SSD was initialized. Simply advance to -- the BD with the stream buffer, without queueing new commands. pssd.bd ← pssd.bd.nextBD; LOOP END; -- Stop queueing commands if we have reached the alleged end-of-file IF pssd.nextPage # pssd.hintLastFA.page + 1 THEN BEGIN -- Queue new command using empty BD and chaining to nextCB ddc: DiskDefs.DDC ← [cb: pssd.thisCB, ca: pssd.bd.buffer, da: nextDA, page: pssd.nextPage, fp: @ks.file.fp, restore: FALSE, action: ReadD]; pssd.thisCB.labelAddress ← LOOPHOLE[@pssd.nextCB.header.diskAddress]; DiskDefs.DoDiskCommand[@ddc]; nextDA ← AltoFileDefs.fillinDA; pssd.nextPage ← pssd.nextPage + 1; END; -- Advance BD, and get another CB to which the next command will be chained pssd.bd ← pssd.bd.nextBD; pssd.thisCB ← pssd.nextCB; pssd.nextCB ← DiskDefs.GetCB[ cbz, clear ! DiskDefs.RetryableDiskError => BEGIN pssd.nextPage ← cbz.currentPage; nextDA ← ks.das[next]; pssd.thisCB ← DiskDefs.GetCB[cbz, clear]; pssd.nextCB ← DiskDefs.GetCB[cbz, clear]; LOOP END]; ENDLOOP; -- If this is the page claimed by the length hint to be the last page -- of the file, and it isn't, then invalidate the hint and force the -- next call on GetBuffer to restart the transfer. IF ks.page = pssd.hintLastFA.page AND ks.das[next] # AltoFileDefs.eofDA THEN BEGIN pssd.hintLastFA.page ← 0; pssd.nextPage ← restart END; pssd.da ← ks.das[current]; pssd.pageNumber ← ks.page; pssd.numChars ← ks.char; RETURN[pssd.bd.buffer] END; ScanCleanupCb: PROCEDURE [cb: CBptr] = BEGIN ssd: Handle = cb.zone.info; ks: DiskHandle = ssd.ks; ssd.bd.full ← TRUE; ks.das[last] ← ks.das[current]; ks.das[current] ← ks.das[next]; ks.das[next] ← DiskDefs.VirtualDA[cb.labelAddress.next]; ks.page ← ks.page + 1; ks.char ← cb.zone.currentBytes; END; Noop: PROCEDURE [CBptr] = BEGIN END; Finish: PUBLIC PROCEDURE [ssd: Handle] = BEGIN OPEN DiskDefs; -- Wait 'til all queued transfers have completed, but don't clean them up -- and don't bother to retry any that suffered errors. ks: DiskHandle = ssd.ks; cbz: CBZptr = ssd.cbz; cbz.cleanup ← Noop; UNTIL cbz.cbQueue[cbz.qHead] = NIL DO [] ← GetCB[cbz, dontClear ! RetryableDiskError => CONTINUE]; ENDLOOP; -- Put the contents of the current page into the stream buffer, -- if it's not already there. IF ks.buffer.word # ssd.bd.buffer THEN InlineDefs.COPY[ to: ks.buffer.word, from: ssd.bd.buffer, nwords: AltoDefs.BytesPerPage/2]; Storage.Free[LOOPHOLE[ssd]]; END; END..