// IfsScav1-2.bcpl - Pass 1 Phase 2
// Copyright Xerox Corporation 1979, 1982
// Last modified February 13, 1982  3:37 PM by Boggs

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

external
[
// outgoing procedures
Pass1Phase2

// incoming procedures
PVWR; PVRR; LockCell; UnlockCell; FlushBuffers
Allocate; Free; MultEq; MoveBlock; RealDiskDA
Gets; PutTemplate; Ws; Wss; PrintPLME
EnumerateLPT; GetLpteIfsName; GetLpteTfsName
GetLpteFa; SetLpteFa; GetLpteFlags; SetLpteFlags
SetLpteType; GetLpteIfp; GetLptLpte

// incoming statics
dsp; keys; sysZone; debugFlag; scavDisk; phase
plme; maxVDA; bytesPerPage; numPages; lpt
]

static [ printLPTE; printPLME; curVDA ]

//-----------------------------------------------------------------------------------------
let Pass1Phase2() = valof
//-----------------------------------------------------------------------------------------
// This is basically the marking pass of a page garbage collector.
// Starting from the set of leader pages, it marks all accessible
//  file pages and checks that chains of pages are 'well formed'.
// The output of this phase is some changes to the plm and the lpt.
// This phase never references the pack being scavenged!
[
phase = 2
Ws("*N[1-2]"); if debugFlag then Gets(keys)
plme = 0; LockCell(lv plme)
numPages = 0
EnumerateLPT(lpt, CheckFile, nil)
PutTemplate(dsp, "*N[1-2] $UD pages used out of $UD", numPages, maxVDA)
UnlockCell(lv plme)
FlushBuffers()
resultis true
]

