// BFSWrite.Bcpl -- Routines to extend files. 
// Copyright Xerox Corporation 1979, 1981
// Last modified October 27, 1981  3:53 PM by Boggs

get "AltoFileSys.d"	// Definitions for structures in the file system
get "Disks.d"		// Definitions for the disk object
get "Bfs.d"		// Definitions for the Diablo 31/44 disk

external
[
// outgoing procedures
BFSWritePages; BFSAssignDiskPage

// incoming procedures from BFSBase.bcpl
DefaultBfsErrorRtn

// incoming procedures from the OS
InitializeDiskCBZ; GetDiskCb; DoDiskCommand
ActOnDiskPages; AssignDiskPage
VirtualDiskDA; RealDiskDA
ReadDDPage; LockDD; UnlockDD
MoveBlock; Zero; SetBlock
DefaultArgs

// incoming statics From BfsMl.Asm
freePageFp; freePageFid; oneBits
]

//----------------------------------------------------------------------------
let BFSWritePages(disk, CAs, DAs, fp, firstPage, lastPage, lastAction,
  lvNumChars, lastNumChars, fixedCA, nil, errorRtn, nil, hintLastPage;
  numargs na) = valof
//----------------------------------------------------------------------------
// Note that DAs!(firstpage-1) will be referenced except when the
//  label of firstPage doesn't need to be rewritten.
// The arguments following lastPage are optional, as for ActOnDiskPages
[
let numChars, firstNewPage = nil, nil
DefaultArgs(lv na, 6, 0, 0, BFSwordsPerPage*2, 0, nil,
 lv DefaultBfsErrorRtn, nil, 0)
if lastAction eq 0 then lastAction = DCwriteD
if lvNumChars eq 0 then lvNumChars = lv numChars

if lastAction ne -1 then
   [
   // First proceed as for a read until there are no more
   //  preallocated pages to write into
   test DAs!firstPage eq fillInDA
      ifso firstNewPage = firstPage
      ifnot
         [
         firstPage = ActOnDiskPages(disk, CAs, DAs, fp, firstPage,
          lastPage, DCwriteD, lvNumChars, lastAction, fixedCA, 0, errorRtn,
          false, hintLastPage)
         if firstPage eq lastPage & (lastAction ne DCwriteD %
          @lvNumChars eq lastNumChars) resultis lastPage
         firstNewPage = firstPage +1
         ]

   // code to assign more pages
      [
      let sink = vec 256
      for i = firstNewPage to lastPage do
         [
         DAs!i = AssignDiskPage(disk, DAs!(i-1))
         if DAs!i eq -1 then
            [ (@errorRtn)(errorRtn, 0, ecDiskFull) ] repeat
         ]
      ActOnDiskPages(disk, 0, DAs, freePageFp, firstNewPage,
       lastPage, DCreadLD, 0, 0, sink, CheckFreePage, errorRtn)
      for i = firstNewPage to lastPage do
         [
         DAs!firstNewPage = DAs!i
         if DAs!i ne fillInDA then firstNewPage = firstNewPage +1
         ]
      ] repeatuntil firstNewPage gr lastPage
   ]

// BFSWritePages (cont'd)

// All the pages have been checked.  Write labels and data.
// The CB zone resues the same stack space as the sink vector.
@lvNumChars = lastNumChars
let cbz = vec CBzoneLength
InitializeDiskCBZ(disk, cbz, firstPage, CBzoneLength, Wretry, errorRtn)

Wretry:
   [
   for i = cbz>>CBZ.currentPage to lastPage do
      [
      let cb = GetDiskCb(disk, cbz)

      // Set up eofDA as the page after the end of the file
      if ((i eq lastPage & lastNumChars ne BFSwordsPerPage*2) %
       (DAs!(i+1) eq fillInDA)) then DAs!(i+1) = eofDA

      // Set up label to be written on this page
      RealDiskDA(disk, DAs!(i+1), lv cb>>CB.label.next)
      RealDiskDA(disk, DAs!(i-1), lv cb>>CB.label.previous)
      cb>>CB.label.numChars = i eq lastPage? lastNumChars, BFSwordsPerPage*2

      DoDiskCommand(disk, cb, (fixedCA ne 0? fixedCA, CAs!i),
       DAs!i, fp, i, DCwriteLD)
      ]
   while cbz>>CBZ.head ne 0 do GetDiskCb(disk, cbz)
   ]  //End of Wretry block

resultis lastPage
]

