// IfsBackupJob1.bcpl -- Main backup process, part 1
// Copyright Xerox Corporation 1979, 1980, 1981, 1982, 1983
// Last modified May 17, 1983  4:40 PM by Taft

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

external
[
// outgoing procedures
BackupEvent; BackupJob; OkToDoBackup

// incoming procedures
BackupDriver; TotalFreePages
OpenIFS; CloseIFS; CreateIFS; DestroyCreateParams
LookupIFSFile; DeleteFileFromFD
CreateJob; DestroyJob; CreateEvent; QueueEvent; JobOK
VFileReadPage; VFileWritePage; LockCell; UnlockCell
DoubleAdd; DoubleSubtract; DoubleUsc; ExtractSubstring; IFSError
SysAllocateZero; SysFree; FreePointer; Zero; MoveBlock; ReadCalendar

// outgoing statics
backupRunning

// incoming statics
primaryIFS; infoVMD; haltFlag; system; driveTab
]

static [ backupRunning = false ]

structure BTimes:
[
before word 2	// back up file if previously backed up earlier than this
after word 2	// back up file if previously backed up later than this
]
manifest lenBTimes = size BTimes/16

//---------------------------------------------------------------------------
let BackupEvent(ecb) be
//---------------------------------------------------------------------------
// Periodic event that starts up the BackupJob if appropriate.
[
backupRunning = true
if OkToDoBackup() then
   if CreateJob(BackupJob, jobTypeBackup) ne 0 then
      [ SysFree(ecb); return ]
backupRunning = false
QueueEvent(ecb, 12000)  //2 minutes
]

//---------------------------------------------------------------------------
and OkToDoBackup() = valof
//---------------------------------------------------------------------------
[
unless JobOK(jobTypeBackup) resultis false
let bi = VFileReadPage(infoVMD, biPage)
unless bi>>BI.okToGo resultis false
LockCell(lv bi)
let now = vec 1
ReadCalendar(now)
let ok = valof
   [
   while DoubleUsc(now, lv bi>>BI.timeStop) ge 0 do
      [
      if bi>>BI.timeInterval.h eq 0 & bi>>BI.timeInterval.l eq 0 then
         resultis false  // else zero interval would cause us to hang in this loop
      AdvanceBackupTime(bi)
      ]
   resultis DoubleUsc(now, lv bi>>BI.timeStart) ge 0 &
    DoubleUsc(now, lv bi>>BI.timeStop) ls 0
   ]
UnlockCell(lv bi)
resultis ok
]

//---------------------------------------------------------------------------
and AdvanceBackupTime() be
//---------------------------------------------------------------------------
[
let bi = VFileWritePage(infoVMD, biPage)
DoubleAdd(lv bi>>BI.timeStart, lv bi>>BI.timeInterval)
DoubleAdd(lv bi>>BI.timeStop, lv bi>>BI.timeInterval)
]

