// ScavScan.bcpl -- analyzes the disk // Copyright Xerox Corporation 1979, 1980, 1981, 1982 // Last modified September 29, 1982 1:30 AM by Boggs get "AltoFileSys.d" get "AltoDefs.d" get "Streams.d" get "Disks.d" get "BFS.d" get "Scavenger.decl" external [ // outgoing procedures ScanDisk; CreateD; ForAllFDs; CheckedVDA // incoming procedures XferPage; XferError; GetBit; SetBit GetString; Confirm; Ws; SysErr GetDiskCb; DoDiskCommand VirtualDiskDA; InitializeDiskCBZ Idle; Noop; Zero; SetBlock; MoveBlock Allocate; Free; Enqueue; Dequeue; Unqueue PutTemplate // outgoing statics usedBT; badBT; fdbQ banzaiFlag // incoming statics sysZone; sysDisk; dsp; log maxVDA; alterFlag; label; data logFlag; pt; bt ] static [ usedBT; badBT; fdbQ; rdbQ banzaiFlag; restartFlag ] manifest [ // pt reserved values: ptFree = -1 ptIncor = -2 ptGood = -3 ptBad = -4 ptToBeChecked = -5 ] structure RD: // Run Descriptor [ fd word // -> fd for this file firstBack word // backP of first page of run firstPN word // PN of first page of run lastNext word // nextP of last page of run used bit // this RD is part of a chain starting from an FD lastPN bit 15 // PN of last page of run ] manifest lenRD = size RD/16 structure DB: // File or Run Descriptor Block [ link word // -> next DB or zero if none ptr word // DB-relative ptr to next available word max word // DB-relative ptr to first word not in DB ] manifest lenDB = size DB/16 structure DQ: // Queue of DBs [ head word // head of queue of DBs tail word // tail of queue of DBs lenD word // length of Ds in a DB on this queue ] manifest lenDQ = size DQ/16 structure String [ length byte; char↑1,1 byte ] //----------------------------------------------------------------------------------------- let ScanDisk() be //----------------------------------------------------------------------------------------- [ Zero(pt, maxVDA+1); pt!0 = ptGood //page zero Zero(bt, maxVDA/8+1) fdbQ = Allocate(sysZone, lenDQ); Zero(fdbQ, lenDQ); fdbQ>>DQ.lenD = lenFD rdbQ = Allocate(sysZone, lenDQ); Zero(rdbQ, lenDQ); rdbQ>>DQ.lenD = lenRD sysZone!2 = ScanOutOfSpace [ // Scan the disk building data structures and doing page-level checks restartFlag = false let zoneLength = sysDisk>>DSK.lengthCBZ + 28*sysDisk>>DSK.lengthCB let cbz = Allocate(sysZone, zoneLength) InitializeDiskCBZ(sysDisk, cbz, 0, zoneLength, ScanRetry, lv ScanError) cbz>>CBZ.cleanupRoutine = ScanCleanup ScanRetry: let scanVDA = cbz>>CBZ.errorDA while scanVDA le maxVDA & not restartFlag do [ if pt!scanVDA eq 0 then DoDiskCommand(sysDisk, GetDiskCb(sysDisk, cbz), data, scanVDA, 0, 0, DCreadLD) if (scanVDA & 77b) eq 0 then Idle() scanVDA = scanVDA +1 ] while @cbz>>CBZ.queueHead ne 0 do GetDiskCb(sysDisk, cbz) Free(sysZone, cbz) if restartFlag loop for vda = 0 to maxVDA if pt!vda eq ptToBeChecked then pt!vda = 0 // Do file-level checks restartFlag = false ForAllFDs(CheckFile) CompactDBs() ] repeatwhile restartFlag sysZone!2 = SysErr Free(sysZone, rdbQ) // Create used-page and bad-page bit-tables usedBT = bt; Zero(usedBT, maxVDA/16+1) badBT = bt+maxVDA/16+1; Zero(badBT, maxVDA/16+1) for vda = 0 to maxVDA do [ let p = pt!vda test p eq ptFree ifso sysDisk>>BFSDSK.freePages = sysDisk>>BFSDSK.freePages +1 ifnot SetBit(usedBT, vda, true) unless p eq ptGood % p eq ptFree % p eq ptIncor do [ SetBit(badBT, vda, true); logFlag = true ] ] for vda = maxVDA+1 to (maxVDA/16)*16+15 do SetBit(usedBT, vda, true) ] //----------------------------------------------------------------------------------------- and ScanCleanup(disk, cb, cbz) be //----------------------------------------------------------------------------------------- [ if restartFlag return let vda = VirtualDiskDA(sysDisk, lv cb>>CB.diskAddress) let dl = cb>>CB.labelAddress // Check for special labels let fid = lv dl>>DL.fileId if fid!0 eq -1 & fid!1 eq -1 & fid!2 eq -1 then [ pt!vda = ptFree; return ] if fid!0 eq -2 & fid!1 eq -2 & fid!2 eq -2 then [ pt!vda = ptIncor; return ] let pn = dl>>DL.pageNumber let nc = dl>>DL.numChars let nextP = CheckedVDA(lv dl>>DL.next) let backP = CheckedVDA(lv dl>>DL.previous) // Check for obvious damage if nc ugr 512 % pn ugr maxVDA % nextP eq vda % backP eq vda % nextP eq eofDA & (nc eq 512 % pn eq 0 % backP eq eofDA) % backP eq eofDA & (nc ne 512 % pn ne 0) % nextP ne eofDA & (nc ne 512 % nextP ugr maxVDA) % backP ne eofDA & (pn eq 0 % backP ugr maxVDA) then [ pt!vda = ptBad; return ] // Work on the FD let sn = lv dl>>DL.fileId.serialNumber let best, duplicate = 0, false let ha = (sn>>SN.word2 & 77777b) rem (maxVDA/8+1) let fd = valof [ let f = bt!ha; if f eq 0 resultis best if f>>FD.sn.word2 eq sn>>SN.word2 & f>>FD.sn.word1 eq sn>>SN.word1 then [ if backP ne eofDA resultis f if f>>FD.firstVDA eq vda resultis f test f>>FD.firstVDA eq 0 ifso best = f ifnot [ f>>FD.duplicate = true; duplicate = true ] ] ha = ha eq maxVDA/8? 0, ha+1 ] repeat if fd eq 0 then [ fd = CreateD(fdbQ); if fd eq 0 return let ha = (sn>>SN.word2 & 77777b) rem (maxVDA/8+1) [ if bt!ha eq 0 then [ bt!ha = fd; break ] //insert ha = ha eq maxVDA/8? 0, ha+1 //collision; reprobe ] repeat fd>>FD.sn.word1 = sn>>SN.word1 fd>>FD.sn.word2 = sn>>SN.word2 fd>>FD.duplicate = duplicate ] if backP eq eofDA then fd>>FD.firstVDA = vda if nextP eq eofDA then [ fd>>FD.lastVDA = vda fd>>FD.lastPN = pn fd>>FD.lastNumChars = nc ] unless fd>>FD.state eq fdBeingChecked % fd>>FD.duplicate return // ScanCleanup (cont'd) // Work on the RD let rd = pt!(vda-1) if rd eq 0 % rd uge 177000b % rd>>RD.fd ne fd % backP ne vda-1 % rd>>RD.lastPN+1 ne pn % rd>>RD.lastNext ne vda then [ rd = CreateD(rdbQ); if rd eq 0 return rd>>RD.fd = fd rd>>RD.firstBack = backP rd>>RD.firstPN = pn ] rd>>RD.lastNext = nextP rd>>RD.lastPN = pn // enter page in pt test nextP eq eofDA & rd>>RD.firstBack eq eofDA ifso //single-run file: abandon RD [ fd>>FD.state = fdGood for v = fd>>FD.firstVDA to fd>>FD.lastVDA do pt!v = ptGood rdbQ>>DQ.tail>>DB.ptr = rdbQ>>DQ.tail>>DB.ptr - lenRD ] ifnot pt!vda = rd ] //----------------------------------------------------------------------------------------- and ScanOutOfSpace(zn, ec, lSbData) = valof //----------------------------------------------------------------------------------------- [ // set every other fd in state fdBeingChecked to state fdToBeChecked restartFlag = 0 ForAllFDs(RemoveHalf) // flush all RDs for vda = 0 to maxVDA do [ let rd = pt!vda if rd ne 0 & rd uls 177000b then pt!vda = rd>>RD.fd>>FD.state eq fdToBeChecked? ptToBeChecked, 0 ] CompactDBs() resultis 0 ] //----------------------------------------------------------------------------------------- and RemoveHalf(fd, nil) be //----------------------------------------------------------------------------------------- [ if fd>>FD.state eq fdBeingChecked then [ if (restartFlag & 1) ne 0 then fd>>FD.state = fdToBeChecked restartFlag = restartFlag +1 ] if fd>>FD.duplicate then fd>>FD.state = fdBeingChecked ] //----------------------------------------------------------------------------------------- and ScanError(nil, cb, nil) be //----------------------------------------------------------------------------------------- [ XferError(nil, cb, nil) let vda = VirtualDiskDA(sysDisk, lv cb>>CB.diskAddress) let header, label, data = vec 2, cb>>CB.labelAddress, cb>>CB.dataAddress if cb>>CB.status<<DST.finalStatus eq checkError then //header check error [ XferPage(DCreadHLD, vda, data, label, header, lv Noop) if (cb>>CB.diskAddress xor 2) eq header>>DH.diskAddress & not banzaiFlag then [ PutTemplate(dsp, "*NThe disk in drive $D looks like disk $D ", cb>>CB.diskAddress.disk, header>>DH.diskAddress.disk) banzaiFlag = Banzai("of a file system.") ] ] if alterFlag then if Confirm("*NMay I rewrite the page?") then [ XferPage(DCwriteHLD, vda, data, label, 0, lv Noop) if (XferPage(DCreadD, vda, data, label, 0, lv Noop) & DSTgoodStatusMask) ne DSTgoodStatus then [ Ws(". It's incorrigable.") SetBlock(lv label>>DL.fileId, -2, lFID) XferPage(DCwriteHLD, vda, data, label, 0, lv Noop) ] ] ] //----------------------------------------------------------------------------------------- and CheckedVDA(lvRealDA) = valof //----------------------------------------------------------------------------------------- [ if lvRealDA>>DA.restore ne 0 resultis -3 if lvRealDA>>DA.sector ge sysDisk>>BFSDSK.nSectors resultis -3 if lvRealDA>>DA.track ge sysDisk>>BFSDSK.nTracks resultis -3 if lvRealDA>>DA.disk ge sysDisk>>BFSDSK.nDisks then [ unless banzaiFlag do [ Ws("*NThis looks like part of a two disk file system, ") banzaiFlag = Banzai("but you are scavenging a single disk.") ] resultis -3 ] resultis VirtualDiskDA(sysDisk, lvRealDA) ] //----------------------------------------------------------------------------------------- and Banzai(string) = valof //----------------------------------------------------------------------------------------- [ logFlag = true Ws(string) Ws("*NCheck your disks, then type *"BANZAI!*" ") let answer = GetString("if you wish to continue scavenging: ") let banzai = "BANZAI!" if answer>>String.length ne banzai>>String.length finish for i = 1 to banzai>>String.length do [ let c1 = answer>>String.char↑i if c1 ge $a & c1 le $z then c1 = c1-($a-$A) let c2 = banzai>>String.char↑i if c2 ge $a & c2 le $z then c2 = c2-($a-$A) if c1 ne c2 finish ] Free(sysZone, answer) resultis true ] //----------------------------------------------------------------------------------------- and CheckFile(fd, nil) be //----------------------------------------------------------------------------------------- [ if fd>>FD.state ne fdBeingChecked then [ if fd>>FD.state eq fdToBeChecked then [ fd>>FD.state = fdBeingChecked restartFlag = true ] return ] // check its structure let vda, lastRD = fd>>FD.firstVDA, 0 fd>>FD.state = valof [ let rd = pt!vda if rd eq 0 % rd uge 177000b % rd>>RD.used ne 0 resultis fdBad test fd>>FD.duplicate ifso rd>>RD.fd = fd ifnot if rd>>RD.fd ne fd resultis fdBad if lastRD ne 0 then if lastRD>>RD.lastPN+1 ne rd>>RD.firstPN % lastRD ne pt!(rd>>RD.firstBack) resultis fdBad rd>>RD.used = true if rd>>RD.lastNext eq eofDA then [ if fd>>FD.duplicate ne 0 then [ fd>>FD.lastPN = rd>>RD.lastPN fd>>FD.lastVDA = vda + (rd>>RD.lastPN - rd>>RD.firstPN) XferPage(DCreadLD, fd>>FD.lastVDA) fd>>FD.lastNumChars = label>>DL.numChars ] resultis fdGood ] vda, lastRD = rd>>RD.lastNext, rd ] repeat if fd>>FD.state ne fdGood return // mark it good vda = fd>>FD.firstVDA [ let rd = pt!vda while pt!vda eq rd do [ pt!vda = ptGood; vda = vda +1 ] vda = rd>>RD.lastNext ] repeatuntil vda eq eofDA ] //----------------------------------------------------------------------------------------- and ForAllFDs(Proc, arg) be //----------------------------------------------------------------------------------------- // Enumerates all active FDs and calls Proc(fd, arg) [ let fdb = fdbQ!0; while fdb ne 0 do [ let ptr = lenDB; while ptr ls fdb>>DB.ptr do [ let fd = fdb + ptr Proc(fd, arg) ptr = ptr + lenFD ] fdb = fdb>>DB.link ] ] //----------------------------------------------------------------------------------------- and CreateD(q) = valof //----------------------------------------------------------------------------------------- // returns a pointer to an RD or an FD. [ manifest dbSize = 240 + lenDB //divisible by lenFD and lenRD let db, lenD = q>>DQ.tail, q>>DQ.lenD if q>>DQ.head eq 0 % db>>DB.ptr+lenD gr db>>DB.max then [ db = Allocate(sysZone, dbSize); if db eq 0 resultis 0 Zero(db, dbSize) db>>DB.ptr = lenDB db>>DB.max = dbSize Enqueue(q, db) ] let d = db + db>>DB.ptr db>>DB.ptr = db>>DB.ptr + lenD resultis d ] //----------------------------------------------------------------------------------------- and CompactDBs() be //----------------------------------------------------------------------------------------- [ let lowestFDB = LowestFDB() [ let rdb = Dequeue(rdbQ); if rdb eq 0 break test rdb ugr lowestFDB ifso [ MoveBlock(rdb, lowestFDB, rdb>>DB.max) Unqueue(fdbQ, lowestFDB) Free(sysZone, lowestFDB) Enqueue(fdbQ, rdb) lowestFDB = LowestFDB() ] ifnot Free(sysZone, rdb) ] repeat Zero(bt, maxVDA/8+1) ForAllFDs(RehashFD) ] //----------------------------------------------------------------------------------------- and RehashFD(fd, nil) be //----------------------------------------------------------------------------------------- [ let ha = (fd>>FD.sn.word2 & 77777b) rem (maxVDA/8+1) [ if bt!ha eq 0 then [ bt!ha = fd; return ] ha = ha eq maxVDA/8? 0, ha+1 ] repeat ] //----------------------------------------------------------------------------------------- and LowestFDB() = valof //----------------------------------------------------------------------------------------- [ let lowestFDB = 177777b let fdb = fdbQ!0; while fdb ne 0 do [ if fdb uls lowestFDB then lowestFDB = fdb fdb = fdb>>DB.link ] resultis lowestFDB ]