// ScavUtilB.bcpl -- Scavenger utility routines
// Copyright Xerox Corporation 1979, 1980, 1981, 1982
// Last modified May 10, 1982  5:48 PM by Boggs

get "AltoFileSys.d"
get "AltoDefs.d"
get "Streams.d"
get "Disks.d"
get "BFS.d"
get "Scavenger.decl"

external
[
// outgoing procedures
DiskToCursor; SysErr; CopyString
XferPage; XferError; TryDisk; FindDShape
ScavAssignDiskPage; ScavReleaseDiskPage
GetString; Confirm; Ws; Wss; WouldHave

// incoming procedures
GetBit; SetBit; MulDiv

SimpleDspEraseBits; SimpleDspCharWidth
Gets; Puts; MoveBlock; Zero; Allocate; DefaultArgs
InitializeDiskCBZ; GetDiskCb; DoDiskCommand
VirtualDiskDA; BfsMakeFpFromLabel
PutTemplate

// incoming statics
sysZone; sysDisk; dsp; keys
logFlag; alterFlag; label; data
maxVDA; usedBT; badBT
]

structure String [ length byte; char↑1,1 byte ]

//-----------------------------------------------------------------------------------------
let DiskToCursor() be
//-----------------------------------------------------------------------------------------
[
@mouseX = 450 + diskAddress>>DA.disk lshift 7 //450 or 578
@mouseY = diskAddress>>DA.track ls 0? 0,
 20 + MulDiv(808-40-16, diskAddress>>DA.track, sysDisk>>BFSDSK.nTracks)
]

//-----------------------------------------------------------------------------------------
and Ws(string) be Wss(dsp, string)
//-----------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
and Wss(stream, string) be
//-----------------------------------------------------------------------------------------
   for i = 1 to string>>String.length do Puts(stream, string>>String.char↑i)

//-----------------------------------------------------------------------------------------
and WouldHave(nil, nil) be unless alterFlag do Ws("Would have ")
//-----------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
and SysErr(p1, errNo, p2, p3, p4, p5) be
//-----------------------------------------------------------------------------------------
[
let temp = p1; p1 = errNo; errNo = temp
(table [ 77403b; 1401b ])("Sys.Errors", lv p1)
]

//-----------------------------------------------------------------------------------------
and Confirm(prompt) = valof
//-----------------------------------------------------------------------------------------
[
Ws(prompt)
switchon Gets(keys) into
   [
   case $Y: case $y: case $*N:
      [ Ws(" Yes"); resultis true ]
   case $N: case $n: case $*177:
      [ Ws(" No"); resultis false ]
   default: Ws(" ?")
   ] repeat
]

//-----------------------------------------------------------------------------------------
and CopyString(dest, source) be
//-----------------------------------------------------------------------------------------
   MoveBlock(dest, source, source>>String.length rshift 1 +1)

//-----------------------------------------------------------------------------------------
and GetString(prompt) = valof
//-----------------------------------------------------------------------------------------
[
Ws(prompt)
let string, count = vec 128, 0
   [
   let char = Gets(keys)
   switchon char into
      [
      case $*001: case $*010:
         [
         if count ne 0 then
            [
            SimpleDspEraseBits(dsp, -SimpleDspCharWidth(dsp, string>>String.char↑count))
            count = count -1
            ]
         endcase
         ]
      case $*S: case $*N: case $*033: break
      case $*027:
         [
         for i = count to 1 by -1 do
            SimpleDspEraseBits(dsp, -SimpleDspCharWidth(dsp, string>>String.char↑i))
         count = 0
         endcase
         ]
      case $*177:
         [
         Ws(" XXX ")
         count = 0
         break
         ]
      default:
         [
         if char ge $*S & char le $*177 & count ls 255 then
            [
            count = count +1
            string>>String.char↑count = char
            Puts(dsp, char)
            ]
         endcase
         ]
      ]
   ] repeat

// leave room for the string to get longer by one character
let res = Allocate(sysZone, count/2+2)
res>>String.length = count
for i = 1 to count do res>>String.char↑i = string>>String.char↑i
resultis res
]

//-----------------------------------------------------------------------------------------
and XferPage(action, vda, d, l, h, lvError; numargs na) = valof
//-----------------------------------------------------------------------------------------
[
let header = vec 1
DefaultArgs(lv na, -2, data, label, header, lv XferError)
let cbz = vec CBzoneLength
InitializeDiskCBZ(sysDisk, cbz, 0, CBzoneLength, XferRetry, lvError)
cbz>>CBZ.cleanupRoutine = XferCleanup
cbz>>CBZ.client = h
XferRetry: let cb = GetDiskCb(sysDisk, cbz)
cb>>CB.labelAddress = l
let fp = vec lFP; BfsMakeFpFromLabel(fp, l)
DoDiskCommand(sysDisk, cb, d, vda, fp, l>>DL.pageNumber, action)
while @cbz>>CBZ.queueHead ne 0 do GetDiskCb(sysDisk, cbz)
resultis cbz>>CBZ.currentPage
]

//-----------------------------------------------------------------------------------------
and XferCleanup(disk, cb, cbz) be
//-----------------------------------------------------------------------------------------
[
MoveBlock(cbz>>CBZ.client, cb>>CB.headerAddress, 2)
cbz>>CBZ.currentPage = cb>>CB.status
]

