// IfsScavLpt.bcpl -- routines for accessing the leader page table
// Copyright Xerox Corporation 1979, 1980, 1982
// Last modified July 29, 1982  3:06 PM by Boggs

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

external
[
// outgoing procedures
OpenLPT; CloseLPT; EnumerateLPT; FlushLPT
GetLptLpte; GetLptFa; GetLptFs; GetLptHome
ReadLPTE; WriteLPTE; GetLptSorted; SetLptSorted

GetLpteType; GetLpteFlags; GetLpteLength; GetLpteDIFRec
GetLpteIfp; GetLpteFa; GetLpteIfsName; GetLpteTfsName

SetLpteType; SetLpteFlags; SetLpteLength; SetLpteDIFRec
SetLpteIfp; SetLpteFa; SetLpteIfsName; SetLpteTfsName

// incoming procedures
Zero; MoveBlock; SetBlock; DefaultArgs; CopyString; ExtractSubstring
OpenFile; TruncateDiskStream; DeleteFile; ReadLeaderPage
FileLength; FilePos; SetFilePos; JumpToFa; GetCurrentFa; CleanupDiskStream
Endofs; Gets; Puts; Closes; Resets; WriteBlock; ReadBlock
DoubleDifference; DoubleIncrement; ReadCalendar; PrintTime
IFSError; Allocate; Free; Enqueue; Dequeue; CallSwat

// incoming statics
sysZone; debugFlag; pass; phase; scratchDisk
]

manifest
[
ecSetLPTE = 507
ecMalformedLPTE = 508
dvTypeHome = 2
]

structure LPTE:		// Leader Page Table Entry
[
type bit 2		// dvTypeFile, dvTypeFree, dvTypeHome
flags bit 4		// bits defined in IfsScavenger.decl
privateFlags bit 2 =
   [
   dirty bit
   gotLonger bit
   ]
length byte		// in words including this one
ifp word = @IFP
fa word = @FA		// of last byte in file
// Following three items are offsets relative to LPTE.type.
// If they are zero, then no space has been allocated.
ifsName word
tfsName word
difRec word
]
manifest
[
lenLPTEHeader = size LPTE/16
maxLenLPTE = 1 lshift size LPTE.length
]

structure LPT:		// lpt>>ST.par1 points at this
[
fa word lFA
lpte word maxLenLPTE
lpth word =
   [
   sorted word		// true if not written since last sorted
   fs word nDisks
   home word lenHome
   ]
]
manifest
[
lenLPT = size LPT/16
lenLPTH = lenLPT - offset LPT.lpth/16
]

// Routines for dealing with Leader Page Table files

//-----------------------------------------------------------------------------------------
let OpenLPT(name, makeEmpty) = valof
//-----------------------------------------------------------------------------------------
[
let st = OpenFile(name, 0, 0, 0, 0, 0, 0, 0, scratchDisk)
if st eq 0 then IFSError(ecScratchFile, name)
let lpt = Allocate(sysZone, lenLPT); Zero(lpt, lenLPT)
st>>ST.par1 = lpt
test makeEmpty
   ifso
      [
      SetBlock(lv lpt>>LPT.fs, -1, nDisks)
      FlushLPT(st)
      TruncateDiskStream(st)
      ]
   ifnot
      [
      let lpte = Gets(st)
      if lpte<<LPTE.type ne dvTypeHome then IFSError(ecMalformedLPTE)
      ReadBlock(st, lv lpt>>LPT.lpth, lenLPTH)
      FileLength(st)  //append to lpt
      ]
GetCurrentFa(st, lv lpt>>LPT.fa)
resultis st
]

//-----------------------------------------------------------------------------------------
and CloseLPT(st, delete) = valof
//-----------------------------------------------------------------------------------------
[
if debugFlag then delete = false
unless delete do FlushLPT(st)
Free(sysZone, st>>ST.par1)
let ld = nil
if delete then
   [
   ld = Allocate(sysZone, 1 lshift scratchDisk>>DSK.lnPageSize)
   ReadLeaderPage(st, ld)
   ]
Closes(st)
if delete then
   [
   DeleteFile(lv ld>>LD.name, 0, 0, 0, 0, scratchDisk)
   Free(sysZone, ld)
   ]
resultis 0
]

//-----------------------------------------------------------------------------------------
and FlushLPT(st) be
//-----------------------------------------------------------------------------------------
[
Resets(st)
let lpte = 0
lpte<<LPTE.type = dvTypeHome
lpte<<LPTE.length = 1+lenLPTH
Puts(st, lpte)
WriteBlock(st, lv st>>ST.par1>>LPT.lpth, lenLPTH)
CleanupDiskStream(st)
]