//-----------------------------------------------------------------------------------------
and CheckFile(l, lpte, nil) be
//-----------------------------------------------------------------------------------------
// This is called once for each leader page table entry.
// It follows the chain of pages starting at the leader page.
[
printLPTE = true

// follow the pointers and build a maximal length chain
let lastPN, lastVDA = -1, eofDA
let ifp = GetLpteIfp(lpte)
curVDA = ifp>>IFP.page
let lfFlags = 0

   [pageLoop
   plme = PVWR(curVDA)
   printPLME = true
   // We check 3 things in deciding whether a page belongs to a chain:
   //	nextP	backP	FID	Action
   //	ok	ok	ok	no action
   //	ok	ok	bad	fix FID
   //	ok	bad	ok	fix backP
   //	ok	bad	bad	backup and truncate
   //	bad	d/c	d/c	backup and truncate
   //	bad	d/c	d/c	backup and truncate
   //	bad	d/c	d/c	backup and truncate
   //	bad	d/c	d/c	backup and truncate
   manifest [ nextpBad = 1; backpBad = 2; fidBad = 4 ]
   let state = 0

   // this page must not be free, bad, or incorrigable
   if plme>>PLME.type ne ptGood then
      [
      PrintPage("this is a $S page!", selecton plme>>PLME.type into
         [
         case ptGood: "good"
         case ptBad: "bad"
         case ptIncorr: "incorrigable"
         case ptFree: "free"
         default: "?"
         ])
      state = state % nextpBad
      ]

   // this page must point back to where we just came from
   if plme>>PLME.backP ne lastVDA then
      [
      PrintPage("back pointer is wrong")
      state = state % backpBad
      ]

   // this page's FID must match the file's FID
   unless MultEq(lv plme>>PLME.fileId, lv ifp>>IFP.serialNumber, lFID) do
      [
      PrintPage("file ID is wrong")
      state = state % fidBad
      ]

//Pass1Phase2 (cont'd)

   switchon state into
      [
      case 0: endcase  //everything ok
      case fidBad:
         [
         plme>>PLME.rewrite = true
         PrintPage("setting FID to $EUO;$UO",
          lv ifp>>IFP.serialNumber, ifp>>IFP.version)
         MoveBlock(lv plme>>PLME.fileId, lv ifp>>IFP.serialNumber, lFID)
         endcase
         ]
      case backpBad:
         [
         plme>>PLME.rewrite = true
         PrintPage("setting back pointer to $UO", lastVDA)
         plme>>PLME.backP = lastVDA
         endcase
         ]
      case fidBad+backpBad:
      default:
         [
         if lastPN eq -1 then
            [
            // This isn't a very good prospect for a leader page.
            // Act like we never saw the plme and get rid of that lpte!
            SetLpteType(lpte, dvTypeFree)
            return
            ]
         PrintPage("backing up to vda $UO and truncating", lastVDA)
         curVDA = lastVDA  //back up
         plme = PVWR(curVDA)
         printPLME = true
         plme>>PLME.rewrite = true
         plme>>PLME.nextP = eofDA  //truncate
         break  //pageLoop
         ]
      ]

//Pass1Phase2 (cont'd)

   // page is accessible, though it may have non-fatal problems
   numPages = numPages +1
   plme>>PLME.accessible = true

   // check page number
   if plme>>PLME.pn ne lastPN+1 then
      [
      plme>>PLME.rewrite = true
      PrintPage("setting page number to $UO", lastPN+1)
      plme>>PLME.pn = lastPN+1
      ]

   // if this is the last page then exit pageLoop
   if plme>>PLME.nextP eq eofDA break  //end of chain

   // this is not the last page, so it must be full
   if plme>>PLME.numChars ne bytesPerPage then
      [
      plme>>PLME.rewrite = true
      PrintPage("setting num Chars to $UO", bytesPerPage)
      plme>>PLME.numChars = bytesPerPage
      ]

   // advance to next page
   lastPN = plme>>PLME.pn
   lastVDA = curVDA
   curVDA = plme>>PLME.nextP
   if plme>>PLME.rewrite then lfFlags = lfFlags % lfDamaged
   ]pageLoop repeat

// We are now at the end of a well-formed chain of pages
//  that started with a page that looked like a leader page.

// last page must not be full
if plme>>PLME.numChars eq bytesPerPage then
   [
   plme>>PLME.rewrite = true
   PrintPage("setting numChars to $UO", bytesPerPage-1)
   plme>>PLME.numChars = bytesPerPage-1
   ]

// in case we truncated the file or the last page was full:
if plme>>PLME.rewrite then lfFlags = lfFlags % lfDamaged

// verify the last fa hint kept in the leader page
let hintFA = GetLpteFa(lpte)
let realFA = vec lFA
realFA>>FA.da = curVDA
realFA>>FA.pageNumber = plme>>PLME.pn
realFA>>FA.charPos = plme>>PLME.numChars
unless MultEq(hintFA, realFA, lFA) do
   [
   PrintPage("setting last fa hint: vda $UO, page number $UO, num chars $UO",
    realFA>>FA.da, realFA>>FA.pageNumber, realFA>>FA.charPos)
   SetLpteFa(lpte, realFA)
   lfFlags = lfFlags % lfRewrite
   ]

// files must contain at least two pages
test plme>>PLME.pn eq 0
   ifso
      [
      PrintPage("deleted this file - just a leader page")
      plme>>PLME.accessible = false  //free this page (done by [1-3])
      SetLpteType(lpte, dvTypeFree)  //not a file so forget this leader page
      ]
   ifnot if lfFlags ne 0 then SetLpteFlags(lpte, GetLpteFlags(lpte) % lfFlags)
]

//-----------------------------------------------------------------------------------------
and PrintPage(string, arg0, arg1, arg2, arg3; numargs na) be
//-----------------------------------------------------------------------------------------
[
if printLPTE then
   [
   let lpte = GetLptLpte(lpt, false)
   let ifp = GetLpteIfp(lpte)
   PutTemplate(dsp,
    "*N[1-2] FID $EUO;$UO, IFS name *"$S*", TFS name *"$S*"",
    lv ifp>>IFP.serialNumber, ifp>>IFP.version,
    GetLpteIfsName(lpte), GetLpteTfsName(lpte))
   printLPTE = false
   ]

if printPLME then
   [
   PrintPLME(curVDA)
   printPLME = false
   ]

if string ne 0 & na gr 0 then
   [
   Ws("*N[1-2]*T*T")
   PutTemplate(dsp, string, arg0, arg1, arg2, arg3)
   ]
]