//-----------------------------------------------------------------------------------------
and XferError(nil, cb, nil) be
//-----------------------------------------------------------------------------------------
[
logFlag = true
let rda, vda = cb>>CB.diskAddress, VirtualDiskDA(sysDisk, lv cb>>CB.diskAddress)
PutTemplate(dsp, "*NHard error at VDA $UOb = Unit $D, Cylinder $D, Head $D, Sector $D.",
 vda, rda<<DA.disk, rda<<DA.track, rda<<DA.head, rda<<DA.sector)
let dl = cb>>CB.labelAddress
if cb>>CB.command.labelAction eq 0 & cb>>CB.status.finalStatus ne 1 then
   PutTemplate(dsp, "*NLabel was: SerialNumber $EUOb, PageNumber $D, NumChars $D.",
    lv dl>>DL.fileId.serialNumber, dl>>DL.pageNumber, dl>>DL.numChars)
Ws("*NAttempted action was:")
for i = 1 to 3 do
   [
   let action = selecton i into
      [
      case 1: cb>>CB.command.headerAction
      case 2: cb>>CB.command.labelAction
      case 3: cb>>CB.command.dataAction
      ]
   action = selecton action into
      [ case 0: "Read"; case 1: "Check"; case 2 to 3: "Write" ]
   let record = selecton i into
      [ case 1: "Header"; case 2: "Label"; case 3: "Data" ]
   PutTemplate(dsp, " $S $S$C", action, record, (i eq 3? $., $,))
   ]
Ws("*NResulting status was:")
let dst = cb>>CB.status
if dst<<DST.seekFail then Ws(" SeekFailed")
if dst<<DST.seeking then Ws(" Seeking")
if dst<<DST.notReady then Ws(" NotReady")
if dst<<DST.dataLate then Ws(" DataLate")
if dst<<DST.noTransfer then Ws(" NoTransfer")
if dst<<DST.checksumError then Ws(" ChecksumError")
if dst<<DST.finalStatus then Ws(selecton dst<<DST.finalStatus into
   [
   case 1: (dst&360b) ne 0? "", " SectorLate"
   case 2: " CheckError"
   case 3: " IllegalSector"
   ])
]

//-----------------------------------------------------------------------------------------
and TryDisk(dsk, trk, hd, sect) = valof
//-----------------------------------------------------------------------------------------
[
let kcb = vec lKCB; Zero(kcb, lKCB)
kcb>>KCB.command = readLD
kcb>>KCB.headerAddress = lv kcb>>KCB.header
kcb>>KCB.labelAddress = label
kcb>>KCB.dataAddress = data
kcb>>KCB.diskAddress.disk = dsk
kcb>>KCB.diskAddress.track = trk
kcb>>KCB.diskAddress.head = hd
kcb>>KCB.diskAddress.sector = sect
until @diskCommand eq 0 loop
for trys = 1 to 5 do
   [
   kcb>>KCB.status = 0
   @diskCommand = kcb
   while (kcb>>KCB.status & DSTdoneBits) eq 0 loop
   if (kcb>>KCB.status & DSTgoodStatusMask) eq DSTgoodStatus break
   ]
resultis kcb>>KCB.status
]

//-----------------------------------------------------------------------------------------
and FindDShape(ld) = valof
//-----------------------------------------------------------------------------------------
[
if ld>>LD.propertyBegin ge offset LD.leaderProps/16 &
 ld>>LD.propertyBegin + ld>>LD.propertyLength le
 offset LD.leaderProps/16 + size LD.leaderProps/16 then
   [
   let ofset = ld>>LD.propertyBegin
   while ofset ls ld>>LD.propertyLength-lDSHAPE-1 do
      [
      let prop = ld + ofset
      if prop>>FPROP.type eq fpropTypeDShape then
         test prop>>FPROP.length eq lDSHAPE+1
            ifso resultis ofset
            ifnot break // bogus leader page
      test prop>>FPROP.length eq 0
        ifso resultis ofset
        ifnot ofset = ofset + prop>>FPROP.length
      ]
   ]

ld>>LD.propertyBegin = offset LD.leaderProps/16
ld>>LD.propertyLength = size LD.leaderProps/16
Zero(lv ld>>LD.leaderProps, size LD.leaderProps/16)
resultis offset LD.leaderProps/16
]

//-----------------------------------------------------------------------------------------
and ScavAssignDiskPage(disk, vda) = valof
//-----------------------------------------------------------------------------------------
[
if vda eq eofDA then vda = 0
// first, try to assign a free page
let i = vda +1
   [
   if i eq vda break
   if i eq maxVDA+1 then i = 0
   if GetBit(usedBT, i) eq 0 then
     [
     SetBit(usedBT, i, true)
     disk>>BFSDSK.freePages = sysDisk>>BFSDSK.freePages -1
     resultis i
     ]
   i = i +1
   ] repeat
// next, try to reuse a bad page
i = vda +1
   [
   if i eq vda then SysErr(0, ecDiskFull, 0)
   if i eq maxVDA+1 then i = 0
   if GetBit(badBT, i) then
      [
      SetBit(badBT, i, false)
      resultis i
      ]
   i = i+1
   ] repeat
]

//-----------------------------------------------------------------------------------------
and ScavReleaseDiskPage(disk, vda) = valof
//-----------------------------------------------------------------------------------------
[
SetBit(usedBT, vda, false)
disk>>BFSDSK.freePages = sysDisk>>BFSDSK.freePages +1
]