// IfsBackupJob2.bcpl -- Main backup process, part 2
// Copyright Xerox Corporation 1979, 1980, 1981, 1982
// Last modified October 3, 1982  3:50 PM by Taft

get "Ifs.decl"
get "IfsSystemInfo.decl"
get "IfsFiles.decl"
get "Disks.d"
get "IfsBackup.decl"
get "IfsDirs.decl"

external
[
// outgoing procedures
BackupDriver

// incoming procedures
BackupIFSDir; CopyFile; OkToDoBackup
CreateFD; DestroyFD; LookupFD; NextFD; UnlockDirFD; LockFile; UnlockFile
GetDIFRec; DIFRecFromDR; CreateDirectoryEntry; TransferLeaderPage
CreateIFSFile; GetDiskFromFD; GetBufferForFD
EmptiestFreePages; InstallDR
OpenIFSStream; CloseIFSStream; KsBufferAddress; WriteBlock
MakeKPMTemplate; VFileWritePage
MoveBlock; Zero; SysFree; FreePointer; IFSError; MultEq; ReadCalendar
DoubleUsc; DoubleAdd; Dismiss; TruePredicate

// incoming statics
infoVMD
]

//---------------------------------------------------------------------------
structure DUR:  // Disk Usage Record -- accumulates correction for DIFRec.diskPageUsage
//---------------------------------------------------------------------------
[
difFD word		// -> FD for directory's DIF (0 if abandoned correction attempt)
diskPageUsage word 2	// accumulated actual disk usage
timeStart word 2	// time at which DIF was encountered
]
manifest lenDUR = size DUR/16

//---------------------------------------------------------------------------
let BackupDriver(fs, backupFS, WantBackup, arg, startingName) = valof
//---------------------------------------------------------------------------
// Issues calls to do the backup.  Returns code saying why it stopped.
// First, backs up IFS.Dir to capture state at start of backup,
//  then incrementally backs up all files in the file system,
//  then backs up IFS.Dir again (if possible) to capture ending state.
// startingName is the name of the file to start at.
[
let ec = nil
let fd = CreateFD(startingName, lcVExplicit, lv ec, fs)
if fd eq 0 then IFSError(ecBackupCreateFD, ec)
fd>>FD.template = MakeKPMTemplate("**")
UpdateBackupInfo(fd)

unless BackupIFSDir(fs, backupFS) do
   [ DestroyFD(fd); resultis backupDiskFull ]
FreePointer(lv fd>>FD.pathStk)
fd>>FD.version = fd>>FD.version-1
unless NextFD(fd) do
   [ DestroyFD(fd); resultis backupDone ]

let dur = vec lenDUR; Zero(dur, lenDUR)

ec = valof
   [
   for i = 1 to 10 do
      [
      if fd>>FD.dr>>DR.type eq drTypeDIF then RepairDiskUsage(dur, fd)
      let res = BackupFile(fd, backupFS, WantBackup, arg, dur)
      if res eq backupDiskFull resultis backupDiskFull

      unless res eq backupDone % res eq backupNotNeeded do
         // abandon accumulating diskPageUsage
         if dur>>DUR.difFD ne 0 then dur>>DUR.difFD = DestroyFD(dur>>DUR.difFD)

      unless NextFD(fd) do
         [
         RepairDiskUsage(dur, 0)
         resultis backupDone  //no more files
         ]
      ]

   UpdateBackupInfo(fd)  //record progress after every 10 files
   unless OkToDoBackup() resultis backupHalted
   ] repeat

if dur>>DUR.difFD ne 0 then DestroyFD(dur>>DUR.difFD)

UpdateBackupInfo(fd)
DestroyFD(fd)
unless ec eq backupDiskFull do
   unless BackupIFSDir(fs, backupFS) do ec = backupDiskFull
resultis ec
]

