// IfsIsf.bcpl -- implement indexed sequential files
// Copyright Xerox Corporation 1979, 1980

// last modified February 26, 1980  3:53 PM by Taft
// last modified October 1, 1979  6:13 PM by Wobber
// last modified by Butterfield, October 26, 1979  1:21 PM

   get "IfsIsf.d"

external   // entry procedures
[  IndexedPageIO   // (fmap, firstpage, core, npg, wflag[, lastnc]) -> lastnc
   MorePageIO   // (fmap, firstpage, scratch, npg, wflag)
   WriteFmap   // (fmap)
   FreeFmap   // (fmap)
   LookupFmap   // (fmap, page, [force])
]

external
[  // O.S.
   SetBlock; Zero
   CallSwat
   ActOnDiskPages
   WriteDiskPages
   DeleteDiskPages
   Allocate; Free
   Usc; Min; Max
   Dvec
   MoveBlock
]


//---------------------------------------------------------------------------
let IndexedPageIO(fmap, firstpage, core, npg, wflag, lastnc; numargs na)=valof
// wflag=-1 for write, 0 for read, 1 for read + extend
// returns numchars of last page transferred
//---------------------------------------------------------------------------
[
let action = (wflag ls 0? DCwriteD, DCreadD);
let pagesize = 1 lshift fmap>>FM.disk>>DSK.lnPageSize;
if na ls 6 then lastnc = pagesize*2;
let extrapage = (wflag eq 0 % lastnc ls pagesize*2)? 0, 1;
if Usc(firstpage+npg+extrapage, NextFmap(fmap)) gr 0 then
 MorePageIO(fmap, firstpage, core, npg+extrapage, wflag);
let CAs = vec ppc+1; let DAs = vec ppc+1;   // ppc+2 words!
CAs!0 = core - pagesize;
DAs!0 = (firstpage eq 0? eofDA, LookupFmap(fmap, firstpage-1));
DAs!1 = LookupFmap(fmap, firstpage);
let pagesleft = npg; let nextDA = nil;
let writeLast = wflag ls 0 & lastnc ls pagesize*2;
if writeLast then
   [
   nextDA = LookupFmap(fmap, firstpage+npg);
   if nextDA eq fillInDA then CallSwat("fillInDA nextDA");  //not read before!
   pagesleft = pagesleft - 1;
   ]
let page = firstpage; let nch = nil;
while pagesleft gr 0 do
   [
   let np = Min(pagesleft, ppc); for j = 1 to np do
    [ CAs!j = CAs!(j-1)+pagesize; DAs!(j+1) = LookupFmap(fmap, page+j); ]
   ActOnDiskPages(fmap>>FM.disk, CAs+1-page, DAs+1-page, lv fmap>>FM.fp,
    page, page+np-1, action, lv nch);
   CAs!0 = CAs!np; DAs!0 = DAs!np; DAs!1 = DAs!(np+1);
   page = page+np; pagesleft = pagesleft-np;
   ]
test writeLast
   ifnot if page eq NextFmap(fmap) then ExtendFmap(fmap, page, DAs!1);
   ifso
      [
      CAs!1 = CAs!0 + pagesize; DAs!2 = eofDA;
      WriteDiskPages(fmap>>FM.disk, CAs + 1 - page, DAs + 1 - page,
       lv fmap>>FM.fp, page, page, DCwriteD, lv nch, lastnc);
      ExtendFmap(fmap, page + 1, eofDA);  //shorten the map
      if nextDA ne eofDA then
         [
         DeleteDiskPages(fmap>>FM.disk, CAs!1,nextDA, lv fmap>>FM.fp, page+1);
         ActOnDiskPages(fmap>>FM.disk, CAs+1-page, DAs+1-page, lv fmap>>FM.fp,
          page, page, DCreadD, lv nch);  //read it back until we get DCZeroD
         ]
      ]
resultis nch
]

