// IfsBackupRestore.bcpl -- restore files from backup
// Copyright Xerox Corporation 1979, 1980, 1981, 1982

// Last modified May 11, 1982  10:30 AM by Taft

get "Ifs.decl"
get "IfsFiles.decl"
get "IfsDirs.decl"
get "IfsBackup.decl"

// outgoing procedures
RestoreLoop; RestoreFile

// incoming procedures
CreateFD; LookupFD; NextFD; DestroyFD; InstallDR; LookupIFSFile
CreateDirectoryEntry; CreateIFSFile; CloseIFSFile; GetDiskFromFD
TransferLeaderPage; DIFRecFromDR; LockFile; LockDirFD; UnlockDirFD
ReadRecLE; DirCompareKey; StringCompare; PurgeVMem
CopyFile; DoubleUsc; IFSError; TelnetAborting
InstallSysParams; InitCachedDIF; InitGroupName
SysFree; FreePointer; MoveBlock; Zero
DefaultArgs; ReturnFrom; CallersFrame; PutTemplate; Ws; IFSPrintError

// incoming statics
dsp; infoVMD

let RestoreLoop(name, backupFS, fs, tree; numargs na) = valof
// Restores all files matching name from backupFS onto fs.
// If tree is provided, a file is restored only if it is present in the tree.
// Returns zero normally, an error code if the initial lookup of name failed.
DefaultArgs(lv na, -2, 0)
let ec = nil
let backupFD = LookupIFSFile(name, lcMultiple+lcVAll, lv ec, backupFS)
if backupFD eq 0 then resultis ec
until TelnetAborting() do
   ec = 0
   if tree ne 0 then
      let record = ReadRecLE(tree, backupFD)
      if record eq 0 % DirCompareKey(backupFD, record) ne 0 then ec = -1
      FreePointer(lv record)
   if ec eq 0 then ec = RestoreFile(backupFD, fs)
   PutTemplate(dsp, "*n  $S ", lv backupFD>>FD.dr>>DR.pathName)
   if ec ne 0 then
      Ws("-- not restored: ")
      switchon ec into
         case ecRestoreNoBackup:
            [ Ws("no-backup"); endcase ]
         case ecRestoreObsolete:
            [ Ws("already exists"); endcase ]
         case -1:
            [ Ws("deleted"); endcase ]
            [ Ws("*n   "); IFSPrintError(dsp, ec) ]
   unless NextFD(backupFD) break

resultis 0
and RestoreFile(backupFD, fs) = valof
// Restores the file described by backupFD onto the file system fs, returning
// zero if successful and an error code if unsuccessful.  Both directories must
// be unlocked at the time of the call and are unlocked upon return.
let buf = GetBufferForFD(backupFD)
let fd = 0

let ec = valof
   let ec = LookupFD(backupFD, lockRead)
   if ec ne 0 resultis ec
   TransferLeaderPage(backupFD, buf)
   if buf>>ILD.noBackup resultis ecRestoreNoBackup

   // Build FD for file in primary fs and see whether it exists
   fd = CreateFD(lv backupFD>>FD.dr>>DR.pathName, lcVExplicit+lcCreate, lv ec, fs)
   if fd eq 0 then IFSError(ecRestoreCreateFD, ec)
   InstallDR(fd, backupFD>>FD.dr)  // in case creating DIF
   ec = LookupFD(fd, lockWrite)
   unless ec eq 0 % ec eq ecDirNotFound do IFSError(ecRestoreLookupFD, ec)

   let isSystemInfo =
    StringCompare(lv fd>>FD.dr>>DR.pathName, "<System>Info!1") eq 0
   let dPages = buf>>ILD.hintLastPageFa.pageNumber+1
   test fd>>FD.lookupStatus eq lsExists
         [  // presently exists, compare write dates and check for no-backup
         let backupWrite = vec 1; MoveBlock(backupWrite, lv buf>>ILD.written, 2)
         let backupDamaged = buf>>ILD.damaged
         TransferLeaderPage(fd, buf)  // leader page of primary file
         if buf>>ILD.noBackup resultis ecRestoreNoBackup

         // Normally restore file only if the backup copy was written more
         // recently.  But in the case of <System>Info!1, restore if the
         // primary copy has never been backed up.  This is because when a
         // new file system is created, a <System>Info!1 is created from
         // whole cloth and has a more recent write date.
         // Also restore if the primary copy is damaged, the backup copy is not,
         // and the write dates are the same.
         unless isSystemInfo & buf>>ILD.backedUp.h eq 0 do
            switchon DoubleUsc(backupWrite, lv buf>>ILD.written) into
               case -1:  // backup copy has older write date than primary
                  resultis ecRestoreObsolete
               case 0:  // backup copy has same write date as primary
                  unless buf>>ILD.damaged & not backupDamaged do
                     resultis ecRestoreObsolete
               // case 1:  // backup copy has newer write date than primary

         dPages = dPages-(buf>>ILD.hintLastPageFa.pageNumber+1)
         if fd>>FD.dr>>DR.type eq drTypeDIF then
            [  // update information cached in DIF record
            let difRec = DIFRecFromDR(fd>>FD.dr)
            let backupDIFRec = DIFRecFromDR(backupFD>>FD.dr)
            MoveBlock(lv backupDIFRec>>DIFRec.diskPageUsage,
             lv difRec>>DIFRec.diskPageUsage, 2)
            MoveBlock(difRec, backupDIFRec, lenDIFRec)
            CreateDirectoryEntry(fd)  //special call to update entry
         [  //doesn't exist, create it
         if fd>>FD.dr>>DR.type eq drTypeDIF then
            Zero(lv (DIFRecFromDR(fd>>FD.dr)>>DIFRec.diskPageUsage), 2)
         ec = CreateIFSFile(fd, buf)
         if ec ne 0 resultis ec

   FreePointer(lv buf)
// RestoreFile (cont'd)

   // Write-lock the file (in primary FS) and restore it.
   // No need to lock backup file, because we are the only client of
   // the backup FS.
   unless LockFile(fd, modeWrite) resultis ecFileBusy

   // if about to restore <System>Info!1, flush in-core pages first.
   if isSystemInfo then PurgeVMem(infoVMD)

   CopyFile(GetDiskFromFD(fd), lv fd>>FD.dr>>DR.fp,
    GetDiskFromFD(backupFD), lv backupFD>>FD.dr>>DR.fp)
   CloseIFSFile(fd, dPages)
   fd = DestroyFD(fd)

   if isSystemInfo then
      // Just restored <System>Info -- make system aware of new contents.
      // The timing of this is a bit dicey, but is the best we can do without
      // tighter coupling between the backup system and VMem.
      [ InstallSysParams(true); InitCachedDIF(); InitGroupName() ]
   resultis 0

// Upon exit from the above block, both directories are locked if the
// restore failed, but neither is locked if it succeeded.
if ec ne 0 then
   if fd ne 0 then [ UnlockDirFD(fd); DestroyFD(fd) ]
FreePointer(lv buf)
resultis ec