FileLog.
LogCreate:
PUBLIC
PROCEDURE [fileInstance: FileInstance.Handle, initialSize: PageCount, owner: AlpineEnvironment.OwnerName] =
BEGIN
textOwner: Rope.Text = Rope.InlineFlatten[owner];
length: CARDINAL = textOwner.length;
textBlock: Log.Block ← [ base: BASE[DESCRIPTOR[textOwner.text]], length: (length+Basics.bytesPerWord-1)/Basics.bytesPerWord];
record: FileLogRecord[create] ← [ volumeID: NULL, fileID: NULL, specifics: create [initialSize: initialSize, owner: [length: length, text: NULL]]];
[volumeID: record.volumeID, fileID: record.fileID] ← FileInstance.GetVolumeIDAndFileID[fileInstance];
[] ← Log.Write[trans: FileInstance.GetTransHandle[fileInstance], recordType: create, recordData: [base: @record, length: SIZE[FileLogRecord[create]], rest: @textBlock], force: TRUE];
END;
LogDelete:
PUBLIC
PROCEDURE [fileInstance: FileInstance.Handle] =
BEGIN
record: FileLogRecord[delete] ← [volumeID: NULL, fileID: NULL, specifics: delete []];
[volumeID: record.volumeID, fileID: record.fileID] ← FileInstance.GetVolumeIDAndFileID[fileInstance];
[] ← Log.Write[trans: FileInstance.GetTransHandle[fileInstance], recordType: delete, recordData: [base: @record, length: SIZE[FileLogRecord[delete]]]];
END;
LogSetSize:
PUBLIC
PROCEDURE [fileInstance: FileInstance.Handle, old, new: PageCount] =
BEGIN
record: FileLogRecord[setSize] ← [volumeID: NULL, fileID: NULL, specifics: setSize [old: old, new: new]];
[volumeID: record.volumeID, fileID: record.fileID] ← FileInstance.GetVolumeIDAndFileID[fileInstance];
[] ← Log.Write[trans: FileInstance.GetTransHandle[fileInstance], recordType: setSize, recordData: [base: @record, length: SIZE[FileLogRecord[setSize]]]];
END;
LogWritePages:
PUBLIC
PROCEDURE [fileInstance: FileInstance.Handle, where:
LONG
POINTER, pageRun: PageRun, referencePattern: ReferencePattern]
RETURNS [recordID: LogRecordID] =
BEGIN
dataBlock: Log.Block ← [base: where, length: pageRun.count*AlpineEnvironment.wordsPerPage];
record: FileLogRecord[writePages] ← [volumeID: NULL, fileID: NULL, specifics: writePages [pageRun: pageRun, referencePattern: referencePattern, data: NULL]];
[volumeID: record.volumeID, fileID: record.fileID] ← FileInstance.GetVolumeIDAndFileID[fileInstance];
[thisRecord: recordID] ← Log.Write[trans: FileInstance.GetTransHandle[fileInstance], recordType: writePages, recordData: [base: @record, length: SIZE[FileLogRecord[writePages]], rest: @dataBlock]];
END;
LogReadPages:
PUBLIC
PROCEDURE [fileInstance: FileInstance.Handle, where:
LONG
POINTER, pageRun: PageRun, recordID: LogRecordID]
RETURNS [referencePattern: ReferencePattern] =
BEGIN
header: FileLogRecord[writePages];
status: Log.ReadProcStatus;
[status: status] ← Log.Read[thisRecord: recordID, to: [base: @header, length: SIZE[FileLogRecord[writePages]]]];
IF status=sourceExhausted THEN ERROR; -- wrong length log record
IF FileInstance.GetFileID[fileInstance] # header.fileID THEN ERROR; -- wrong file
IF pageRun.firstPage<header.pageRun.firstPage
OR pageRun.firstPage+pageRun.count>header.pageRun.firstPage+header.pageRun.count
THEN ERROR; -- pageRun is not a subinterval of the one contained in the log record
[status: status] ← Log.Read[thisRecord: recordID, wordsToSkip: SIZE[FileLogRecord[writePages]] + CARDINAL[pageRun.firstPage-header.pageRun.firstPage] * AlpineEnvironment.wordsPerPage, to: [base: where, length: pageRun.count * AlpineEnvironment.wordsPerPage]];
IF status=sourceExhausted THEN ERROR; -- log record not as long as it says it is
RETURN [header.referencePattern];
END;
LogWriteLeaderPage:
PUBLIC
PROCEDURE [fileInstance: FileInstance.Handle, where:
LONG
POINTER]
RETURNS [recordID: LogRecordID] =
BEGIN
dataBlock: Log.Block ← [base: where, length: AlpineEnvironment.wordsPerPage];
record: FileLogRecord[writeLeaderPage] ← [volumeID: NULL, fileID: NULL, specifics: writeLeaderPage [data: NULL]];
[volumeID: record.volumeID, fileID: record.fileID] ← FileInstance.GetVolumeIDAndFileID[fileInstance];
[thisRecord: recordID] ← Log.Write[trans: FileInstance.GetTransHandle[fileInstance], recordType: writeLeaderPage, recordData: [base: @record, length: SIZE[FileLogRecord[writeLeaderPage]], rest: @dataBlock]];
END;
LogReadLeaderPage:
PUBLIC
PROCEDURE [fileInstance: FileInstance.Handle, where:
LONG
POINTER, recordID: LogRecordID] =
BEGIN
header: FileLogRecord[writeLeaderPage];
status: Log.ReadProcStatus;
[status: status] ← Log.Read[thisRecord: recordID, to: [base: @header, length: SIZE[FileLogRecord[writeLeaderPage]]]];
IF status=sourceExhausted THEN ERROR; -- wrong length log record
IF FileInstance.GetFileID[fileInstance] # header.fileID THEN ERROR; -- wrong file
[status: status] ← Log.Read[thisRecord: recordID, wordsToSkip: SIZE[FileLogRecord[writeLeaderPage]], to: [base: where, length: AlpineEnvironment.wordsPerPage]];
IF status#normal THEN ERROR; -- log record not as long as it says it is
END;
LogFileLock:
PUBLIC
PROCEDURE [fileInstance: FileInstance.Handle] =
BEGIN
lockSubID: FileLockFormat.FileLockSubID = [file[]];
record: FileLogRecord[lock] ← [volumeID: NULL, fileID: NULL, specifics: lock [lockSubID: LOOPHOLE[lockSubID]]];
[volumeID: record.volumeID, fileID: record.fileID] ← FileInstance.GetVolumeIDAndFileID[fileInstance];
[] ← Log.Write[trans: FileInstance.GetTransHandle[fileInstance], recordType: lock, recordData: [base: @record, length: SIZE[FileLogRecord[lock]]]];
END;
Internal procedures
FileRecoveryProc: Log.RecoveryProc
--[record, type, trans, outcome]-- =
BEGIN
fileInstance: FileInstance.Handle;
header: FileLogRecord;
wordsRead: CARDINAL;
exists: BOOLEAN ← TRUE;
IF type NOT IN DefinedFileRecord THEN ERROR;
Attempt to read the longest possible FileLogRecord, and make sure it is at least as long as the shortest possible FileLogRecord.
[wordsRead: wordsRead] ← Log.ReadForRecovery[thisRecord: record, to: [base: @header, length: SIZE[FileLogRecord]]];
IF wordsRead<SIZE[FileLogRecord[delete]] THEN ERROR; -- log record too short
fileInstance ← FileInstance.Register[trans: trans, volumeID: header.volumeID, fileID: header.fileID];
Recover the update only if the file still exists, under the assumption that if it does not exist then it must have been deleted by some later committed transaction. GetSize is an efficient way to check for the existence of a file because FilePageMgr caches the file's existence and size.
[] ← FilePageMgr.GetSize[FileInstance.GetFileHandle[fileInstance] !
FilePageMgr.NoSuchFile => {exists ← FALSE; CONTINUE}];
IF exists
THEN
WITH r: header
SELECT type
FROM
create =>
BEGIN
owner is not actually needed for recovery, so omit this:
owner: OwnerName;
Fetch: SAFE PROCEDURE RETURNS [c: CHAR] = TRUSTED
{c ← r.owner.text[index]; index ← index+1};
index: CARDINAL ← 0;
IF wordsRead<
SIZE[FileLogRecord[create]]
THEN
ERROR;
-- log record too short
owner ← Rope.FromProc[len: r.owner.length, p: Fetch];
RecoverCreate[fileInstance: fileInstance, initialSize: r.initialSize, outcome: outcome];
END;
delete =>
BEGIN
IF wordsRead<SIZE[FileLogRecord[delete]] THEN ERROR; -- log record too short
RecoverDelete[fileInstance: fileInstance, outcome: outcome];
END;
setSize =>
BEGIN
IF wordsRead<SIZE[FileLogRecord[setSize]] THEN ERROR; -- log record too short
RecoverSetSize[fileInstance: fileInstance, old: r.old, new: r.new, outcome: outcome];
END;
writePages =>
BEGIN
IF wordsRead<SIZE[FileLogRecord[writePages]] THEN ERROR; -- log record too short
RecoverWritePages[fileInstance: fileInstance, recordID: record, pageRun: r.pageRun, outcome: outcome];
END;
writeLeaderPage =>
BEGIN
IF wordsRead<SIZE[FileLogRecord[writeLeaderPage]] THEN ERROR; -- log record too short
RecoverWriteLeaderPage[fileInstance: fileInstance, recordID: record, outcome: outcome];
END;
lock =>
BEGIN
IF wordsRead<SIZE[FileLogRecord[lock]] THEN ERROR; -- log record too short
RecoverLock[fileInstance: fileInstance, lockSubID: r.lockSubID, outcome: outcome];
END;
ENDCASE => ERROR;
Unregister to balance the above Register, so the fileInstance use count will be zero when recovery finished. Pretend that an update has occurred (even if it hasn't) so that the fileInstance will stay around.
FileInstance.SetMaxDeltaVersion[fileInstance, 1];
FileInstance.Unregister[fileInstance];
END;