<> <> DIRECTORY AlpineEnvironment, AlpineInternal, AlpineLog, AlpTransaction, AlpDirectory, AlpInstance, BackupControl, BackupBTree, BackupLog, Basics, BasicTime, CedarProcess, CountedVM, File, FileLog, FS, IO, LogBasic, LogInline, LogRep, Process, RefText, RestartFile, Rope, TransactionMap, Worker; <<>> BackupControlImpl: CEDAR MONITOR IMPORTS AlpDirectory, AlpTransaction, AlpInstance, BackupBTree, BackupLog, BasicTime, CountedVM, CedarProcess, File, LogBasic, LogInline, Process, RefText, RestartFile, TransactionMap EXPORTS BackupControl SHARES FileLog = BEGIN BackupVolume: TYPE ~ AlpineInternal.BackupVolume; backupCycle: BackupControl.BackupCycle _ NIL; <> <<>> FileLogRecord: TYPE = FileLog.FileLogRecord; RecoverFiles: PUBLIC PROC [files: LIST OF Rope.ROPE, in, out: IO.STREAM] ~ { <> <> <<Body>> }; RecoverBackupDB: PUBLIC PROC [backupVolume: BackupVolume] ~ { <> <<Body>> }; RecoverServer: PUBLIC PROC [in, out: IO.STREAM] ~ { <> <<Body>> }; continueBackup: BOOL _ FALSE; backupRunning: BOOL _ FALSE; firstRecord: LogRecordID; StartBackup: PUBLIC ENTRY PROC [backupVolume: BackupVolume, newBTree: BOOLEAN _ FALSE] RETURNS [startedBackup : BOOL _ TRUE] ~ { IF backupRunning THEN RETURN[ startedBackup _ FALSE ]; continueBackup _ TRUE; backupRunning _ TRUE; TRUSTED {Process.Detach[FORK BackupProcess[backupVolume, newBTree]] }; }; StopBackup: PUBLIC ENTRY PROC [] ~ { continueBackup _ FALSE; }; TransactionHandle: TYPE = AlpTransaction.Handle; MapHandle: TYPE = TransactionMap.Handle; LogRecordID: TYPE = AlpineLog.RecordID; BULogRecordID: TYPE = BackupLog.RecordID; UniversalFile: TYPE = AlpineEnvironment.UniversalFile; BackupProcess: PROC [backupVolume: BackupVolume, newBTree: BOOLEAN] ~ { currentRecord: LogRecordID; backupRecord: BULogRecordID; trans: TransactionHandle; mapTrans: MapHandle; btreeFile: UniversalFile; completedTranses: LIST OF MapHandle _ NIL; <> CedarProcess.SetPriority[background]; trans _ AlpTransaction.Create[AlpInstance.Create["sea-wolf.alpine", NIL, ALL[0]]]; mapTrans _ TransactionMap.GetHandle[trans.transID]; btreeFile _ AlpDirectory.Lookup["/sea-wolf.alpine/chauser.pa/backup.btree"].file; BackupBTree.OpenForNormalOperation[trans, btreeFile, newBTree]; [firstRecord, backupRecord] _ BackupBTree.GetPositionInfo[]; -- what if it can't be read? IF firstRecord = AlpineLog.nullRecordID THEN TRUSTED {firstRecord _ RestartFile.ReadRestartRecord[].recordIDForCheckpointCompleteRecord }; currentRecord _ firstRecord; IF newBTree THEN { <> backupRecord _ BackupLog.Format[backupVolume, 200, currentRecord]; }; BackupLog.OpenForNormalOperation[backupVolume, backupRecord]; TRUSTED { [] _ LogBasic.OpenRecordStreamFromCheckpoint[checkpointWord: LogBasic.WordNumberFromRecordID[currentRecord], checkpointRecord: currentRecord, firstRecord: currentRecord];}; WHILE continueBackup DO endOfLog, truncatedRecord: BOOLEAN; nextRecord: LogRecordID; TRUSTED {[endOfLog: endOfLog, truncatedRecord: truncatedRecord, currentRecord: nextRecord] _ LogBasic.AdvanceRecordStream[]}; IF NOT endOfLog THEN { currentRecord _ nextRecord; [backupRecord, completedTranses] _ ProcessLogRecord[currentRecord, backupRecord, btreeFile, mapTrans, completedTranses]; -- copy to backupLog, expanding if necessary; what about volume changes? <> <> <> <> } ELSE { <> TRUSTED {LogBasic.CloseRecordStream[]}; BackupBTree.SetPositionInfo[currentRecord, backupRecord]; BackupBTree.SetFullyConsistent[backupRecord, BasicTime.Now[]]; BackupLog.Force[]; BackupBTree.Commit[]; FOR doneTranses: LIST OF MapHandle _ completedTranses, doneTranses.rest WHILE doneTranses # NIL DO <> IF doneTranses.first # NIL THEN TransactionMap.AssertBackupFinished[doneTranses.first] ENDLOOP; completedTranses _ NIL; mapTrans _ TransactionMap.GetHandle[trans.transID]; Process.Pause[Process.SecondsToTicks[30--*60--]]; TRUSTED { [] _ LogBasic.OpenRecordStreamFromCheckpoint[checkpointWord: LogBasic.WordNumberFromRecordID[currentRecord], checkpointRecord: currentRecord, firstRecord: currentRecord];}; }; ENDLOOP; BackupLog.Close[]; BackupBTree.Commit[]; [] _ AlpTransaction.Finish[trans, commit]; backupRunning _ FALSE; }; nBufferPages: CARDINAL = 8; WordsPerPage: CARDINAL = AlpineEnvironment.wordsPerPage; nBufferWords: CARDINAL = nBufferPages*WordsPerPage; bufferHandle: CountedVM.Handle _ CountedVM.Allocate[nBufferWords]; Block: TYPE = AlpineLog.Block; PageNumber: TYPE = AlpineEnvironment.PageNumber; RecordID: TYPE = LogRecordID; bufferBlock: Block _ [base: bufferHandle.pointer, length: 0, rest: NIL]; CopyLogPages: PROC [recordID: LogRecordID, wordsToSkip: CARDINAL, nPages: CARDINAL] RETURNS [followingRecord: RecordID] ~ { nIt: CARDINAL = nPages / nBufferPages; nLeft: CARDINAL = nPages MOD nBufferPages; bufferBlock.length _ nBufferWords; FOR i: CARDINAL _ 0, i+1 WHILE i < nIt DO TRUSTED {[] _ LogBasic.Get[ thisRecord: recordID, to: [base: NIL, length: wordsToSkip+i*WordsPerPage, rest: @bufferBlock]];}; TRUSTED{ [] _ BackupLog.Write[ recordData: bufferBlock, continuation: TRUE ]; }; ENDLOOP; bufferBlock.length _ nLeft*WordsPerPage; TRUSTED {[] _ LogBasic.Get[ thisRecord: recordID, to: [base: NIL, length: wordsToSkip+nIt*WordsPerPage, rest: @bufferBlock]];}; TRUSTED{ followingRecord _ BackupLog.Write[ recordData: bufferBlock, continuation: TRUE ]; }; }; CopyFilePages: PROC [universalFile: UniversalFile, firstPage: PageNumber, nPages: PageNumber] RETURNS [followingRecord: RecordID] ~ { nIt: CARDINAL = nPages / nBufferPages; nLeft: CARDINAL = nPages MOD nBufferPages; fileHandle: File.Handle; bufferBlock.length _ nBufferWords; <> fileHandle _ File.Open[ volume: File.FindVolumeFromID[universalFile.volumeID], fp: universalFile.fileID]; FOR i: CARDINAL _ 0, i+1 WHILE i < nIt DO TRUSTED {fileHandle.Read[ from: [firstPage+i*nBufferPages], nPages: nBufferPages, to: bufferHandle.pointer];}; TRUSTED{ [] _ BackupLog.Write[ recordData: bufferBlock, continuation: TRUE ] }; ENDLOOP; TRUSTED {fileHandle.Read[ from: [firstPage+nIt*nBufferPages], nPages: nLeft, to: bufferHandle.pointer];}; bufferBlock.length _ nLeft*WordsPerPage; TRUSTED{ followingRecord _ BackupLog.Write[ recordData: bufferBlock, continuation: TRUE ]; }; }; TransHeader: TYPE = LogRep.TransactionHeader; BUHeader: TYPE = MACHINE DEPENDENT RECORD [ seal (0): BULogRecordID, prevInFile (3): BULogRecordID, transHeader (6): TransHeader ]; ProcessLogRecord: PROC [currentRecord: LogRecordID, backupRecord: BULogRecordID, btreeFile: UniversalFile, backupTrans: MapHandle, completedTranses: LIST OF MapHandle] RETURNS[ nextBackupRecord: BULogRecordID, newCompletedTranses: LIST OF MapHandle] ~ { currentRecordType: AlpineLog.RecordType _ PeekType[currentRecord]; baseRecord, lastIncrementRecord: BULogRecordID; name: Rope.ROPE _ ""; RecordTypeHeader: TYPE = LogRep.RecordTypeHeader; rest: AlpineLog.Block; buHd: BUHeader; trans: MapHandle; UpdateDirectory: PROC [fileID: UniversalFile] ~ { found: BOOLEAN; [found: found, baseRecord: baseRecord, lastIncrementRecord: lastIncrementRecord, name: name] _ BackupBTree.GetFileInfo[fileID]; IF NOT found THEN { IF currentRecordType # create THEN { <> <> <> <> <> baseRecord _ BackupLog.nullRecordID; name _ ""; } ELSE baseRecord _ backupRecord; }; IF fileID # btreeFile THEN BackupBTree.SetFileInfo[universalFile: fileID, baseRecord: baseRecord, lastIncrementRecord: backupRecord, name: name]; buHd.prevInFile _ lastIncrementRecord; }; buHd.seal _ backupRecord; nextBackupRecord _ backupRecord; newCompletedTranses _ completedTranses; SELECT currentRecordType FROM writePages => { record: FileLogRecord[writePages]; <> <> <> <<>> TRUSTED { rest _ [base: @record, length: SIZE[FileLogRecord[writePages]]]; [] _ LogBasic.Get[thisRecord: currentRecord, to: [base: @buHd.transHeader, length: SIZE[TransHeader], rest: @rest]] }; trans _ TransactionMap.GetHandle[buHd.transHeader.transID]; IF (trans = backupTrans) OR ((trans#NIL) AND TransactionMap.GetOutcome[trans] = commit) THEN { UpdateDirectory[ [record.volumeID, record.fileID] ]; <> TRUSTED{ [] _ BackupLog.Write[ recordData: [base: @buHd, length: SIZE[BUHeader], rest: @rest]]; }; nextBackupRecord _ CopyLogPages[ recordID: currentRecord, wordsToSkip: SIZE[FileLogRecord[writePages]] + SIZE[TransHeader], nPages: record.pageRun.count ]; }; }; writeLeaderPage => { record: FileLogRecord[writeLeaderPage]; TRUSTED { rest _ [base: @record, length: SIZE[FileLogRecord[writeLeaderPage]]]; [] _ LogBasic.Get[thisRecord: currentRecord, to: [base: @buHd.transHeader, length: SIZE[TransHeader], rest: @rest]] }; trans _ TransactionMap.GetHandle[buHd.transHeader.transID]; IF (trans = backupTrans) OR ((trans#NIL) AND TransactionMap.GetOutcome[trans] = commit) THEN { UpdateDirectory[ [record.volumeID, record.fileID] ]; <> TRUSTED{ [] _ BackupLog.Write[ recordData: [base: @buHd, length: SIZE[BUHeader], rest: @rest]]; }; nextBackupRecord _ CopyLogPages[ recordID: currentRecord, wordsToSkip: SIZE[FileLogRecord[writeLeaderPage]] + SIZE[TransHeader], nPages: record.pageCount ]; }; }; setSize => { record: FileLogRecord[setSize]; TRUSTED { rest _ [base: @record, length: SIZE[FileLogRecord[setSize]]]; [] _ LogBasic.Get[thisRecord: currentRecord, to: [base: @buHd.transHeader, length: SIZE[TransHeader], rest: @rest]] }; trans _ TransactionMap.GetHandle[buHd.transHeader.transID]; IF (trans = backupTrans) OR ((trans#NIL) AND TransactionMap.GetOutcome[trans] = commit) THEN { UpdateDirectory[ [record.volumeID, record.fileID] ]; <> TRUSTED{ nextBackupRecord _ BackupLog.Write[ recordData: [base: @buHd, length: SIZE[BUHeader], rest: @rest]]; }; }; }; create => { record: FileLogRecord[create]; textOwner: REF TEXT; textBlock: AlpineLog.Block; TRUSTED { rest _ [base: @record, length: SIZE[FileLogRecord[create]]]; [] _ LogBasic.Get[thisRecord: currentRecord, to: [base: @buHd.transHeader, length: SIZE[TransHeader], rest: @rest]]; textOwner _ RefText.ObtainScratch[record.owner.length]; textBlock _ [ base: BASE[DESCRIPTOR[textOwner.text]], length: (record.owner.length+Basics.bytesPerWord-1)/Basics.bytesPerWord]; [] _ LogBasic.Get[thisRecord: currentRecord, to: [base: NIL, length: SIZE[TransHeader]+SIZE[FileLogRecord[create]], rest: @textBlock]] }; trans _ TransactionMap.GetHandle[buHd.transHeader.transID]; IF (trans = backupTrans) OR ((trans#NIL) AND TransactionMap.GetOutcome[trans] = commit) THEN { UpdateDirectory[ [record.volumeID, record.fileID] ]; <> TRUSTED{ [] _ BackupLog.Write[ recordData: [base: @buHd, length: SIZE[BUHeader], rest: @rest]]; nextBackupRecord _ BackupLog.Write[ recordData: textBlock ]; }; }; RefText.ReleaseScratch[textOwner]; }; delete => { record: FileLogRecord[delete]; TRUSTED { rest _ [base: @record, length: SIZE[FileLogRecord[delete]]]; [] _ LogBasic.Get[thisRecord: currentRecord, to: [base: @buHd.transHeader, length: SIZE[TransHeader], rest: @rest]] }; trans _ TransactionMap.GetHandle[buHd.transHeader.transID]; IF (trans = backupTrans) OR ((trans#NIL) AND TransactionMap.GetOutcome[trans] = commit) THEN { UpdateDirectory[ [record.volumeID, record.fileID] ]; <> TRUSTED{ nextBackupRecord _ BackupLog.Write[ recordData: [base: @buHd, length: SIZE[BUHeader], rest: @rest]]; }; }; }; writePagesToBase => { record: FileLogRecord[writePagesToBase]; TRUSTED { rest _ [base: @record, length: SIZE[FileLogRecord[writePagesToBase]]]; [] _ LogBasic.Get[thisRecord: currentRecord, to: [base: @buHd.transHeader, length: SIZE[TransHeader], rest: @rest]] }; trans _ TransactionMap.GetHandle[buHd.transHeader.transID]; IF (trans = backupTrans) OR ((trans#NIL) AND TransactionMap.GetOutcome[trans] = commit) THEN { UpdateDirectory[ [record.volumeID, record.fileID] ]; <> TRUSTED{ [] _ BackupLog.Write[ recordData: [base: @buHd, length: SIZE[BUHeader], rest: @rest]]; }; nextBackupRecord _ CopyFilePages[ universalFile: [record.volumeID, record.fileID], firstPage: record.pageRun.firstPage, nPages: record.pageRun.count ]; }; }; workerCompleting => { record: Worker.CompletingLogRep; buHd.prevInFile _ BackupLog.nullRecordID; TRUSTED { rest _ [base: @record, length: SIZE[Worker.CompletingLogRep]]; [] _ LogBasic.Get[thisRecord: currentRecord, to: [base: @buHd.transHeader, length: SIZE[TransHeader], rest: @rest]] }; <> TRUSTED{ nextBackupRecord _ BackupLog.Write[ recordData: [base: @buHd, length: SIZE[BUHeader], rest: @rest]]; }; newCompletedTranses _ CONS[TransactionMap.GetHandle[buHd.transHeader.transID], newCompletedTranses]; }; ENDCASE => NULL; }; PeekType: PROC [thisRecord: LogRecordID] RETURNS [recordType: AlpineLog.RecordType] = TRUSTED { recordTypeHeader: LogRep.RecordTypeHeader; status: AlpineLog.ReadProcStatus; TRUSTED {[status: status] _ LogBasic.GetCurrentRecord[currentRecord: thisRecord, to: [base: @recordTypeHeader, length: LogRep.RecordTypeHeader.SIZE]]}; IF status = sourceExhausted THEN ERROR ; RETURN [recordTypeHeader.type]; }; ScanBackupLog: PROC [backupVolume: BackupVolume] RETURNS [] ~ TRUSTED { currentRecord: RecordID; header: BUHeader; headerBlock: Block _ [base: @header, length: BUHeader.SIZE]; status: BackupLog.ReadProcStatus; currentRecord _ BackupLog.OpenForRecovery[backupVolume]; DO [status: status] _ BackupLog.Read[currentRecord, 0, headerBlock]; IF status # normal THEN EXIT; SELECT header.transHeader.type FROM writePages => { record: FileLogRecord[writePages]; [] _ BackupLog.Read[currentRecord, SIZE[BUHeader], [base: @record, length: SIZE[FileLogRecord[writePages]]]]; currentRecord _ LogInline.AddLC[currentRecord, SIZE[BUHeader]+SIZE[FileLogRecord[writePages]]+WordsPerPage*record.pageRun.count]; }; writeLeaderPage => { record: FileLogRecord[writeLeaderPage]; [] _ BackupLog.Read[currentRecord, SIZE[BUHeader], [base: @record, length: SIZE[FileLogRecord[writeLeaderPage]]]]; currentRecord _ LogInline.AddLC[currentRecord, SIZE[BUHeader]+SIZE[FileLogRecord[writeLeaderPage]]+WordsPerPage*record.pageCount]; }; setSize => { record: FileLogRecord[setSize]; [] _ BackupLog.Read[currentRecord, SIZE[BUHeader], [base: @record, length: SIZE[FileLogRecord[setSize]]]]; currentRecord _ LogInline.AddLC[currentRecord, SIZE[BUHeader]+SIZE[FileLogRecord[setSize]]]; }; create => { record: FileLogRecord[create]; [] _ BackupLog.Read[currentRecord, SIZE[BUHeader], [base: @record, length: SIZE[FileLogRecord[create]]]]; currentRecord _ LogInline.AddLC[currentRecord, SIZE[BUHeader]+SIZE[FileLogRecord[create]]+(record.owner.length+Basics.bytesPerWord-1)/Basics.bytesPerWord]; }; delete => { record: FileLogRecord[delete]; [] _ BackupLog.Read[currentRecord, SIZE[BUHeader], [base: @record, length: SIZE[FileLogRecord[delete]]]]; currentRecord _ LogInline.AddLC[currentRecord, SIZE[BUHeader]+SIZE[FileLogRecord[delete]]]; }; writePagesToBase => { record: FileLogRecord[writePagesToBase]; [] _ BackupLog.Read[currentRecord, SIZE[BUHeader], [base: @record, length: SIZE[FileLogRecord[writePagesToBase]]]]; currentRecord _ LogInline.AddLC[currentRecord, SIZE[BUHeader]+SIZE[FileLogRecord[writePagesToBase]]+WordsPerPage*record.pageRun.count]; }; workerCompleting => { record: Worker.CompletingLogRep; [] _ BackupLog.Read[currentRecord, SIZE[BUHeader], [base: @record, length: SIZE[Worker.CompletingLogRep]]]; currentRecord _ LogInline.AddLC[currentRecord, SIZE[BUHeader]+SIZE[Worker.CompletingLogRep]]; }; ENDCASE => NULL; ENDLOOP; BackupLog.Close[]; }; <> <