// 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)
]