//----------------------------------------------------------------------------
and CheckFreePage(disk, cb, cbz) be
//----------------------------------------------------------------------------
[
let fid = lv cb>>CB.labelAddress>>DL.fileId
for i = 0 to lFID-1 do if fid!i ne freePageFid!i then  //oop! bit table lied
   (cbz>>CBZ.DAs)!(cb>>CB.truePageNumber) = fillInDA
]

//----------------------------------------------------------------------------
and BFSAssignDiskPage(disk, vda, nil; numargs na) = valof
//----------------------------------------------------------------------------
// Assigns in a sequential manner, in order of increasing
//  virtual disk address.  Second argument is the VDA previously
//  assigned; the code tries to assign pages sequentially in this case.
// However, for a new file the VDA passed is eofDA; in this case
//  the code resumes looking in the bit table where it last left off
//  trying to allocate a file.  The idea is to reduce bit table
//  scanning time and also page in/outs.
// Returns -1 if the bit table is full; else the VDA of the page assigned.
// Special three-argument for does not really do an assignment.
// Returns false if VDA+1 is assigned; true if it is available.
[
let ddMgr = disk>>BFSDSK.ddMgr
LockDD(ddMgr, disk)
vda = vda eq eofDA? disk>>BFSDSK.lastPageAlloc, vda+1

// The first lKDHeader words of DD aren't bit table bits
vda = vda + bitTableBias

// May have to do as many as n+2 iterations,
// where n is the number of bit table pages.
let diskBTsize = disk>>BFSDSK.diskBTsize
for i = 1 to (diskBTsize+lKDHeader) rshift BFSlnWordsPerPage +3 do
   [
   // At top of loop: vda = VDA to be examined next.
   if vda<<VDA.wordNum ge diskBTsize+lKDHeader then
      vda = bitTableBias // Wrap around
   let pa = vda<<VDA.pageNum
   let diskBTaddr = ReadDDPage(ddMgr, disk, pa+1)
   
   // Test the bit corresponding to "vda".  If it fails, test the remainder
   // of the word one bit at a time.
   let wa = vda<<VDA.wordNumInPage
   let bitMask = oneBits!(vda<<VDA.bitNum)
      [
      let free = ((diskBTaddr!wa) & bitMask) eq 0
      if na eq 3 then [ UnlockDD(ddMgr, disk); resultis free ]
      if free then
         [
         vda = vda - bitTableBias
         diskBTaddr!wa = (diskBTaddr!wa) % bitMask
         disk>>BFSDSK.freePages = disk>>BFSDSK.freePages-1
         disk>>BFSDSK.lastPageAlloc = vda
         UnlockDD(ddMgr, disk, true)  //mark bt page dirty
         resultis vda
         ]
      bitMask = bitMask rshift 1
      vda = vda+1
      ] repeatuntil bitMask eq 0
   
   // Now search the rest of the page one word at a time.
   // On the last page we may run past the end of real data, but that's ok
   // because on the next pass vda will be wrapped around to zero.
   wa = wa+1 repeatuntil wa ge BFSwordsPerPage % diskBTaddr!wa ne -1

   // wa now addresses a word containing other than -1 if one was found.
   // wa eq BFSwordsPerPage otherwise.
   vda = (pa lshift BFSlnWordsPerPage + wa) lshift 4
   ]

UnlockDD(ddMgr, disk)
resultis -1  // Bit table full
]