// IfsLeafVMemOpen.bcpl - Leaf VMem Open - SWAPPABLE
// Copyright Xerox Corporation 1979, 1980, 1981

// Last modified December 4, 1981  12:05 PM by Taft

get ksTypeReadOnly, charItem from "Streams.d";
get ecAllocLeafVMem, ecBadPageSize, ecBadOldPageNumber
 from "IfsLeafErrors.decl";
get DSK, eofDA, fillInDA, lnPageSize from "Disks.d";
get FD, dr, modeRead, modeReadWrite, oldPageNumber from "IfsDirs.decl";
get "IfsLeaf.decl";
get "IfsLeafVMem.decl";
get "IfsIsf.d";

// This is gross, but it's absolutely impossible to get Tfs.d here...
// get DCreadnD from "Tfs.d";
manifest DCreadnD = 153011B;

external
[
//outgoing procedures
OpenLeafVMem; FindLVMD; FMapChecksum;

//incoming procedures
Allocate; CloseIFSFile; Closes; CreateDiskStream; DoubleUsc;
Enqueue; FileLength; Free; GetDiskFromFD; GetBufferForFD;
IFSError; InitFmap; InsertBefore; MultEq; MoveBlock;
Noop; OpenIFSFile; ReadCalendar; TransferLeaderPage; Zero;
ActOnDiskPages;

//incoming statics
leafVMI; sysZone;
]

//----------------------------------------------------------------------------
let OpenLeafVMem(fd, mode, lvEc) = valof
//----------------------------------------------------------------------------
[
let ild = GetBufferForFD(fd);
@lvEc = OpenIFSFile(fd, mode, ild);
if @lvEc ne 0 then [ Free(sysZone, ild); resultis 0; ]

let fp = lv fd>>FD.dr>>DR.fp; let disk = GetDiskFromFD(fd);
if disk>>DSK.lnPageSize - logVMPageLength ne logVPagesPerLeafPage then
   IFSError(ecBadPageSize);

// Verify the hintLastPageFA here.....
let page = ild>>ILD.hintLastPageFa.pageNumber;
let DAs = vec 3;
DAs!0 = fillInDA; DAs!1 = ild>>ILD.hintLastPageFa.da; DAs!2 = fillInDA;
let numChars = nil;
if page eq 0 %
 ActOnDiskPages(disk, nil, DAs-page+1, lv fd>>FD.dr>>DR.fp, page, page,
 DCreadnD, lv numChars, 0, nil, 0, 0, true) ne page %
 DAs!2 ne eofDA % numChars ne ild>>ILD.hintLastPageFa.charPos then
   [
   // hintLastPageFa is wrong; use DiskStreams to fix it.
   // We don't use IFSStreams because we want the file to stay open.
   let s = CreateDiskStream(fp, ksTypeReadOnly, charItem, 0, 0, 0, nil, disk);
   if s eq 0 then
      [
      CloseIFSFile(fd); Free(sysZone, ild);
      @lvEc = ecAllocLeafVMem; resultis 0;
      ]
   FileLength(s); Closes(s); 
   TransferLeaderPage(fd, ild);
   ]

if fd>>FD.oldPageNumber eq 0 then
   fd>>FD.oldPageNumber = ild>>ILD.hintLastPageFa.pageNumber;

let lvmd = 0; let lastpage = ild>>ILD.hintLastPageFa.pageNumber-1;
// Look for existing lvmd w/same da.
unless mode eq modeReadWrite do lvmd = FindLVMD(fp);
if lvmd eq 0 then 
   [
   lvmd = MakeLVMD(fp, ild, disk);
   // Assign a file index, and insert into ordered lvmd queue.
   let fileIndex = 0; let testLVMD = leafVMI>>LeafVMI.lvmdQueue.head;
   while testLVMD ne 0 & fileIndex eq testLVMD>>LVMD.fileIndex do
      [
      let onebit = signBit;
      for i = 1 to bitsPerWord do  // "add" one to fileIndex
         [
         fileIndex = fileIndex xor onebit;
         if (fileIndex & onebit) ne 0 then break;
         onebit = onebit rshift 1;
         ]
      testLVMD = @testLVMD;
      ]
   test testLVMD eq 0
      ifso Enqueue(lv leafVMI>>LeafVMI.lvmdQueue, lvmd);
      ifnot InsertBefore(lv leafVMI>>LeafVMI.lvmdQueue, testLVMD, lvmd);
   lvmd>>LVMD.fileIndex = fileIndex;
   lvmd>>LVMD.lastAddress.high = lastpage rshift logPagesPerWord;
   lvmd>>LVMD.lastAddress.low = lastpage lshift logBytesPerPage +
    ild>>ILD.hintLastPageFa.charPos;
   ]

ReadCalendar(lv ild>>ILD.read)
unless mode eq modeRead do
   [
   ReadCalendar(lv ild>>ILD.created); ReadCalendar(lv ild>>ILD.written);
   ild>>ILD.checksum = 0;  // changing data will invalidate software checksum
   ]
TransferLeaderPage(fd, ild, true); Free(sysZone, ild);
lvmd>>LVMD.refCount = lvmd>>LVMD.refCount + 1; resultis lvmd;
]