//-----------------------------------------------------------------------------------------
and ReadLPTE(st, lpte; numargs na) = valof
//-----------------------------------------------------------------------------------------
// Reads the next lpte from lpt 
// Returns pointer to lpte or zero if EOF encountered.
[
if Endofs(st) resultis 0
if na ls 2 then lpte = lv st>>ST.par1>>LPT.lpte
lpte!0 = Gets(st)
ReadBlock(st, lpte+1, lpte>>LPTE.length-1)
lpte>>LPTE.privateFlags = 0  //clear dirty and gotLonger bits
resultis lpte
]

//-----------------------------------------------------------------------------------------
and WriteLPTE(st, lpte; numargs na) be
//-----------------------------------------------------------------------------------------
// Write an lpte at the current stream position
// By default, the lpte comes from the lpt
[
if na ls 2 then lpte = lv st>>ST.par1>>LPT.lpte
if lpte>>LPTE.length eq 0 then CallSwat("LPTE with 0 length")
WriteBlock(st, lpte, lpte>>LPTE.length)
st>>ST.par1>>LPT.sorted = false
]

//-----------------------------------------------------------------------------------------
and GetLptLpte(st, initIt; numargs na) = valof
//-----------------------------------------------------------------------------------------
// Get a pointer to the lpte in 'lpt'.
// Get it ready for filling if 'initIt' is true.
[
let lpte = lv st>>ST.par1>>LPT.lpte
if na gr 1 & initIt then
   [
   Zero(lpte, maxLenLPTE)
   SetLpteLength(lpte, lenLPTEHeader)
   SetLpteType(lpte, dvTypeFile)
   ]
resultis lpte
]

//-----------------------------------------------------------------------------------------
and GetLptFs(st) = lv st>>ST.par1>>LPT.fs
//-----------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
and GetLptFa(st) = lv st>>ST.par1>>LPT.fa
//-----------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
and GetLptHome(st) = lv st>>ST.par1>>LPT.home
//-----------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
and GetLptSorted(st) = st>>ST.par1>>LPT.sorted
//-----------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
and SetLptSorted(st, val) be st>>ST.par1>>LPT.sorted = val
//-----------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
and EnumerateLPT(st, Proc, arg) be
//-----------------------------------------------------------------------------------------
// Enumerates the LPT, calling Proc(lpte, arg) for each lpte.
// The enumeration begins at the first record for the current pack.
// If Proc dirties the lpte, it is written back onto the file.
// If the length changed so that it can't be written back in place,
//  it is written on a temporary file, and appended to the real file
//  after the enumeration is complete.
[
let ovflLpt = 0
JumpToFa(st, lv st>>ST.par1>>LPT.fa)
let startTime = vec 1; ReadCalendar(startTime)

   [
   let lpte = ReadLPTE(st)
   if lpte eq 0 break  //end of file
   let length = lpte>>LPTE.length
   Proc(st, lpte, arg)
   if lpte>>LPTE.dirty then
      [
      if lpte>>LPTE.gotLonger then
         [
         if ovflLpt eq 0 then
            ovflLpt = OpenLPT("IfsScavenger.ovflLpt", true)
         WriteBlock(ovflLpt, lpte, lpte>>LPTE.length)
         lpte>>LPTE.type = dvTypeFree
         lpte>>LPTE.length = length
         ]
      let filePos = vec 1; FilePos(st, filePos)
      DoubleIncrement(filePos, -(length lshift 1))
      SetFilePos(st, filePos)
      WriteBlock(st, lpte, length)
      st>>ST.par1>>LPT.sorted = false
      ]
   ] repeat

if ovflLpt ne 0 then
   [
   Resets(ovflLpt)
      [
      let lpte = ReadLPTE(ovflLpt)
      if lpte eq 0 break
      if lpte>>LPTE.type eq dvTypeFile then
         WriteBlock(st, lpte, lpte>>LPTE.length)
      ] repeat
   CloseLPT(ovflLpt, true)
   st>>ST.par1>>LPT.sorted = false
   ]

PrintTime(startTime)
]

//these routines get fields of an lpte

