// IfsScav1-3.bcpl - Pass 1 Phase 3
// Copyright Xerox Corporation 1979, 1980, 1982
// Last modified July 27, 1982  7:30 PM by Boggs

get "IfsScavenger.decl"
get "Streams.d"
get "Disks.d"
get "TFS.d"

external
[
// outgoing procedures
Pass1Phase3

// incoming procedures
OpenFile; Closes; Puts; Gets
RealDiskDA; ActOnDiskPages
InitializeDiskCBZ; GetDiskCb; DoDiskCommand
Allocate; Free; Zero; MoveBlock; IFSError; Usc
EnumerateLPT; GetLpteIfp; GetLpteType; GetLpteFlags
GetLpteIfsName; GetLpteTfsName; GetLpteFa
ReadCalendar; PrintDiskError; SysErr
CopyString; PutTemplate; Ws; PrintTime; PrintPLME
PVRR; LockCell; UnlockCell

// incoming statics
sysZone; dsp; keys; freePageFid; debugFlag
scavDisk; scratchDisk; maxVDA; wordsPerPage
plme; bpl; phase; lpt
]

manifest ecImpossibleType = 502

//-----------------------------------------------------------------------------------------
let Pass1Phase3() = valof
//-----------------------------------------------------------------------------------------
// This phase makes the changes to the disk that were announced
//  during phase 2.  Any page that is not free and was not marked
//  'accessible' during phase 2 is made free here, and a truth
//  bit map is built up in 'scavenger.dd'.
[
phase = 3
Ws("*N[1-3]"); if debugFlag then Gets(keys)
let dds = OpenFile("IfsScavenger.bitTable", ksTypeWriteOnly,
 0, 0, 0, 0, 0, 0, scratchDisk)
if dds eq 0 then IFSError(ecScratchFile, "IfsScavenger.bitTable")

// rewrite any labels that need changing
Ws("*N[1-3] PLM & BT"); if debugFlag then Gets(keys)
let startTime = vec 1; ReadCalendar(startTime)
let cbz = Allocate(sysZone, CBzoneLength)
plme = 0; LockCell(lv plme)
let bmWord, bmCount = 0, 0  //bit map
let free, rewrite = nil, nil
let vda = 0
   [pageLoop
   plme = PVRR(vda)
   switchon plme>>PLME.type into
      [
      case ptFree:
         [ free = true; rewrite = plme>>PLME.rewrite; endcase ]
      case ptIncorr:
         [ free = false; rewrite = true; endcase ]
      case ptBad:
         [
         MoveBlock(lv plme>>PLME.fileId, freePageFid, lFID)
         free = true; rewrite = true
         endcase
         ]
      case ptGood:
         [
         if plme>>PLME.accessible eq 0 & vda ne 0 then
            [  //vda 0 is special.  No sense alarming people.
            if debugFlag then
               [ Ws("*N[1-3] Inaccessible page:"); PrintPLME(vda) ]
            docase ptBad
            ]
         free = false; rewrite = plme>>PLME.rewrite
         endcase
         ]
      default: IFSError(ecImpossibleType)
      ]

   // special page zero check
   if vda eq 0 then
      [
      // vda 0 plays a crucial role in end-of-file detection
      //  and must have a special label, so unconditionally set it here.
      // Note that this may not be physical page 0 if this is a T-300.
      Zero(plme, lenPLME)
      free = false
      rewrite = true
      ]

// Pass1Phase3 (cont'd)

   // record page in bit table
   bmWord = (bmWord lshift 1) + (free? 0, 1)
   bmCount = bmCount +1; if bmCount eq 16 then
      [
      Puts(dds, bmWord)
      bmCount, bmWord = 0, 0
      ]

   if rewrite then
      [
      // some change to the page is necessary.
      // only the label is rewritten from the information in the plme.
      InitializeDiskCBZ(scavDisk, cbz, 0, CBzoneLength,
       plmRetry, lv Phase3Error)
      plmRetry:  //<-------------===   *****
      let cb = GetDiskCb(scavDisk, cbz)
      let label = lv cb>>CB.label
      label>>DL.numChars = plme>>PLME.numChars
      RealDiskDA(scavDisk, plme>>PLME.nextP, lv label>>DL.next)
      RealDiskDA(scavDisk, plme>>PLME.backP, lv label>>DL.previous)
      DoDiskCommand(scavDisk, cb, 0, vda, lv plme>>PLME.fileId,
       plme>>PLME.pn, (plme>>PLME.type eq ptIncorr? DCwriteHLD, DCwriteLnD))
      while @cbz>>CBZ.queueHead ne 0 do GetDiskCb(scavDisk, cbz)
      ]
   vda = vda +1
   ]pageLoop repeatuntil Usc(vda, maxVDA) gr 0

PrintTime(startTime)
UnlockCell(lv plme)

// fill out last bit table word and close bit table file
if bmCount ne 0 then
   Puts(dds, bmWord lshift (16-bmCount) + -1 rshift bmCount)
Closes(dds)

// write out the bad page list
// ActOnPages is not used, because we want REAL page 0, not just VDA 0
Ws("*N[1-3] BPL"); if debugFlag then Gets(keys)
InitializeDiskCBZ(scavDisk, cbz, 0, CBzoneLength,
 bplRetry, lv Phase3Error)
bplRetry:  //<-------------===   *****
let fpBPL = vec lFP; Zero(fpBPL, lFP)
let cb = GetDiskCb(scavDisk, cbz)
DoDiskCommand(scavDisk, cb, bpl, fillInDA, fpBPL, 0, DCwriteD)
while @cbz>>CBZ.queueHead ne 0 do GetDiskCb(scavDisk, cbz)
Free(sysZone, cbz)

// The disk now contains only well formed files and free pages.
// We have a correct bit table, but don't yet know the state
//  of SysDir or DiskDescriptor.

// rewrite leader pages that need changing
Ws("*N[1-3] LPT"); if debugFlag then Gets(keys)
let ld = Allocate(sysZone, wordsPerPage)
EnumerateLPT(lpt, CheckLD, ld)
Free(sysZone, ld)
resultis true
]

