// Scavenger.bcpl -- Alto file system scavenger // Copyright Xerox Corporation 1979, 1980, 1981, 1982 // Last modified May 12, 1982 1:21 AM by Boggs // Design and construction: James H. Morris, 1974. // Maintenance: Richard K. Johnsson, 1976-1981. // Renovation: David R. Boggs, 1982. get "AltoDefs.d" get "AltoFileSys.d" get "Streams.d" get "SysDefs.d" get "Disks.d" get "BFS.d" get "Scavenger.decl" external [ // outgoing procedures SplitPuts // incoming procedures InitScavenger FixDirs; ScanDisk; ForAllFDs Confirm; GetBit; Ws; WouldHave StartLog; StopLog; XferPage; TryDisk Endofs; Gets; Puts; Closes; Resets BFSInit; CloseDisk; VirtualDiskDA; RealDiskDA OpenFile; ReadBlock; WriteBlock; TruncateDiskStream PositionPage; SetFilePos; GetCompleteFa; FileLength MoveBlock; Zero; DisableInterrupts; DoubleAdd; RetryCall; Noop AddToZone; Allocate; Free; Enqueue; Dequeue; Junta; CallSwat LoadRam; InitBcplRuntime; EtherBoot; PutTemplate; SimpleDspPuts InitializeFstream; SetupFstream; CurrentPos // outgoing statics log; logFlag; pt; bt // incoming statics sysZone; sysDisk; keys; dsp maxVDA; alterFlag; label; data usedBT; badBT RamImage ] static [ log; logFlag; pt; bt lenHashTab; hashTab; ramFlag ] //----------------------------------------------------------------------------------------- let Scavenger(blv, upe, cfa) be //----------------------------------------------------------------------------------------- [ ramFlag = LoadRam(RamImage) eq 0 if ramFlag then InitBcplRuntime() Junta((ramFlag? levBuffer, levBcpl), AfterJunta) ] //----------------------------------------------------------------------------------------- and AfterJunta() be //----------------------------------------------------------------------------------------- [ InitScavenger() let freeEnd = sysZone -1 //ahem... let freeBegin = ramFlag? InitScavenger, InitBcplRuntime AddToZone(sysZone, freeBegin, freeEnd-freeBegin) pt = Allocate(sysZone, maxVDA+1) bt = Allocate(sysZone, (maxVDA/16+1)*2) [ ScanDisk() FixSNs() FixDirs() if alterFlag then [ FixDD() CloseDisk(sysDisk) sysDisk = BFSInit(sysZone, true) if sysDisk eq 0 then CallSwat("BFSInit failed") ] StopLog() FixGarbage() let message = logFlag? (log? "See Scavenger.Log$", "Errors found"), "You have a beautiful file system!" if log ne 0 then [ Closes(log); log = 0 ] PutTemplate(dsp, "*N$S", message) if alterFlag then WriteRemCm(message) unless logFlag & not alterFlag do Finish() unless Confirm("*NDo it again, altering the disk to correct errors? ") do Finish() alterFlag, logFlag, log = true, false, StartLog() ] repeat ] //----------------------------------------------------------------------------------------- and FixDD() be //----------------------------------------------------------------------------------------- [ let dd = OpenFile("DiskDescriptor") SetFilePos(dd, 0, lKDHeader+sysDisk>>BFSDSK.diskBTsize) TruncateDiskStream(dd) //so Closes won't truncate Resets(dd) WriteBlock(dd, sysDisk>>DSK.diskKd, lKDHeader) WriteBlock(dd, usedBT, sysDisk>>BFSDSK.diskBTsize) Closes(dd) ] //----------------------------------------------------------------------------------------- and WriteRemCm(message) be //----------------------------------------------------------------------------------------- [ let fp = vec lFP; Zero(fp, lFP) let out = OpenFile("Rem.Cm", ksTypeWriteOnly, charItem, 0, fp) let in = OpenFile("Rem.Cm", ksTypeReadOnly, charItem, 0, fp) PutTemplate(out, "// $S*N", message) until Endofs(in) do Puts(out, Gets(in)) Closes(in) Closes(out) ] //----------------------------------------------------------------------------------------- and FixSNs() be //----------------------------------------------------------------------------------------- // This routine insures that the serial numbers of all files // are different by re-writing label blocks if necessary. // It also updates sysDisk's lastSN to be the largest SN of any file. [ lenHashTab = 1 ForAllFDs(FindMaxSN) hashTab = Allocate(sysZone, lenHashTab) Zero(hashTab, lenHashTab) ForAllFDs(ChangeSN) Free(sysZone, hashTab) ] //----------------------------------------------------------------------------------------- and FindMaxSN(fd, nil) be //----------------------------------------------------------------------------------------- [ lenHashTab = lenHashTab +2 if sysDisk>>BFSDSK.lastSn.part1 gr fd>>FD.sn.part1 return if sysDisk>>BFSDSK.lastSn.part1 eq fd>>FD.sn.part1 & sysDisk>>BFSDSK.lastSn.word2 uge fd>>FD.sn.word2 return sysDisk>>BFSDSK.lastSn.part1 = fd>>FD.sn.part1 sysDisk>>BFSDSK.lastSn.word2 = fd>>FD.sn.word2 ] //----------------------------------------------------------------------------------------- and ChangeSN(fd, nil) be //----------------------------------------------------------------------------------------- [ let ha = (fd>>FD.sn.word2 & 77777b) rem lenHashTab [ if hashTab!ha eq 0 then [ hashTab!ha = fd; return ] if (hashTab!ha)>>FD.sn.word1 eq fd>>FD.sn.word1 & (hashTab!ha)>>FD.sn.word2 eq fd>>FD.sn.word2 break ha = ha eq lenHashTab-1? 0, ha+1 //collision; reprobe ] repeat // fd is a duplicate SN DoubleAdd(lv sysDisk>>BFSDSK.lastSn, table [ 0; 1 ]) sysDisk>>BFSDSK.lastSn.directory = 0 //make sure MoveBlock(lv fd>>FD.sn, lv sysDisk>>BFSDSK.lastSn, lSN) // change the file's labels let vda = fd>>FD.firstVDA [ XferPage(DCreadLD, vda) if label>>DL.pageNumber eq 0 then [ logFlag = true PutTemplate(dsp, "*N$PChanged SerialNumber of $S from $EUOb to $EUOb.", WouldHave, nil, lv data>>LD.name, lv label>>DL.fileId.serialNumber, lv fd>>FD.sn) ] MoveBlock(lv label>>DL.fileId.serialNumber, lv fd>>FD.sn, lSN) label>>DL.fileId.version = 1 if alterFlag then XferPage(DCwriteLD, vda) vda = VirtualDiskDA(sysDisk, lv label>>DL.next) ] repeatuntil vda eq eofDA ] //----------------------------------------------------------------------------------------- and FixGarbage() be //----------------------------------------------------------------------------------------- [ let nextBadDA = 0 for vda = 1 to maxVDA do if GetBit(badBT, vda) then [ nextBadDA = vda; break ] unless nextBadDA ne 0 return let lastGDA, prevGDA = 0, 0 if alterFlag then [ // Capture address of first page let stream = OpenFile("Scavenger.Garbage$") let cfa = vec lCFA; GetCompleteFa(stream, cfa) Closes(stream) // Find last page of Scavenger.Garbage$ lastGDA = cfa>>CFA.fp.leaderVirtualDa [ XferPage(DCreadLD, lastGDA) if label>>DL.next eq 0 break lastGDA = VirtualDiskDA(sysDisk, lv label>>DL.next) ] repeat prevGDA = VirtualDiskDA(sysDisk, lv label>>DL.previous) // Fix previous page's forward link XferPage(DCreadLD, prevGDA) RealDiskDA(sysDisk, nextBadDA, lv label>>DL.next) XferPage(DCwriteLD, prevGDA) ] // Splice in the bad pages PutTemplate(dsp, "*N$PAdded these pages to Scavenger.Garbage$$:", WouldHave, nil) Ws("*NVDA New PN Old PN Old SN") let prevBadDA = prevGDA let currBadDA = nextBadDA let oldLabel = vec lDL [ XferPage(DCreadLD, currBadDA, 0, oldLabel) PutTemplate(dsp, "*N$U5Ob $U5D $U5D $EUOb", currBadDA, label>>DL.pageNumber+1, oldLabel>>DL.pageNumber, lv oldLabel>>DL.fileId.serialNumber) RealDiskDA(sysDisk, prevBadDA, lv label>>DL.previous) [ nextBadDA = nextBadDA +1 if nextBadDA gr maxVDA then [ RealDiskDA(sysDisk, lastGDA, lv label>>DL.next); break ] if GetBit(badBT, nextBadDA) then [ RealDiskDA(sysDisk, nextBadDA, lv label>>DL.next); break ] ] repeat label>>DL.pageNumber = label>>DL.pageNumber +1 if alterFlag then XferPage(DCwriteLD, currBadDA) prevBadDA = currBadDA currBadDA = nextBadDA ] repeatuntil currBadDA gr maxVDA // Adjust label in last page of Scavenger.Garbage$ label>>DL.next = 0 RealDiskDA(sysDisk, prevBadDA, lv label>>DL.previous) label>>DL.pageNumber = label>>DL.pageNumber +1 label>>DL.numChars = 0 if alterFlag then XferPage(DCwriteLD, lastGDA) ] //----------------------------------------------------------------------------------------- and Finish() be //----------------------------------------------------------------------------------------- [ let sys = CheckVitalFile("Sys.boot", 256) let exec = CheckVitalFile("Executive.run", 100) let font = CheckVitalFile("SysFont.al", 4) if alterFlag & sys ne 0 then //Reinstall sys.boot [ XferPage(DCreadLD, sys); XferPage(DCwriteHLD, 0) ] if sysDisk ne 0 then CloseDisk(sysDisk) if exec eq 0 % font eq 0 % sys eq 0 then [ Resets(dsp) Ws("*NIt's unlikely this disk will boot. Some vital files are missing or damaged.") Ws("*NFollowing each file name are steps outlining a way to recover.") unless sys do Ws("*N Sys.boot: 1)NetExec 2)NewOS 3)You need to Install.") unless exec do Ws("*N Executive.run: 1)NetExec 2)Ftp 3)Retrieve 'Executive.run'.") unless font do Ws("*N SysFont.al: 1)NetExec 2)Ftp 3)Retrieve a font as 'SysFont.al'.") if Confirm("*NShall I get the NetExec? ") then EtherBoot(10b) Ws("*NType any character to try booting from the disk anyway:") Gets(keys) ] Closes(dsp) // Boot the OS from the disk without using SIO. // This is how the microcode does it. DisableInterrupts() @displayListHead = 0; for i = 0 to 32000 loop @2 = TryDisk(0, 0, 0, 0) MoveBlock(402b, label, lDL) MoveBlock(1, data, 256) goto 1 ] //----------------------------------------------------------------------------------------- and CheckVitalFile(name, length) = valof //----------------------------------------------------------------------------------------- // Returns 0 if file is missing or less then length pages long. // Returns VDA of page 1 otherwise. [ unless alterFlag resultis true let s = OpenFile(name, ksTypeReadOnly); if s eq 0 resultis 0 let cfa, pn, vda = vec lCFA, nil, nil FileLength(s); GetCompleteFa(s, cfa) pn = cfa>>CFA.fa.pageNumber PositionPage(s, 1); GetCompleteFa(s, cfa) vda = cfa>>CFA.fa.da Closes(s) resultis pn uls length? 0, vda ] //----------------------------------------------------------------------------------------- and StartLog() = valof //----------------------------------------------------------------------------------------- [ log = Allocate(sysZone, lFS) InitializeFstream(log, charItem, LogOverflow, CallSwat) LogOverflow() resultis log ] //----------------------------------------------------------------------------------------- and LogOverflow(nil, char; numargs na) be //----------------------------------------------------------------------------------------- [ manifest [ blkSize = 256; blkChars = 2*(blkSize-2) ] let buf = Allocate(sysZone, blkSize, true) test buf eq 0 ifso log>>ST.puts = Noop //gulp... ifnot [ Enqueue(log, buf) buf!1 = blkChars SetupFstream(log, buf+2, 0, blkChars) if na eq 2 then RetryCall(log, char) ] ] //----------------------------------------------------------------------------------------- and StopLog() be //----------------------------------------------------------------------------------------- [ let s = 0 if logFlag & alterFlag then s = OpenFile("Scavenger.Log$", ksTypeWriteOnly, charItem) [ let buf = log>>ST.par1; if buf eq 0 break log>>ST.par1 = buf!0 if s then [ let nc = buf!0 eq 0? CurrentPos(log), buf!1 WriteBlock(s, buf+2, nc/2) if (nc & 1) eq 1 then Puts(s, ((buf+2)!(nc/2)) rshift 8) ] Free(sysZone, buf) ] repeat Free(sysZone, log); log = s ] //----------------------------------------------------------------------------------------- and SplitPuts(nil, char) be //----------------------------------------------------------------------------------------- [ SimpleDspPuts(dsp, char) //ahem if log ne 0 then Puts(log, char) ]