//-----------------------------------------------------------------------------------------
and GetLpteType(lpte) = lpte>>LPTE.type
//-----------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
and GetLpteFlags(lpte) = lpte>>LPTE.flags
//-----------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
and GetLpteLength(lpte) = lpte>>LPTE.length
//-----------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
and GetLpteIfp(lpte) = lv lpte>>LPTE.ifp
//-----------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
and GetLpteFa(lpte) = lv lpte>>LPTE.fa
//-----------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
and GetLpteIfsName(lpte) = valof
//-----------------------------------------------------------------------------------------
[
let ifsName = lpte>>LPTE.ifsName
resultis lpte + (ifsName? ifsName, offset LPTE.ifsName/16)
]

//-----------------------------------------------------------------------------------------
and GetLpteTfsName(lpte) = valof
//-----------------------------------------------------------------------------------------
[
let tfsName = lpte>>LPTE.tfsName
resultis lpte + (tfsName? tfsName, offset LPTE.tfsName/16)
]

//-----------------------------------------------------------------------------------------
and GetLpteDIFRec(lpte) = valof
//-----------------------------------------------------------------------------------------
[
let difRec = lpte>>LPTE.difRec
resultis difRec ne 0? lpte+difRec, 0
]

// These routines set fields of an lpte

//-----------------------------------------------------------------------------------------
and SetLpteType(lpte, type) be
//-----------------------------------------------------------------------------------------
[
lpte>>LPTE.type = type
lpte>>LPTE.dirty = true
]

//-----------------------------------------------------------------------------------------
and SetLpteFlags(lpte, flags) be
//-----------------------------------------------------------------------------------------
[
lpte>>LPTE.flags = flags
lpte>>LPTE.dirty = true
]

//-----------------------------------------------------------------------------------------
and SetLpteLength(lpte, length) be
//-----------------------------------------------------------------------------------------
[
lpte>>LPTE.length = length
lpte>>LPTE.dirty = true
]

//-----------------------------------------------------------------------------------------
and SetLpteIfp(lpte, ifp) be
//-----------------------------------------------------------------------------------------
[
MoveBlock(lv lpte>>LPTE.ifp, ifp, lFP)
lpte>>LPTE.dirty = true
]

//-----------------------------------------------------------------------------------------
and SetLpteFa(lpte, fa) be
//-----------------------------------------------------------------------------------------
[
MoveBlock(lv lpte>>LPTE.fa, fa, lFA)
lpte>>LPTE.dirty = true
]

//-----------------------------------------------------------------------------------------
and SetLpteIfsName(lpte, ifsName) be
//-----------------------------------------------------------------------------------------
[
if (lpte>>LPTE.ifsName eq 0)? true, (ifsName>>String.length gr
 (lpte+lpte>>LPTE.ifsName)>>String.length) then
   [
   lpte>>LPTE.ifsName = lpte>>LPTE.length
   lpte>>LPTE.length = lpte>>LPTE.length + ifsName>>String.length rshift 1 +1
   if lpte>>LPTE.length gr maxLenLPTE then IFSError(ecSetLPTE)
   lpte>>LPTE.gotLonger = true
   ]
CopyString(lpte+lpte>>LPTE.ifsName, ifsName)
lpte>>LPTE.dirty = true
]

//-----------------------------------------------------------------------------------------
and SetLpteTfsName(lpte, tfsName) be
//-----------------------------------------------------------------------------------------
[
if (lpte>>LPTE.tfsName eq 0)? true, (tfsName>>String.length gr
 (lpte+lpte>>LPTE.tfsName)>>String.length) then
   [
   lpte>>LPTE.tfsName = lpte>>LPTE.length
   lpte>>LPTE.length = lpte>>LPTE.length + tfsName>>String.length rshift 1 +1
   if lpte>>LPTE.length gr maxLenLPTE then IFSError(ecSetLPTE)
   lpte>>LPTE.gotLonger = true
   ]
CopyString(lpte+lpte>>LPTE.tfsName, tfsName)
lpte>>LPTE.dirty = true
]

//-----------------------------------------------------------------------------------------
and SetLpteDIFRec(lpte, difRec) be
//-----------------------------------------------------------------------------------------
[
if lpte>>LPTE.difRec eq 0 then
   [
   lpte>>LPTE.difRec = lpte>>LPTE.length
   lpte>>LPTE.length = lpte>>LPTE.length + lenDIFRec
   if lpte>>LPTE.length gr maxLenLPTE then IFSError(ecSetLPTE)
   lpte>>LPTE.gotLonger = true
   ]
MoveBlock(lpte+lpte>>LPTE.difRec, difRec, lenDIFRec)
lpte>>LPTE.dirty = true
]