// IfsBackupCopy.bcpl -- Copy file using TFS-level operations only
// Copyright Xerox Corporation 1979, 1981, 1982

// Last modified October 3, 1982  12:27 PM by Taft

get "AltoFileSys.d"
get "Disks.d"
get "Tfs.d"

// outgoing procedures

// incoming procedures
ActOnDiskPages; WriteDiskPages; DeleteDiskPages
DefaultArgs; Allocate; SysAllocate; SysFree; MultEq
Umin; SetBlock; MoveBlock; Noop

// incoming statics
numOvXMPages; numVMemBufs; sysZone

let CopyFile(dDisk, dFP, sDisk, sFP, LeaderProc, maxPage, dontCopyZero;
    numargs na) be
// Copies a file pointed to by sFP on sDisk to one pointed to by dFP
// on dDisk.  The file is exactly copied, including the leader page,
// except the last page hint in the destination file is set appropriately.
// If maxPage is supplied, it specifies the maximum page to be copied.
// If LeaderProc is supplied, LeaderProc(buffer) is called with the leader
// page in the buffer as it goes by.
// If dontCopyZero is true, the leader page is not copied (however, the
// fixing of the last page hint and the call to LeaderProc still occur).
// This procedure once worked on any kind of disk but now works only on
// Tridents due to its use of the special TFS DCreadnD action.
DefaultArgs(lv na, -4, Noop, #177777, false)

// Begin by allocating only one buffer, into which we read the leader page.
// Then, using the last page hint, allocate more buffers (up to a total of
// maxNBuffers) for doing the remainder of the file.  Thus we maximize
// the transfer rate for large files, but avoid doing the relatively
// expensive Allocates (which snarf pages from VMem) for small files.
let maxNBuffers = numOvXMPages ne 0? numVMemBufs/3, 5
let nBuffers = 1
let CAs = SysAllocate(3*maxNBuffers+7)
let sDAs = CAs+maxNBuffers+2
let dDAs = sDAs+maxNBuffers+2
let buf1 = SysAllocate(1 lshift sDisk>>DSK.lnPageSize)
CAs!1 = buf1
sDAs!1 = sFP>>FP.leaderVirtualDa
dDAs!0 = fillInDA
dDAs!1 = dFP>>FP.leaderVirtualDa
dDAs!2 = fillInDA

// Following triples of variables must be declared in the order of their
// appearance in an FA structure.
let hintLastDA, hintLastPage, hintLastNumChars = nil, 0, nil
let lastDA, lastPage, numChars = nil, nil, nil

let page = 0
// CopyFile (cont'd)

// loop to copy groups of pages
   // Read pages from source file.
   // On first iteration, read only the leader page.
   SetBlock(sDAs+2, fillInDA, nBuffers)
   lastPage = Umin(page+nBuffers-1, maxPage)
   lastPage = ActOnDiskPages(sDisk, CAs-page+1, sDAs-page+1, sFP,
    page, lastPage, DCreadD, lv numChars, 0, 0, 0, 0, 0, hintLastPage)
   sDAs!1 = sDAs!(lastPage-page+2)  //DA of lastPage+1
   if lastPage eq maxPage then sDAs!1 = eofDA  //simulate EOF if reach maxPage
   if page eq 0 then
      [  // Have leader page in buf1 now.
      MoveBlock(lv hintLastDA, lv buf1>>LD.hintLastPageFa, lFA)
      nBuffers = Umin((hintLastPage eq 0? 5, hintLastPage), maxNBuffers)
      for i = 2 to nBuffers do
         [  // Allocate more buffers, but stop if we exhaust storage.
         CAs!i = Allocate(sysZone, 1 lshift sDisk>>DSK.lnPageSize, true)
         if CAs!i eq 0 then [ nBuffers = i-1; break ]
      CAs!(nBuffers+1) = buf1  //for creating extra page

      // Guess what the last DA will be, and put that in the leader page.
      hintLastDA = dDAs!1 + hintLastPage
      buf1>>LD.hintLastPageFa.da = hintLastDA

   // The last page must be treated specially -- do not write it yet.
   if sDAs!1 eq eofDA then
      [ lastPage = lastPage-1; if lastPage uls page break ]

   // Write pages onto destination file.  In case writing a new file,
   // we must create one extra page to ensure that we will start
   // with an existing page on the next iteration.  The lastAction for
   // this page is DCreadnD to prevent its label from being rewritten
   // in the case of overwriting an existing file.  If the label were to be
   // rewritten, WriteDiskPages would set the next DA to eofDA and the
   // remainder of the file would be lost!  (In the case of a new file,
   // data from CAs!(lastPage+1) is initially written into the extra page.
   // There is an extra entry in CAs for this purpose).
   // Additional hairiness:  if this is the first iteration and we are not
   // to copy page zero, then we read it instead of writing.
   SetBlock(dDAs+3, fillInDA, nBuffers)
   (page eq 0 & dontCopyZero? ActOnDiskPages, WriteDiskPages)(dDisk,
    CAs-page+1, dDAs-page+1, dFP, page, lastPage+1, DCreadnD, 0, 0)

   // lastPage is the last page copied
   // lastPage+1 is the "extra" page
   // lastPage+2 is eofDA (unless overwriting an existing file)
   MoveBlock(dDAs, dDAs+lastPage-page+1, 3)  //DAs for lastPage to lastPage+2
   if sDAs!1 eq eofDA break
   page = lastPage+1
   ] repeat
// CopyFile (cont'd)

lastPage = lastPage+1

// lastPage-1 is the last page copied -- the next to last page of the file.
// lastPage is the last page of the file -- it has been read from the
// source file, assigned in the destination file, but not yet written out.
// page is the page number corresponding to CAs!1.
// dDAs!1 is the DA for lastPage, and dDAs!2 is the DA for lastPage+1 if it
// exists or eofDA otherwise.
let leftOverDA = dDAs!2  //save DA of lastPage+1 for possible truncation
dDAs!2 = eofDA

// A full last page is anomalous and would cause WriteDiskPages to
// do the wrong thing.  Force it to be less than full.
numChars = Umin(numChars, (2 lshift sDisk>>DSK.lnPageSize)-1)
WriteDiskPages(dDisk, CAs-page+1, dDAs-lastPage+1, dFP,
 lastPage, lastPage, 0, 0, numChars)

// truncate any remaining pages
if leftOverDA ne eofDA then
   DeleteDiskPages(dDisk, buf1, leftOverDA, dFP, lastPage+1)

// see whether our hint last FA guess was correct, and fix it if wrong.
lastDA = dDAs!1
unless MultEq(lv hintLastDA, lv lastDA, lFA) do
   dDAs!0 = fillInDA
   dDAs!1 = dFP>>FP.leaderVirtualDa
   dDAs!2 = fillInDA
   ActOnDiskPages(dDisk, CAs+1, dDAs+1, dFP, 0, 0, DCreadD)
   MoveBlock(lv buf1>>LD.hintLastPageFa, lv lastDA, lFA)
   ActOnDiskPages(dDisk, CAs+1, dDAs+1, dFP, 0, 0, DCwriteD)

for i = 1 to nBuffers do SysFree(CAs!i)