// DiskStreamsScan.bcpl -- fast file scanner -- adjunct to DiskStreams // Copyright Xerox Corporation 1979 // Last modified July 13, 1979 3:20 PM by Taft get "DiskStreams.decl" external [ // outgoing procedures InitScanStream; GetScanStreamBuffer; FinishScanStream // incoming procedures PositionPtr; CleanupDiskStream; SetLengthHint VirtualDiskDA InitializeDiskCBZ; DoDiskCommand; GetDiskCb Allocate; Free; MoveBlock; Noop ] //--------------------------------------------------------------------------- structure SSDx: // Scan Stream Descriptor, extended version //--------------------------------------------------------------------------- [ @SSD // Public cells (da, pageNumber, numChars) bd word // -> current BD being worked on nextPage word // page number for next command to initiate; -1 => restart thisCb word // CB for this BD's next command nextCb word // CB for next command to chain to ks word // -> disk stream cbz word // -> CBZ lenCBZ word // length of CBZ in words ] manifest lSSD = size SSDx/16 //--------------------------------------------------------------------------- structure BD: // Buffer Descriptor //--------------------------------------------------------------------------- [ nextBD word // -> next BD in ring full word // false if empty or being filled, true if full buffer word // -> buffer for this BD ] manifest lBD = size BD/16 //--------------------------------------------------------------------------- let InitScanStream(ks, bufTable, nBufs) = valof //--------------------------------------------------------------------------- // 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 GetScanStreamBuffer 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 FinishScanStream is called). // Note: the da, pageNumber, and numChars cells in the SSD are public // and refer to the page most recently returned by GetScanStreamBuffer. [ // Get the stream into a clean state PositionPtr(ks, 0) CleanupDiskStream(ks) // Initialize the SSD and the CBZ let lenCBZ = ks>>KS.disk>>DSK.lengthCBZ + ks>>KS.disk>>DSK.lengthCB*(nBufs+2) let ssd = Allocate(ks>>KS.zone, lSSD + lenCBZ + (nBufs+1)*lBD) ssd>>SSDx.ks = ks ssd>>SSDx.cbz = ssd+lSSD ssd>>SSDx.lenCBZ = lenCBZ ssd>>SSDx.nextPage = -1 // Initialize the BDs for the buffers we have been given let bd = ssd>>SSDx.cbz + lenCBZ ssd>>SSDx.bd = bd for i = 0 to nBufs-1 do [ bd>>BD.nextBD = bd+lBD bd>>BD.buffer = bufTable!i bd>>BD.full = false bd = bd>>BD.nextBD ] // Initialize an extra BD for the stream buffer and mark it full bd>>BD.nextBD = ssd>>SSDx.bd // Close the ring bd>>BD.buffer = ks>>KS.bufferAddress bd>>BD.full = true resultis ssd ] //--------------------------------------------------------------------------- and GetScanStreamBuffer(ssd) = valof //--------------------------------------------------------------------------- // 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 GetScanStreamBuffer. [ ssd>>SSDx.bd>>BD.full = false let nextDA = fillInDA let ks, cbz = ssd>>SSDx.ks, ssd>>SSDx.cbz let disk = ks>>KS.disk // -1 means that this is the first call since the ssd was initialized, // or that the length hint failed and we have to restart the transfer. if ssd>>SSDx.nextPage eq -1 then [ InitializeDiskCBZ(disk, cbz, ks>>KS.pageNumber+1, ssd>>SSDx.lenCBZ, Sretry, lv ks>>KS.bfsErrorRtn) cbz>>CBZ.cleanupRoutine = ScanCleanupCb cbz>>CBZ.client = ssd // so the cleanup routine can find it Sretry: // Errors cause BfsGetCb to return here ssd>>SSDx.nextPage = cbz>>CBZ.currentPage nextDA = ks>>KS.DAs.next ssd>>SSDx.thisCb = GetDiskCb(disk, cbz) ssd>>SSDx.nextCb = GetDiskCb(disk, cbz) // always keep one cb ahead ] // 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). [ // repeat if ks>>KS.DAs.next eq eofDA then [ unless nextDA eq eofDA resultis 0 // reached end-of-file // 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. ssd>>SSDx.bd = ssd>>SSDx.bd>>BD.nextBD; loop ] // Stop queueing commands if we have reached the alleged end-of-file if ssd>>SSDx.nextPage ne ks>>KS.hintLastPageFa.pageNumber+1 then [ // Queue new command using empty BD and chaining to nextCb DoDiskCommand(disk, ssd>>SSDx.thisCb, ssd>>SSDx.bd>>BD.buffer, nextDA, lv ks>>KS.fp, ssd>>SSDx.nextPage, DCreadD, ssd>>SSDx.nextCb) nextDA = fillInDA ssd>>SSDx.nextPage = ssd>>SSDx.nextPage+1 ] // Advance BD, and get another CB to which the next command will be chained ssd>>SSDx.bd = ssd>>SSDx.bd>>BD.nextBD ssd>>SSDx.thisCb = ssd>>SSDx.nextCb ssd>>SSDx.nextCb = GetDiskCb(disk, cbz) ] repeatuntil ssd>>SSDx.bd>>BD.full // 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 GetScanStreamBuffer to restart the transfer. if ks>>KS.pageNumber eq ks>>KS.hintLastPageFa.pageNumber & ks>>KS.DAs.next ne eofDA then [ ks>>KS.hintLastPageFa.pageNumber = 0; ssd>>SSDx.nextPage = -1 ] ssd>>SSDx.da = ks>>KS.DAs.current ssd>>SSDx.pageNumber = ks>>KS.pageNumber ssd>>SSDx.numChars = ks>>KS.numChars resultis ssd>>SSDx.bd>>BD.buffer ] //--------------------------------------------------------------------------- and ScanCleanupCb(disk, cb, cbz) be //--------------------------------------------------------------------------- // The cleanupRoutine called by BfsGetCb immediately before handing back // a CB corresponding to a completed transfer. [ let ssd = cbz>>CBZ.client ssd>>SSDx.bd>>BD.full = true let ks = ssd>>SSDx.ks ks>>KS.DAs.last = ks>>KS.DAs.current ks>>KS.DAs.current = ks>>KS.DAs.next ks>>KS.DAs.next = cbz>>CBZ.nextDA ks>>KS.pageNumber = ks>>KS.pageNumber+1 ks>>KS.numChars = cbz>>CBZ.currentNumChars ] //--------------------------------------------------------------------------- and FinishScanStream(ssd) be //--------------------------------------------------------------------------- // Waits for disk activity to cease, updates the state in the disk stream, // and destroys the SSD. The stream is left positioned at the beginning // of the last page returned by GetScanStreamBuffer. [ // Wait til all queued transfers have completed, but don't clean them up // and don't bother to retry any that suffered errors. let ks, cbz = ssd>>SSDx.ks, ssd>>SSDx.cbz cbz>>CBZ.cleanupRoutine = Noop cbz>>CBZ.retry = FlushRetry FlushRetry: while cbz>>CBZ.head ne 0 do GetDiskCb(ks>>KS.disk, cbz) // Put the contents of the current page into the stream buffer, // if it's not already there. MoveBlock(ks>>KS.bufferAddress, ssd>>SSDx.bd>>BD.buffer, ks>>KS.charsPerPage rshift 1) // If we reached end-of-file, ensure that the length hint is correct. // This is a no-op if we did not reach end-of-file. SetLengthHint(ks) Free(ks>>KS.zone, ssd) ]