//---------------------------------------------------------------------------
and UpdateBackupInfo(fd) be
//---------------------------------------------------------------------------
[
let bi = VFileWritePage(infoVMD, biPage)
MoveBlock(lv bi>>BI.pathName, lv fd>>FD.dr>>DR.pathName, lenPathName)
]

//---------------------------------------------------------------------------
and RepairDiskUsage(dur, fd) be
//---------------------------------------------------------------------------
// Checks and corrects if necessary the disk page usage described by dur.  Then,
// if fd ne 0, assumes it is the DIF for a new directory and begins accumulating
// disk usage information for it.
// The way the disk usage correction works in the face of concurrent directory
// activity is as follows:
// 1. When a DIF is encountered, make a copy of the FD in the DUR, and record the
// current time.
// 2. For all files in the directory, accumulate the page count in the DUR.
// If a busy file or a file with write time ge starting time is encountered,
// abandon trying to correct the disk usage for this directory (by forgetting the FD).
// 3. When the next DIF is encountered, if the actual accumulated page count
// differs from the one in the remembered FD, AND the actual DIF page count has
// not changed in the meantime, then update the DIF from the accumulated page count.
[
let difFD = dur>>DUR.difFD
if difFD ne 0 then
   [
   unless MultEq(lv dur>>DUR.diskPageUsage,
    lv (DIFRecFromDR(difFD>>FD.dr))>>DIFRec.diskPageUsage) do
      [ // accumulated total is different from remembered value
      let oldUsage = vec 1
      MoveBlock(oldUsage, lv (DIFRecFromDR(difFD>>FD.dr))>>DIFRec.diskPageUsage, 2)
      if LookupFD(difFD, lockWrite) eq 0 &
       MultEq(oldUsage, lv (DIFRecFromDR(difFD>>FD.dr))>>DIFRec.diskPageUsage) then
         [ // remembered value hasn't changed, so fix it
         MoveBlock(lv (DIFRecFromDR(difFD>>FD.dr))>>DIFRec.diskPageUsage,
          lv dur>>DUR.diskPageUsage, 2)
         CreateDirectoryEntry(difFD)
         ]
      UnlockDirFD(difFD)
      ]
   DestroyFD(dur>>DUR.difFD)
   ]
Zero(dur, lenDUR)
if fd ne 0 then dur>>DUR.difFD = GetDIFRec(fd)
ReadCalendar(lv dur>>DUR.timeStart)
]