//---------------------------------------------------------------------------
and BackupJob(ctx) be
//---------------------------------------------------------------------------
// This process is started up to perform backup according to the state
//  information in the <System>Info file.
// It runs either to completion or to some stopping point
//  (e.g., backup disk full, system halting, etc.),
//  updates the backup state information, and kills itself.
[
ctx>>RSCtx.userInfo = system
ctx>>RSCtx.type = jobTypeBackup
let bi = VFileReadPage(infoVMD, biPage)
LockCell(lv bi)
let ec = nil
let iBFSD = bi>>BI.iBFSD
   [  //loop to find a usable backup file system
   bi = VFileWritePage(infoVMD, biPage)
   let bfsd = lv bi>>BI.bfsd↑iBFSD
   if bfsd>>BFSD.state eq bfsdUsable % bfsd>>BFSD.state eq bfsdInUse then
      [  //found one, try to open it
      bfsd>>BFSD.state = bfsdUsed
      let backupFS = OpenIFS(lv bfsd>>BFSD.id, lv bfsd>>BFSD.errorCode)
      if backupFS eq 0 loop
      if backupFS>>IFS.type ne ifsTypeBackup then
         [ CloseIFS(backupFS); bfsd>>BFSD.errorCode = ecNotBackupFS; loop ]
      bfsd>>BFSD.state = bfsdInUse
      bi>>BI.iBFSD = iBFSD

      // refresh it if appropriate
      if bfsd>>BFSD.refresh then
         [
         bi = 0  //unlock BI page
         let cPar = BuildCParFromIFS(backupFS)
         CloseIFS(backupFS)
         backupFS = CreateIFS(cPar, lv ec)
         DestroyCreateParams(cPar)
         bi = VFileWritePage(infoVMD, biPage)
         bfsd = lv bi>>BI.bfsd↑iBFSD
         bfsd>>BFSD.errorCode = ec
         if backupFS eq 0 then [ bfsd>>BFSD.state = bfsdUsed; loop ]
         bfsd>>BFSD.refresh = false
         ]

      // Compute starting name and cutoff date.
      // If the previous backup was interrupted and we are still within the
      //  same backup time window, restart at the point of interruption.
      let startingName = ExtractSubstring((bi>>BI.inProgress &
       DoubleUsc(lv bi>>BI.timeLastStart, lv bi>>BI.timeStart) eq 0?
       lv bi>>BI.pathName, "<!>!1"))  // <!>!1 is less than any real filename
      MoveBlock(lv bi>>BI.timeLastStart, lv bi>>BI.timeStart, 2)
      bi>>BI.inProgress = true
      let bTimes = vec lenBTimes
      ReadCalendar(lv bTimes>>BTimes.before)
      DoubleSubtract(lv bTimes>>BTimes.before, lv bi>>BI.fullPeriod)
      // If no "after" cutoff has been specified, use the current time.
      // This forces files that appear to have been backed up in the future
      // to be backed up again.
      test bi>>BI.timeBackupAfter.h eq 0
         ifso ReadCalendar(lv bTimes>>BTimes.after)
         ifnot MoveBlock(lv bTimes>>BTimes.after, lv bi>>BI.timeBackupAfter, 2)

// BackupJob (cont'd)

      // back up files
      bi = 0  //unlock page
      ec = BackupDriver(primaryIFS, backupFS, WantBackup, bTimes,
       startingName)
      SysFree(startingName)

      // update state appropriately
      bi = VFileWritePage(infoVMD, biPage)
      bfsd = lv bi>>BI.bfsd↑iBFSD
      bfsd>>BFSD.errorCode = ec
      bfsd>>BFSD.state = ec eq backupDiskFull? bfsdUsed, bfsdUsable
      if ec eq backupDone then
         [
         bi>>BI.inProgress = false
         Zero(lv bi>>BI.timeBackupAfter, 2)
         AdvanceBackupTime()
         ]
      TotalFreePages(backupFS, lv bfsd>>BFSD.freePages)
      CloseIFS(backupFS)
      if ec eq backupHalted % not OkToDoBackup() break
      ]

   iBFSD = (iBFSD+1) rem numBFSD
   if iBFSD eq bi>>BI.iBFSD then [ bi>>BI.okToGo = false; break ]
   ] repeat

UnlockCell(lv bi)

// Queue an event that will restart this process when appropriate
CreateEvent(BackupEvent)
DestroyJob()
]

//---------------------------------------------------------------------------
and WantBackup(ild, bTimes) = valof
//---------------------------------------------------------------------------
// The WantBackup procedure passed to BackupDriver and thence to BackupFile.
// The conditions under which a file is backed up are as follows:
// 1. A file is never backed up if its ILD.noBackup bit is set.
// 2. Otherwise, a file is backed up if any of the following are true:
//   a. write date greater than last backup date;
//   b. last backup date less than bTimes.before;
//   c. last backup date greater than bTimes.after;
[
if ild>>ILD.noBackup resultis false
resultis DoubleUsc(lv ild>>LD.written, lv ild>>ILD.backedUp) ge 0 %
 DoubleUsc(lv ild>>ILD.backedUp, lv bTimes>>BTimes.before) ls 0 %
 DoubleUsc(lv ild>>ILD.backedUp, lv bTimes>>BTimes.after) ge 0
]

//---------------------------------------------------------------------------
and BuildCParFromIFS(ifs) = valof
//---------------------------------------------------------------------------
// Creates and returns a CPar structure usable for recreating a given
// IFS that is now open.
[
let cPar = SysAllocateZero(lenCPar)
cPar>>CPar.id = ExtractSubstring(ifs>>IFS.id)
cPar>>CPar.name = ExtractSubstring(ifs>>IFS.name)
cPar>>CPar.dirSize = 1000 * ifs>>IFS.numUnits
for i = 0 to ifs>>IFS.numUnits-1 do
   [
   // Record only file system 0 on T-300s
   let drive = ifs>>IFS.lpdt↑i>>DSK.driveNumber
   if ifs>>IFS.lpdt↑i eq driveTab>>DriveTab↑drive.disk↑0 then
      [
      cPar>>CPar.lpMap↑(cPar>>CPar.numUnits) = drive
      cPar>>CPar.numUnits = cPar>>CPar.numUnits+1
      ]
   ]
cPar>>CPar.type = ifs>>IFS.type
resultis cPar
]