// SpruceFiles.Bcpl -- Spruce Files -- page-level, buffered operation // Errors 2000 get "sprucefiles.d" get "isf.d" get "bfs.d" // Spruce Files // A SPruceFile structure contains an Isf FM (file map), completely filled in, and descriptions of the // state of a number of file buffers (page number, cleanliness, buffer location). Isf routines are used for // access to files via this structure; this package provides routines for creating the structures and // (optionally) the files, deleting the structures, for attaching SpruceStreams to them (n readers, or no // readers and one reader/writer), and for finding and filling the core buffers. external [ // defined here UnAccessSpruceFile; FindSprucePage; GetSprucePage; PutSprucePage AccessSpruceFile; DiskObject; FileLeng; InitSpruceFile; ResetSpruceFile ] external [ // external // from OS CallersFrame; DefaultArgs; Zero; OpenFile; FileLength; GetCurrentFa; Closes; sysDisk // from ISF LookupFmap // From Spruce Utilities Max; Min; PageToPos // From Other Spruce Sources SpruceError; FSGetX; FSGet; FSPut; SpruceZone; tridentDisk; drive1Disk // From SpruceFsParams TfsParams; BfsParams // From SpruceFilesMl VpageToRpage // virtual to real // From packages Dequeue; Enqueue; InsertAfter; Unqueue // From Mevents // ReportEvent ] static dcbHead; manifest DCBHead = #420 // Called for one of three reasons: // by Create... to set up major file structures, cb zone, etc.; after restoration from checkpoint file, to // reset all values; whenever any of the buffering decisions must be changed. // Assumes that no substructures are currently allocated, independent of pointer values. So, if called // more than once during one zone era, a call to Reset... must precede it. This is NOT enforced!! // If maxAhead is 0, no cbzone will be allocated let InitSpruceFile(sF, numBuffers, numCbs, zone; numargs na) be [ // prepare restored file descriptor for file activity DefaultArgs(lv na, 1, sF>>SPruceFile.numBuffers, sF>>SPruceFile.numCbs, SpruceZone) if sF>>SPruceFile.valid then test numBuffers eq sF>>SPruceFile.numBuffers & numCbs eq sF>>SPruceFile.numCbs & zone eq sF>>SPruceFile.zone ifso return ifnot ResetSpruceFile(sF) // guaranteed "invalid" at this point sF>>SPruceFile.zone = zone // file now valid sF>>SPruceFile.readers = 0 sF>>SPruceFile.numCbs = numCbs let map = sF>>SPruceFile.map let disk = DiskObject(sF>>SPruceFile.deviceCode) map>>FM.zone, map>>FM.disk = zone, disk let lnPageSize = disk>>DSK.lnPageSize sF>>SPruceFile.lnPageSize = lnPageSize sF>>SPruceFile.pageSize = 1 lshift lnPageSize Zero(lv sF>>SPruceFile.bufQs^0, 6) // 3 queues let fParams = BfsParams() if sF>>SPruceFile.deviceCode eq DISKT80 then [ fParams = TfsParams() // half the buffers for Trident numBuffers = (numBuffers+1) rshift 1 ] sF>>SPruceFile.fParams = fParams sF>>SPruceFile.numBuffers = numBuffers let cbz = sF>>SPruceFile.cbz; if not cbz & numCbs then [ let lenCBZ = fParams>>FParams.lCBZ+numCbs*(fParams>>FParams.lCB+1) cbz = FSGetX(lenCBZ) fParams>>FParams.InitializeCbStorage(disk, cbz, 0, lenCBZ, 0, fParams>>FParams.lvDefaultErrorRtn) cbz!(fParams>>FParams.offsetCleanup) = PostDone ] sF>>SPruceFile.cbz = cbz ] and ResetSpruceFile(spruceFile) be // must be initted again before use! But file assumed valid [ if spruceFile>>SPruceFile.readers then SpruceError(2040,spruceFile) let q = lv spruceFile>>SPruceFile.diskQ; while q>>Q.head do CleanUp(spruceFile, true) q = lv spruceFile>>SPruceFile.memQ let zone = spruceFile>>SPruceFile.zone while q>>Q.head do FSPut(Dequeue(q), zone) Zero(lv spruceFile>>SPruceFile.cbQ, 2) let cbz = spruceFile>>SPruceFile.cbz; if cbz then FSPut(cbz, zone) spruceFile>>SPruceFile.cbz = 0 spruceFile>>SPruceFile.valid = 0 ] // Spruce File access functions, for attaching streams to the file. // AccessSpruceFile(spruceFile, type [... spruceReadAccess]) grants read or read/write access to the file, // if such access is valid. Standard stuff; many readers or one writer. Returns false if unsuccessful, -1 // if write access granted, number of readers if read access granted. UnAccessSpruceFile(spruceFile) // cancels write access or reduces the reader count. It returns the reader count to indicate whether the // file can be forgotten. This collection is relatively error prone, in that no checking for proper use is // made after appropriate access is granted. ~~ Because Spruce Bands code reads and writes same file // from multiple streams, treat all requests as read requests for the present. ~~ and AccessSpruceFile(spruceFile, type; numargs na) = valof [ if na>1 then na = 1 // ~~ all requests are for read access! (see above) DefaultArgs(lv na, 1, spruceReadAccess) let readers = spruceFile>>SPruceFile.readers if readers < 0 % (readers ne 0 & type eq spruceWriteAccess) resultis false readers = readers + type spruceFile>>SPruceFile.readers = readers resultis readers ] and UnAccessSpruceFile(spruceFile) = valof [ let readers = spruceFile>>SPruceFile.readers-1 if readers<0 then readers = 0 spruceFile>>SPruceFile.readers = readers // accessibility check if readers resultis readers while spruceFile>>SPruceFile.diskQ.head do CleanUp(spruceFile, true) // disk activities cease let sP = spruceFile>>SPruceFile.memQ.head while sP do [ if sP>>SPrucePage.accessors % sP>>SPrucePage.dirty then SpruceError(2050, spruceFile, sP>>SPrucePage.pageNumber) sP = sP>>SPrucePage.link ] resultis 0 // = readers ] and DiskObject(i) = selecton i into [ case DISK31: sysDisk; case DISK31B: drive1Disk; case DISKT80: tridentDisk; default: 0 ] and FileLeng(sF, pPos, itemSize; numargs na) = PageToPos( pPos, sF>>SPruceFile.numPages, sF>>SPruceFile.numChars, (na eq 3? itemSize, wordItem), sF) and GetSprucePage(sF,pageNo, numPages, fill; numargs na) = valof [ // ReportEvent($G, pageNo) if na<4 then fill = true; unless fill do numPages = 1 // read in page contents let index = vec 20; Zero(index, 20) let firstPage, lastPage, backwards, len, sP= pageNo, nil, false, 0, nil if numPages<0 then [ backwards = true; numPages = -numPages; firstPage = firstPage-numPages+1 ] if numPages>20% not sF>>SPruceFile.valid then SpruceError(2060, sF) lastPage = Min(sF>>SPruceFile.numPages, firstPage+numPages-1) firstPage = Max(1, firstPage); numPages = lastPage-firstPage+1 let base = index-firstPage CleanUp(sF, false) // post completions, rescue all complete cbs -- retry on errors for i = 0 to 1 do // gather existing pages in range [ sP = sF>>SPruceFile.bufQs^i.head // one of two while sP do [ let pn = sP>>SPrucePage.pageNumber if firstPage le pn & pn le lastPage then base!pn = sP len = len+1; sP = sP>>SPrucePage.link ] ] let oP = lv sF>>SPruceFile.memQ for i = firstPage to lastPage do [ sP = 0 let pNo = backwards? firstPage+(lastPage-i), i if base!pNo loop if len>SPruceFile.numBuffers then [ sP = FSGet(minLenSPrucePage+sF>>SPruceFile.pageSize, sF>>SPruceFile.zone) test sP then [ len = len+1; Enqueue(oP, sP) ] or len = #77777 ] unless sP do [ sP, oP = oP>>SPrucePage.link, sP unless sP do test pageNo ne pNo break or [ CleanUp(sF, true); oP = lv sF>>SPruceFile.memQ; loop ] let pn = sP>>SPrucePage.pageNumber unless sP>>SPrucePage.accessors%sP>>SPrucePage.dirty% firstPage le pn&pn le lastPage break ] repeat unless sP break; sP>>SPrucePage.status = 0 sP>>SPrucePage.buffer = (lv sP>>SPrucePage.buffer)+1 // ~~ for now base!pNo = sP; sP>>SPrucePage.pageNumber = pNo ] sP = base!pageNo; unless sP do SpruceError(2030, sF, pageNo) sP>>SPrucePage.accessors = sP>>SPrucePage.accessors+1 // ~~ ugh! if still recovering from error, may look unqueued for transfer; need another bit while sF>>SPruceFile.reschedule do CleanUp(sF, false) // ~~ ugh if not fill & not sP>>SPrucePage.scheduled then [ sP>>SPrucePage.numChars = pageNo eq sF>>SPruceFile.numPages? sF>>SPruceFile.numChars, sF>>SPruceFile.pageSize lshift 1 // ReportEvent($g, pageNo) resultis sP ] // don't fill, so don't care about contents let fileBackwards = sF>>SPruceFile.backwards if backwards ne fileBackwards&(sP>>SPrucePage.valid%sP>>SPrucePage.scheduled) then numPages=-1 for i = 0 to numPages-1 do // read numPages [ let aSP = index!(fileBackwards? numPages-1-i, i) if aSP then unless aSP>>SPrucePage.valid%aSP>>SPrucePage.scheduled%Schedule(sF, aSP, opRead) break ] until sP>>SPrucePage.valid do CleanUp(sF, true) // ReportEvent($g, pageNo) resultis sP ] and PutSprucePage(sF, sP) = valof [ unless sP resultis 0 let pageNo = sP>>SPrucePage.pageNumber if sP>>SPrucePage.dirty then [ // ReportEvent($W, sP>>SPrucePage.pageNumber) sP>>SPrucePage.dirty = false CleanUp(sF, false) // account for disk activity unless Schedule(sF, sP, opWrite) do CleanUp(sF, true) repeatuntil sP>>SPrucePage.scheduled // ReportEvent($w, sP>>SPrucePage.pageNumber) ] sP>>SPrucePage.accessors = Max(sP>>SPrucePage.accessors-1,0) resultis sP // don't know why ] and Schedule(sF, sP, operation) = valof // returns true if sP has been presented to microcode queue [ // ReportEvent($S, sP>>SPrucePage.pageNumber + operation lshift 8) if sF>>SPruceFile.deviceCode eq DISKT80&@DCBHead then [ dcbHead = @DCBHead; @DCBHead = 0 ] // turn off display, perhaps sP>>SPrucePage.valid = false; sP>>SPrucePage.scheduled = false test operation eq opReschedule then operation = sP>>SPrucePage.operation or [ Unqueue(lv sF>>SPruceFile.memQ, sP) // it's there, but it's OK if it isn't sP>>SPrucePage.operation = operation Enqueue(lv sF>>SPruceFile.diskQ, sP) ] let cb = Dequeue(lv sF>>SPruceFile.cbQ); unless cb do [ sF>>SPruceFile.reschedule = true; resultis false ] sP>>SPrucePage.cb = cb sP>>SPrucePage.scheduled = true // Do Command // can add simulation at this point if desirable let map = sF>>SPruceFile.map let page = VpageToRpage(sF, sP>>SPrucePage.pageNumber) let fp, da = lv map>>FM.fp, LookupFmap(map, page) let disk = map>>FM.disk let op = operation eq opWrite? DCwriteD, DCreadD cb>>CB.link = 0 // DoDiskCommand doesn't clear sF>>SPruceFile.fParams>>FParams.DoDiskCommand(disk, cb, sP>>SPrucePage.buffer, da, fp, page, op) resultis true ] and CleanUp(sF, doSomething) be // ****** sF MUST remain 1st arg!!! ****** [ // ReportEvent($C, doSomething) let disk, cbz, cbQ = sF>>SPruceFile.map>>FM.disk, sF>>SPruceFile.cbz, lv sF>>SPruceFile.cbQ let diskQ, fParams = lv sF>>SPruceFile.diskQ, sF>>SPruceFile.fParams cbz!(fParams>>FParams.offsetRetry) = MRetry if false then // entered from GetCb via MRetry label MRetry: [ // cbz has been rebuilt -- must reschedule all pending ops Zero(cbQ, 2) sF>>SPruceFile.reschedule = true let sP = diskQ>>Q.head // ReportEvent($X, sP? sP>>SPrucePage.pageNumber, -1) while sP do [ sP>>SPrucePage.scheduled = false; sP = sP>>SPrucePage.link ] // Retry activity complete, may be re-invoked without ultimate error ] // Get cbs out of zone, post results [ // **** MUST call GetCb directly **** let cb = fParams>>FParams.GetCb(disk, cbz, false, true) unless cb do [ if dcbHead& not @(fParams>>FParams.nextDiskCommand) then [ @DCBHead = dcbHead; dcbHead = 0 ] // turn on display, perhaps test doSomething ifnot break ifso test diskQ>>Q.head then loop or SpruceError(2030, sF) ] doSomething = false // did something Enqueue(cbQ, cb) ] repeat // reschedule any operations unscheduled due to error if sF>>SPruceFile.reschedule then [ let aSP = diskQ>>Q.head while aSP do // Schedule will NOT detect errors and get too recursive! [ unless aSP>>SPrucePage.scheduled % Schedule(sF, aSP, opReschedule) break aSP = aSP>>SPrucePage.link ] unless aSP do sF>>SPruceFile.reschedule = false ] // ReportEvent($c, doSomething) ] and PostDone(disk, cb) be // how to derive file from cb? [ // **** N.B. CleanUp(sF) calls GetCb which calls PostDone **** let sF = @((@CallersFrame())+4) // **** VERY implementation-dependent **** let diskQ = lv sF>>SPruceFile.diskQ let sP, fParams = diskQ>>Q.head, sF>>SPruceFile.fParams if sF>>SPruceFile.deviceCode ne DISKT80& ((not sP)%sP>>SPrucePage.cb ne cb) then // check for restore produced in previous GetCb test cb>>CB.diskAddress.restore eq 1 then return or SpruceError(2110) // synch. error unless Dequeue(diskQ) do SpruceError(2110) // ReportEvent($P, sP>>SPrucePage.pageNumber) sP>>SPrucePage.numChars = (cb!(fParams>>FParams.offsetLabelAddress))!(fParams>>FParams.offsetLabelNumChars) sP>>SPrucePage.valid = true // ship it!o sP>>SPrucePage.scheduled = false Enqueue(lv sF>>SPruceFile.memQ, sP) ] // June 3, 1977 10:22 AM, created // June 4-9, 1977, initial development // June 27, 1977 4:02 PM, off-by-one bug in subfile offset setting // June 27, 1977 10:54 PM, SpruceFileLength -> FileLeng // July 5, 1977 8:37 AM, revise error codes to match "Press" approach // July 5-21, 1977, major revisions // August 4, 1977 9:53 PM, ~~ consider all Access.... requests to be read access requests // August 5, 1977 9:16 AM, add backwards-arranged (by page) files // December 21, 1977 1:11 PM, mark all page buffers empty after all readers go away // March 10, 1978 8:56 AM, move infrequently used functions to sprucefilesinit.bcpl // March 11, 1978 2:13 PM, use old buffer if not space for new one // September 14, 1978 10:52 AM, DiskObject(deviceCode) maps codes to objects // October 5, 1978 10:39 PM, add fill arg to GetSprucePage // October 9, 1978 8:38 AM, add concurrent I/O!! // October 9, 1978 10:35 PM, add reports // October 10, 1978 12:13 PM, make work better // October 10, 1978 4:59 PM, add retry code // October 11, 1978 8:35 AM, modify retry code -- CleanUp does rescheduling -- doggedly // October 11, 1978 12:24 PM, FindSprucePage reorganized for faster search // October 11, 1978 3:34 PM, make above work // October 12, 1978 12:03 AM, combine GetSprucePage and FindSprucePage // October 13, 1978 4:44 PM, VpageToRpage in Schedule, just at disk interface -- still // need to account for backwards-oriented files in optimization code // October 16, 1978 12:05 PM, disable ReportEvent via comment // October 16, 1978 2:36 PM, reenable most reports, change W reports // October 24, 1978 1:43 PM, try to make backwards files work reasonably well (reading) // October 30, 1978 6:57 AM, Tfs->TFS // October 31, 1978 8:09 AM, adjust buffer sizes for T80 // October 31, 1978 11:29 PM, use SPruceFile.fParams to compensate for different locations of things // November 3, 1978 9:09 AM, turn off display when running T80 // November 5, 1978 12:35 PM, comment out event reporting // February 20, 1979 9:08 AM, accommodate bfs change from CB.nextCommand to CB.link (!) //