//----------------------------------------------------------------------------
and FindLVMD(fp) = valof
//----------------------------------------------------------------------------
[
manifest fpOffset = (offset LVMD.fmap + offset FM.fp)/16;

let unit = fp>>IFP.unit; let page = fp>>IFP.page;
let lvmd = leafVMI>>LeafVMI.lvmdQueue.head;
while lvmd ne 0 do
 test unit eq (lvmd+fpOffset)>>IFP.unit & page eq (lvmd+fpOffset)>>IFP.page
   ifso break;
   ifnot lvmd = @lvmd;
resultis lvmd;
]

//----------------------------------------------------------------------------
and MakeLVMD(fp, ild, disk) = valof
//----------------------------------------------------------------------------
[
// Try old page 0 filemap...remember,
//  "last" is relative to the start of the MAP itself.
let oldMap = lv ild>>LeafILD.fmap; let last = ild>>LeafILD.fmapLast;

// Compute the page number and DA of last file page referenced in the map.
// Compare these with the hintLastPageFA.
let lastRun = lv oldMap!(last-2); let lastPage = oldMap!last - 1;
let oldMapOK = oldMap!(last+1) eq fillInDA %
 lastPage eq ild>>ILD.hintLastPageFa.pageNumber &
 (lastRun!1 + (lastPage - lastRun!0)) eq ild>>ILD.hintLastPageFa.da

// Check the stored fp on the leader page, the fmap checksum,
// the validity of "last", the seal, and make sure that the
// fmap write date is later than the last write date of the file.
oldMapOK = oldMapOK & ild>>LeafILD.fmapChecksum eq FMapChecksum(oldMap,last) &
 (last ge lenMapEntry) & (last le maxLenOldFMap-lenMapEntry) &
 MultEq(fp, lv ild>>LeafILD.fmapFP, lFP) & ild>>LeafILD.fmapSeal eq version &
 DoubleUsc(lv ild>>LeafILD.fmapWritten, lv ild>>ILD.written) ge 0

let mapLength = oldMapOK? last+mapoffset+lenMapEntry, lenLeafFMap; 
let lvmd = Allocate(sysZone, lenLVMD + mapLength); Zero(lvmd, lenLVMD);
let fmap = lv lvmd>>LVMD.fmap;
// This doesn't fail unless lenLeafFMap is wrong!!
InitFmap(fmap, mapLength, fp, false, 0, sysZone, disk, leafFMapDelta)
if oldMapOK then
   [
   MoveBlock(lv fmap>>FM.map, oldMap, last+lenMapEntry);
   fmap>>FM.last = last+mapoffset;
   ]
resultis lvmd;
]

//----------------------------------------------------------------------------
and FMapChecksum(map, last) = valof
//----------------------------------------------------------------------------
[
let sum = 0;
for i = map to lv map!(last+1) do sum = sum + @i;
resultis sum;
]