//---------------------------------------------------------------------------
and MorePageIO(fmap, firstpage, scratch, npg, wflag) be
//---------------------------------------------------------------------------
[
if Usc(firstpage + npg, NextFmap(fmap)) le 0 then return;
fmap>>FM.onern = firstpage; fmap>>FM.oneda = fillInDA  //in case map is full
let pagesize = 1 lshift fmap>>FM.disk>>DSK.lnPageSize
let zone = fmap>>FM.zone
if wflag eq -1 then test zone eq -1
   ifso [ scratch = pagesize; Dvec(MorePageIO, lv scratch) ]
   ifnot scratch = Allocate(zone, pagesize)
let lastpage = firstpage+npg-1; let lastmappage = NextFmap(fmap)
let nch = nil; let page = lastmappage-1; let DAs = vec ppc+2   // ppc+3 words!
if firstpage gr page then [ fmap>>FM.onern = firstpage; fmap>>FM.oneda = fillInDA ]
DAs!0 = (page eq 0? eofDA, LookupFmap(fmap, page-1)); DAs!1 = LookupFmap(fmap, page)
while page ls lastpage do
   [
   let np = Min(ppc, lastpage-page); let lastxpage = page + np;
   SetBlock(DAs+2, fillInDA, np+1); let pageDA = DAs+1-page;
   let npage = ActOnDiskPages(fmap>>FM.disk, 0, pageDA, lv fmap>>FM.fp,
    page, lastxpage, DCreadD, lv nch, DCreadD, scratch)
   for xpage = page+1 to npage+1 do ExtendFmap(fmap, xpage, pageDA!xpage);
   page = npage; DAs!0 = pageDA!(page-1); DAs!1 = pageDA!page
   if page eq lastxpage loop  //else extend the file
   unless (fmap>>FM.extend gr 0) & (wflag ne 0) do
    CallSwat("Attempt to access non-existent page")
   lastpage = Max(lastpage, page+fmap>>FM.extend)
   if nch ne 0 then
      [
      pageDA = DAs+1-page; DAs!2 = eofDA
      ActOnDiskPages(fmap>>FM.disk, 0, pageDA, lv fmap>>FM.fp,
       page, page, DCreadD, lv nch, DCreadD, scratch)
      DAs!2 = fillInDA; DAs!3 = eofDA
      WriteDiskPages(fmap>>FM.disk, 0, pageDA, lv fmap>>FM.fp,
       page, page+1, DCwriteD, 0, 0, scratch)
      page = page+1; ExtendFmap(fmap,page,DAs!2); DAs!0 = DAs!1; DAs!1 = DAs!2
      ]
   Zero(scratch, pagesize)
   while page ls lastpage do
      [
      pageDA = DAs+1-page; np = Min(ppc, lastpage-page)
      SetBlock(DAs+2, fillInDA, np); (DAs+2)!np = eofDA
      WriteDiskPages(fmap>>FM.disk, 0, pageDA, lv fmap>>FM.fp,
       page, page+np, DCwriteD, 0, 0, scratch)
      for xpage=page+1 to page+np+1 do ExtendFmap(fmap, xpage, pageDA!xpage);
      page = page+np; DAs!0 = pageDA!(page-1); DAs!1 = pageDA!page
      ]
   break
   ]
if NextFmap(fmap) gr lastmappage then WriteFmap(fmap)
if (wflag eq -1) & (zone ne -1) then Free(zone, scratch)
]

//---------------------------------------------------------------------------
and WriteFmap(fmap) be
//---------------------------------------------------------------------------
if fmap>>FM.rewrite then ActOnDiskPages(fmap>>FM.disk, 0, lv fmap>>FM.DA0,
 lv fmap>>FM.fp, 1, 1, DCwriteD, 0, DCwriteD, fmap)

//---------------------------------------------------------------------------
and LookupFmap(fmap, page, force; numargs n) = valof
//---------------------------------------------------------------------------
[
if (page eq fmap>>FM.onern) & (fmap>>FM.oneda ne fillInDA) &
 ((n ls 3) % (not force)) resultis fmap>>FM.oneda
let hi = fmap>>FM.last; let lo = mapoffset;
fmap = fmap>>FM.fmap;
switchon Usc(page, fmap!hi) into
 [ case 1: resultis fillInDA; case 0: resultis fmap!(hi+1); ]
while hi-lo gr 2 do
   [
   let mid = ((lo+hi) rshift 1) & -2
   test Usc(page, fmap!mid) ge 0
      ifso lo = mid
      ifnot hi = mid
   ]
let lp = lv (fmap!lo)
resultis (lp!1+page-@lp)
]

//---------------------------------------------------------------------------
and ExtendFmap(fmap, page, da) = valof
//---------------------------------------------------------------------------
[
if page eq fmap>>FM.onern then fmap>>FM.oneda = da;
let last = fmap>>FM.last; let lastp = lv fmap>>FM.fmap!last;
if da eq fillInDA then CallSwat("fillInDA ExtendFmap");
test da eq eofDA
   ifso
      [
      if Usc(page, @lastp) gr 0 % page eq 0 then resultis false;
      [ lastp = lastp - 2; ] repeatuntil Usc(page, @lastp) gr 0;
      lastp!2=page; lastp!3=eofDA; fmap>>FM.last = lastp + 2 - fmap>>FM.fmap;
      ]
   ifnot
      [
      if page ne @lastp then resultis false;
      test da eq (lastp!-1)+@lastp-(lastp!-2)
         ifso @lastp = @lastp+1;  // still in same chunk
         ifnot
            [
            if last eq fmap>>FM.end then test fmap>>FM.extendmap
               ifnot resultis false;  // out of space
               ifso
                  [
                  let maplength = fmap>>FM.end + lenMapEntry - mapoffset;
                  let delta = fmap>>FM.extendmap + 1 & -2;
                  let newMap = Allocate(fmap>>FM.zone, maplength + delta);
                  MoveBlock(newMap, lv (fmap>>FM.fmap)>>FM.map, maplength);
                  fmap>>FM.end = fmap>>FM.end + delta;
                  FreeFmap(fmap); fmap>>FM.fmap = newMap - mapoffset;
                  lastp = lv fmap>>FM.fmap!last;
                  ]
            lastp!1 = da; lastp!2 = @lastp+1; lastp!3 = fillInDA;
            fmap>>FM.last = last+2;
            ]
      ]
resultis true
]

//---------------------------------------------------------------------------
and NextFmap(fmap) = fmap>>FM.fmap!(fmap>>FM.last)
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
and FreeFmap(fmap) = valof
//---------------------------------------------------------------------------
[
if fmap>>FM.fmap ne fmap then Free(fmap>>FM.zone, fmap>>FM.fmap + mapoffset);
resultis fmap;
]