//---------------------------------------------------------------------------
and BackupFile(fd, backupFS, WantBackup, arg, dur) = valof
//---------------------------------------------------------------------------
// Checks to see whether the file described by FD needs to be backed up, by
// calling WantBackup(ild, arg) with ild pointing to the file's leader page.
// If so, backs up the file onto the backup file system backupFS
// and marks the file as having been backed up.
// Invalidates dur if a file with newer write date is encountered (see
// RepairDiskUsage in IfsBackupJob.bcpl).
// Should be called with the directory unlocked and returns with it unlocked.
// Returns one of the backup result codes (see IfsBackup.decl).
[
if LookupFD(fd) ne 0 resultis backupCantAccess

// If this is a DIF, restore the cached directory information to the file
if fd>>FD.dr>>DR.type eq drTypeDIF then
   [
   let stream = OpenIFSStream(fd, 0, modeReadWrite)
   if stream ne 0 then
      [
      // Know the first page of the file is in the buffer now
      let difRec = DIFRecFromDR(fd>>FD.dr)
      unless MultEq(difRec, KsBufferAddress(stream), lenDIFRec) do
         WriteBlock(stream, difRec, lenDIFRec)
      CloseIFSStream(stream)
      ]
   ]

// Read leader page and check dates
let buf = GetBufferForFD(fd)
let ec = valof
   [
   if LookupFD(fd, lockRead) ne 0 resultis backupCantAccess
   TransferLeaderPage(fd, buf)

   // accumulate disk pages used in this directory
   if dur ne 0 then
      [
      if DoubleUsc(lv buf>>ILD.written, lv dur>>DUR.timeStart) ge 0 then
         // abandon accumulating diskPageUsage
         if dur>>DUR.difFD ne 0 then dur>>DUR.difFD = DestroyFD(dur>>DUR.difFD)
      let pageCount = vec 1
      pageCount!0, pageCount!1 = 0, buf>>LD.hintLastPageFa.pageNumber+1
      DoubleAdd(lv dur>>DUR.diskPageUsage, pageCount)
      ]

   unless WantBackup(buf, arg) resultis backupNotNeeded

   // Make sure there is enough room in the backup file system.
   // This test is not right in the case of overwriting an existing
   // file, but it errs on the side of being too conservative.
   if EmptiestFreePages(backupFS) uls
     buf>>LD.hintLastPageFa.pageNumber+100 resultis backupDiskFull

   // Want to back up this file; attempt to lock it for reading.
   // If file is busy, wait up to 10 seconds for it to become free.
   for try = 1 to 10 do
      [
      if LockFile(fd, modeRead) resultis 0
      UnlockDirFD(fd)
      Dismiss(100)
      if LookupFD(fd, lockRead) ne 0 resultis backupCantAccess
      ]

   resultis backupFileBusy
   ]

// BackupFile (cont'd)

UnlockDirFD(fd)
if ec ne 0 then
   [ SysFree(buf); resultis ec ]

// Make FD for file in backup system
let backupFD = CreateFD(lv fd>>FD.dr>>DR.pathName, lcCreate+lcVExplicit,
 lv ec, backupFS)
if backupFD eq 0 then IFSError(ecBackupCreateFD, ec)

// Make backup DR be an exact copy of primary DR (in case DIF, etc.)
InstallDR(backupFD, fd>>FD.dr)

// See whether file already exists in backup system.
// If there is no such directory in the backup file system and we are not
// now trying to back up a DIF, then make a recursive call to back up the
// DIF first.
ec = LookupFD(backupFD, lockWrite)
if ec eq ecDirNotFound then
   [
   ec = 0
   if fd>>FD.dr>>DR.type ne drTypeDIF then
      [
      UnlockDirFD(backupFD)
      let difFD = GetDIFRec(fd)
      if difFD eq 0 then IFSError(ecCantFindDIF)
      let res = BackupFile(difFD, backupFS, TruePredicate, nil, 0)
      DestroyFD(difFD)
      if res ne backupDone then
         [
         UnlockFile(fd)
         DestroyFD(backupFD)
         SysFree(buf)
         resultis res
         ]
      ec = LookupFD(backupFD, lockWrite)
      ]
   ]

unless ec eq 0 do IFSError(ecBackupLookupFD, ec)

// BackupFile (cont'd)

test backupFD>>FD.lookupStatus eq lsExists
   ifso
      [  // file already exists.
      // If it is a DIF, ensure stuff in dir entry is up-to-date
      if fd>>FD.dr>>DR.type eq drTypeDIF then CreateDirectoryEntry(backupFD)
      ]
   ifnot
      [  // file doesn't exist, create it
      ec = CreateIFSFile(backupFD, buf)
      if ec ne 0 then IFSError(ecBackupCreateFile, ec)
      ]

SysFree(buf)  // don't hold on to buf across CopyFile call
UnlockDirFD(backupFD)

// Actually copy the file to the backup disk
CopyFile(GetDiskFromFD(backupFD), lv backupFD>>FD.dr>>DR.fp,
 GetDiskFromFD(fd), lv fd>>FD.dr>>DR.fp, SetBackupDate)

// Mark file as having been backed up
buf = GetBufferForFD(fd)
TransferLeaderPage(fd, buf)
SetBackupDate(buf)
TransferLeaderPage(fd, buf, true)
SysFree(buf)

// Clean up and leave
UnlockFile(fd)
DestroyFD(backupFD)
resultis backupDone
]

//---------------------------------------------------------------------------
and SetBackupDate(buf) be ReadCalendar(lv buf>>ILD.backedUp)
//---------------------------------------------------------------------------