//-----------------------------------------------------------------------------------------
and CheckLD(l, lpte, ld) be
//-----------------------------------------------------------------------------------------
// Called for each lpte, it updates the leader page if necessary,
//  and remembers the highest serial number (used when rebuilding the
//  disk descriptor in phase 4).
[
if GetLpteType(lpte) eq dvTypeFile then
   [
   let ifp = GetLpteIfp(lpte)
   let flags = GetLpteFlags(lpte)
   if (flags & (lfRewrite % lfDamaged)) ne 0 then
      [
      ActOnDiskPages(scavDisk, lv ld, lv ifp>>IFP.page, ifp, 0, 0, DCreadD)
      if (flags & lfRewrite) ne 0 then
         [
         MoveBlock(lv ld>>LD.hintLastPageFa, GetLpteFa(lpte), lFA)
         if GetLpteTfsName(lpte)>>String.length ne 0 then
            CopyString(lv ld>>ILD.name, GetLpteTfsName(lpte))
         if GetLpteIfsName(lpte)>>String.length ne 0 then
            CopyString(lv ld>>ILD.pathName, GetLpteIfsName(lpte))
         ]
      if (flags & lfDamaged) ne 0 then ld>>ILD.damaged = true
      ActOnDiskPages(scavDisk, lv ld, lv ifp>>IFP.page, ifp, 0, 0, DCwriteD)
      ]

   // remember the largest file Sn.
   let fileSn = lv ifp>>IFP.serialNumber
   let lastSn = lv scavDisk>>TFSDSK.lastSn
   switchon Usc(lastSn>>ISN.part1, fileSn>>ISN.part1) into
      [
      case 1: endcase
      case 0: if Usc(lastSn>>ISN.part2, fileSn>>ISN.part2) ge 0 endcase
      case -1:
         [
         lastSn>>ISN.part2 = fileSn>>ISN.part2
         lastSn>>ISN.part1 = fileSn>>ISN.part1
         endcase
         ]
      ]
   ]
]

//-----------------------------------------------------------------------------------------
and Phase3Error(nil, cb, errorCode) be
//-----------------------------------------------------------------------------------------
   test errorCode eq ecUnRecovDiskError
      ifso PrintDiskError(cb)
      ifnot SysErr(0, errorCode, cb)