Types
GMT: TYPE = BasicTime.GMT;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
WalnutOpsHandle: TYPE = WalnutDefs.WalnutOpsHandle;
RootHandle: TYPE = WalnutRoot.RootHandle;
RootHandleRec: PUBLIC TYPE = WalnutRoot.RootHandleRec;
InternalLogInfo: TYPE = WalnutRoot.InternalLogInfo;
MiscLogInfo: TYPE = WalnutRoot.MiscLogInfo;
WhichTempLog: TYPE = WalnutRoot.WhichTempLog;
AlpTHandle: TYPE = WalnutRoot.AlpTHandle;
TransRequest: TYPE = { abort, close, continue };
Opening/closing the rootFile
for the stream for the rootFile - it is a small file
rootBufferOption: FS.StreamBufferParms = [vmPagesPerBuffer: 4, nBuffers: 1];
rootStreamOptions: FS.StreamOptions ¬ [
tiogaRead: FALSE,
truncatePagesOnClose: FALSE,
closeFSOpenFileOnClose: TRUE -- Note: this option is changed for opening temp logs when doing copies, so that we can Unlock the file before closing it
];
Open:
PUBLIC
ENTRY PROC[opsH: WalnutOpsHandle, newSegmentOk, masquerade, newHandle:
BOOL]
RETURNS[ok:
BOOL] = {
ENABLE UNWIND => NULL;
isReadOnly: BOOL ¬ FALSE;
BEGIN
reason: ATOM;
openFile: FS.OpenFile;
rStream: STREAM;
fullName: ROPE;
cp: FS.ComponentPositions;
rootHandle: RootHandle;
[fullName, cp, ] ¬ FS.ExpandName[opsH.rootName];
opsH.rootName ¬ fullName;
IF opsH.rootHandle =
NIL
THEN
opsH.rootHandle ¬ rootHandle ¬ NEW[WalnutRoot.RootHandleRec]
ELSE rootHandle ¬ opsH.rootHandle;
rootHandle.baseName ¬ fullName.Substr[0, cp.base.start];
rootHandle.transInfo ¬ NIL;
rootHandle.alpTH ¬ CreateTrans[opsH];
rootHandle.dbTransH ¬ rootHandle.alpTH;
The following code is commented out since it causes the create timestamp on the root file to be updated which forces a scavenge. ...DBT
IF ~opsH.readOnly THEN {
ENABLE FS.Error => { reason ← error.code; GOTO error };
openFile ← FS.Open[name: opsH.rootName, lock: $write]; -- under rootHandle.alpTH
FS.Close[openFile];
EXITS error =>
IF reason = $unknownFile THEN
ERROR WalnutDefs.Error[$root, $RootNotFound, opsH.rootName]
ELSE
IF reason = $accessDenied THEN isReadOnly ← TRUE
ELSE ERROR WalnutDefs.Error[$root, reason, opsH.rootName];
}
ELSE isReadOnly ← TRUE;
opsH.readOnly ← isReadOnly;
End of root file timestamp problem. ... DBT
BEGIN
ENABLE
FS.Error => { reason ¬ error.code;
GOTO error };
openFile ¬ FS.Open[name: opsH.rootName, lock: $read]; -- under rootHandle.alpTH
rStream ¬ FS.StreamFromOpenFile[
openFile: openFile,
streamOptions: rootStreamOptions,
streamBufferParms: streamBufferOption];
rootHandle.createDate ¬ FS.GetInfo[openFile].created;
EXITS error =>
ERROR WalnutDefs.Error[$root, reason, opsH.rootName];
END;
parse the root file to get names for database, newMail, readArchive, and first logs
BEGIN ENABLE IO.EndOfStream, IO.Error => GOTO cantParse;
finished: BOOL ¬ FALSE;
rStream.SetIndex[0];
DO
curPos: INT ¬ rStream.GetIndex[];
rootEntry: WalnutKernelDefs.RootEntry ¬ NextRootEntry[rStream];
DO
IF rootEntry.ident.Equal["Key",
FALSE]
THEN
{ opsH.key ¬ rootEntry.value; EXIT };
IF rootEntry.ident.Equal["MailFor",
FALSE]
THEN
{ opsH.mailFor ¬ rootEntry.value; EXIT };
IF rootEntry.ident.Equal["Database",
FALSE]
THEN
{
IF opsH.dbName.Find["-cached", 0,
FALSE] = -1
THEN
opsH.dbName ¬ rootEntry.value.Concat["-cached"];
EXIT;
};
IF rootEntry.ident.Equal["SchemaType",
FALSE]
THEN {
default is complete - simple only if value is work simple
opsH.completeSchema ¬ IF rootEntry.value.Equal["Simple", FALSE] THEN FALSE ELSE TRUE;
EXIT;
};
IF rootEntry.ident.Equal["NewMailLog",
FALSE]
THEN {
rootHandle.newMailLog ¬ NEW[WalnutRoot.MiscLogInfoRec ¬ [name: rootHandle.baseName.Concat[rootEntry.value] ]];
EXIT
};
IF rootEntry.ident.Equal["ReadArchiveLog",
FALSE]
THEN {
rootHandle.readArchiveLog ¬ NEW[WalnutRoot.MiscLogInfoRec ¬ [name: rootHandle.baseName.Concat[rootEntry.value]] ];
EXIT
};
IF rootEntry.ident.Equal["LogInfo",
FALSE]
THEN {
li: InternalLogInfo = NEW[WalnutRoot.InternalLogInfoObject ¬ [name: rootHandle.baseName.Concat[rootEntry.info.fileName]] ];
li.internalFileID ¬ rootEntry.info.internalFileID;
li.shortName ¬ rootEntry.info.fileName;
li.logSeqNoPos ¬ rootEntry.info.seqNoPos;
li.logSeqNo ¬
IF rootEntry.info.logSeqNo.Fetch[0] = 'E
THEN -1
ELSE Convert.IntFromRope[rootEntry.info.logSeqNo];
IF li.logSeqNo = -1
THEN rootHandle.expungeLog ¬ li
ELSE
rootHandle.currentLog ¬ li;
EXIT;
};
IF rootEntry.ident.Equal["End",
FALSE]
THEN {
finished ¬ TRUE;
EXIT
};
rStream.Close[ ! IO.Error, FS.Error => CONTINUE];
FinishTransaction[rootHandle, close ! WalnutDefs.Error => CONTINUE];
rootHandle ¬ NIL;
ERROR WalnutDefs.Error[$log, $BadRootFile,
IO.PutFR["Bad entry %g in RootFile at pos %g",
[rope[rootEntry.ident]], [integer[curPos]] ]];
ENDLOOP;
IF finished THEN EXIT;
ENDLOOP;
EXITS
cantParse => {
rStream.Close[ ! IO.Error, FS.Error => CONTINUE];
FinishTransaction[rootHandle, close ! WalnutDefs.Error => CONTINUE];
ERROR WalnutDefs.Error[$log, $BadRootFile, opsH.rootName];
};
END;
rStream.Close[];
rStream¬ NIL; -- not needed
BEGIN
who: ROPE ¬ NIL;
SELECT
TRUE
FROM
opsH.key.Length[] = 0 => who ¬ "Key";
opsH.dbName.Length[] = 0 => who ¬ "Database";
opsH.mailFor.Length[] = 0 => who ¬ "MailFor";
rootHandle.newMailLog = NIL => who ¬ "NewMailLog";
rootHandle.readArchiveLog = NIL => who ¬ "ReadArchiveLog";
rootHandle.currentLog = NIL => who ¬ "CurrentLog";
rootHandle.expungeLog = NIL => who ¬ "ExpungeLog";
ENDCASE;
IF who #
NIL
THEN
ERROR WalnutDefs.Error[$root, $BadRootFile,
IO.PutFR1["Incomplete RootFile - %g entry is missing", [rope[who]] ] ];
END;
IF openFile #
FS.nullOpenFile
THEN
FS.Close[openFile ! FS.Error => CONTINUE];
FinishTransaction[rootHandle, close ! WalnutDefs.Error => CONTINUE];
rootHandle.alpTH ¬ NIL;
rootHandle.dbTransH ¬ NIL;
END;
check for possible duplicate segment names or numbers
IF newHandle
THEN {
FOR ohL:
LIST
OF WalnutOpsHandle ¬ opsHandleList, ohL.rest
UNTIL ohL =
NIL
DO
IF opsH.rootName.Equal[ohL.first.rootName,
FALSE]
THEN
ERROR WalnutDefs.Error[$root, $DuplicateRootFile, opsH.rootName];
ENDLOOP;
opsHandleList ¬ CONS[opsH, opsHandleList];
};
DeclareDBSegment[opsH, newSegmentOk];
RETURN[TRUE];
};
StartTransaction:
PUBLIC
PROC[opsH: WalnutOpsHandle, openDB:
BOOL ¬
TRUE]
RETURNS[schemaInvalid: BOOL] = {
rH: RootHandle = opsH.rootHandle;
IF rH.alpTH #
NIL
THEN {
IF rH.rootOpenFileForLogs #
FS.nullOpenFile
THEN
FS.Close[rH.rootOpenFileForLogs ! FS.Error => CONTINUE];
CloseDBTrans[opsH];
rH.alpTH ¬ NIL;
};
[rH.rootOpenFileForLogs, rH.alpTH] ¬ GetRootReadLock[opsH];
rH.dbTransH ¬ rH.alpTH;
IF openDB THEN schemaInvalid ¬ OpenDBTrans[opsH] ELSE schemaInvalid ¬ TRUE;
};
Shutdown:
PUBLIC
PROC[opsH: WalnutOpsHandle] = {
rH: RootHandle;
IF (rH ¬ opsH.rootHandle) = NIL THEN RETURN;
CloseInternalLog[rH.currentLog];
CloseMLog[opsH: opsH, mli: rH.readArchiveLog];
CloseMLog[opsH: opsH, mli: rH.newMailLog, abortIfExternal: TRUE];
opsH.rootHandle ¬ NIL;
FinishTransaction[rH, close ! WalnutDefs.Error => CONTINUE];
};
CloseTransaction:
PUBLIC
PROC[opsH: WalnutOpsHandle] = {
rH: RootHandle;
IF (rH ¬ opsH.rootHandle) = NIL THEN RETURN;
CloseInternalLog[rH.currentLog];
CloseMLog[opsH, rH.readArchiveLog];
IF rH.rootOpenFileForLogs #
FS.nullOpenFile
THEN
FS.Close[rH.rootOpenFileForLogs ! FS.Error => CONTINUE];
rH.rootOpenFileForLogs ¬ FS.nullOpenFile;
FinishTransaction[rH, close ! WalnutDefs.Error => CONTINUE];
rH.alpTH ¬ NIL;
rH.dbTransH ¬ NIL;
};
GetOpsHandleList:
PUBLIC
ENTRY
PROC
RETURNS[
LIST
OF WalnutOpsHandle] = {
ENABLE UNWIND => NULL;
RETURN[opsHandleList]
};
ExamineHandleList:
PUBLIC
ENTRY
PROC[proc:
PROC[ohL:
LIST
OF WalnutOpsHandle]
RETURNS[
LIST
OF WalnutOpsHandle] ] = {
ENABLE UNWIND => NULL;
opsHandleList ¬ proc[opsHandleList];
};
FinishTransaction:
PROC[rootHandle: RootHandle, request: TransRequest] = {
IF rootHandle = NIL OR rootHandle.alpTH = NIL THEN RETURN;
SELECT request
FROM
abort => NULL;
continue => NULL;
close => NULL;
ENDCASE =>
WalnutDefs.Error[$bug, $UnknownCode, "Bad value passed to FinishTransaction"];
};
CommitAndContinue:
PUBLIC
PROC[opsH: WalnutOpsHandle] =
{ FinishTransaction[opsH.rootHandle, continue] };
AbortTransaction:
PUBLIC
PROC[opsH: WalnutOpsHandle] = {
rH: RootHandle;
IF (rH ¬ opsH.rootHandle) = NIL THEN RETURN;
FinishTransaction[rH, abort];
CloseInternalLog[rH.currentLog];
StopExpunge[opsH];
CloseMLog[opsH, rH.readArchiveLog];
rH.alpTH ¬ NIL;
rH.dbTransH ¬ NIL;
};
EraseDB:
PUBLIC
PROC[opsH: WalnutOpsHandle] = {
rH: RootHandle = opsH.rootHandle;
IF rH =
NIL
THEN
ERROR WalnutDefs.Error[$root, $RootNotOpen, "Trying to do an EraseDB"];
};
FlushMailStream:
PUBLIC
PROC[opsH: WalnutOpsHandle] = {
WalnutStream.FlushStream[strm: opsH.rootHandle.newMailLog.stream, setCreateDate: TRUE];
IF opsH.rootHandle.newMailLog.trans = NIL THEN RETURN;
FinishMiscTrans[alpTH: opsH.rootHandle.newMailLog.trans, abort: FALSE, continue: TRUE, opsH: opsH]
};
GetRootFileStamp:
PUBLIC
PROC[opsH: WalnutOpsHandle]
RETURNS[rootFileStamp:
GMT] = {
reason: ATOM;
BEGIN
ENABLE
FS.Error => { reason ¬ error.code;
GOTO error };
of:
FS.OpenFile ¬
FS.Open[name: opsH.rootName]; -- under opsH.rootHandle.alpTH
rootFileStamp ¬ FS.GetInfo[of].created;
FS.Close[of];
EXITS
error =>
ERROR WalnutDefs.Error[$root, reason, opsH.rootName];
END;
};
Managing the CurrentLog
AcquireWriteLock:
PUBLIC
PROC[opsH: WalnutOpsHandle] = {
NULL;
};
OpenLogStreams:
PUBLIC
PROC[opsH: WalnutOpsHandle] = {
rH: RootHandle = opsH.rootHandle;
IF rH =
NIL
--OR rH.alpTH =
NIL--
THEN
ERROR WalnutDefs.Error[$log, $TransNotOpen, "Must do Shutdown and Startup"];
IF opsH.readOnly
THEN rH.currentLog.readStream ¬
StreamOpen[name: rH.currentLog.name, readOnly: TRUE--, trans: rH.alpTH--]
ELSE {
rH.currentLog.writeStream ¬
StreamOpen[name: rH.currentLog.name--, trans: rH.alpTH--];
Establish EOF invariant for log stream, DCS, April 26, 1991
rH.currentLog.writeStream.SetIndex[rH.currentLog.writeStream.GetLength[]];
rH.currentLog.readStream ¬
FS.StreamFromOpenFile[FS.OpenFileFromStream[rH.currentLog.writeStream]];
<<FS.StreamFromOpenStream[rH.currentLog.writeStream];>>
}
};
ReleaseWriteLock:
PUBLIC
PROC[opsH: WalnutOpsHandle] = {
uses same trans as before
currentLog: InternalLogInfo = opsH.rootHandle.currentLog;
CloseInternalLog[currentLog];
currentLog.writeStream ¬
StreamOpen[name: currentLog.name--, trans: opsH.rootHandle.alpTH--];
Establish EOF invariant for log stream, DCS, April 26, 1991
currentLog.writeStream.SetIndex[currentLog.writeStream.GetLength[]];
currentLog.readStream ¬ FS.StreamFromOpenFile[FS.OpenFileFromStream[currentLog.writeStream]];
<<currentLog.readStream ¬ FS.StreamFromOpenStream[currentLog.writeStream];>>
};
CloseLogStreams:
PUBLIC
PROC[opsH: WalnutOpsHandle] = {
rH: RootHandle = opsH.rootHandle;
IF rH = NIL THEN RETURN;
CloseInternalLog[rH.currentLog];
StopExpunge[opsH];
FinishTransaction[rH, continue];
};
Managing the ExpungeLog
GetQuota:
PUBLIC
PROC[opsH: WalnutOpsHandle]
RETURNS[spaceInUse, quota:
INT] = {
rH: RootHandle = opsH.rootHandle;
spaceInUse ¬ 0;
quota ¬ 1000000;
};
GetStreamsForExpunge:
PUBLIC
PROC[opsH: WalnutOpsHandle, starting:
BOOL, pagesWanted:
INT] = {
assert: have a write lock on the currentLog, the expungeLog is not open
rH: RootHandle = opsH.rootHandle;
expungeLog: InternalLogInfo = opsH.rootHandle.expungeLog;
IF expungeLog.writeStream #
NIL
THEN
expungeLog.writeStream.Close[ ! IO.Error, FS.Error => CONTINUE];
expungeLog.writeStream ¬ NIL;
IF starting
THEN {
delete and re-create the expunge log, allowing alpine to place the file on the volume with the most free space
openFile: FS.OpenFile ¬ [];
FS.Delete[name: expungeLog.name]; -- should be under transaction rH.alpTH
openFile ¬ FS.Create[
name: expungeLog.name,
pages: 0]; -- under rH.alpTH
FinishTransaction[rH, continue];
If the file is going to be unlocked, then this option must be false
rootStreamOptions[closeFSOpenFileOnClose] ¬ TRUE;
rH.expungeLog.writeStream ¬
FS.StreamFromOpenFile[openFile: openFile, accessRights: $write,
initialPosition: $start, streamOptions: rootStreamOptions,
streamBufferParms: streamBufferOption];
rH.expungeLog.readStream ¬
FS.StreamFromOpenFile[openFile];
}
ELSE {
expungeLog.writeStream ¬ StreamOpen[
name: expungeLog.name, trans: rH.alpTH, exclusive: TRUE];
expungeLog.readStream ¬
FS.StreamFromOpenFile[FS.OpenFileFromStream[expungeLog.writeStream]];
};
};
SwapLogs:
PUBLIC
PROC[opsH: WalnutOpsHandle, expungeFileID:
INT, newRootStamp:
GMT]
RETURNS[newLogLen:
INT] = {
Swaps the current log and expunge log so that they are interchanged. The newRootStamp is ignored.
rH: RootHandle = opsH.rootHandle;
tempLog: InternalLogInfo;
reason: ATOM;
IF rH.currentLog.internalFileID = expungeFileID
THEN
-- already did swap
RETURN[rH.currentLog.writeStream.GetLength[]];
IF rH.expungeLog.internalFileID # expungeFileID
THEN
ERROR WalnutDefs.Error[$expungeLog, $WrongFileID,
IO.PutFR["FileID is: %g, FileID expected is: %g",
[integer[rH.expungeLog.internalFileID]], [integer[expungeFileID]]] ];
IF rH.expungeLog.writeStream =
NIL
THEN
ERROR WalnutDefs.Error[$expungeLog, $LogNotOpen, "Doing SwapLogs"];
BEGIN
ENABLE
FS.Error => { reason ¬ error.code;
GOTO error };
of:
FS.OpenFile ¬ FS.Open[
name: opsH.rootName, wantedCreatedTime: rH.createDate,
lock: $write]; -- under rH.alpTH
rStream:
STREAM ¬ FS.StreamFromOpenFile[
openFile: of, accessRights: $write,
streamOptions: rootStreamOptions,
streamBufferParms: streamBufferOption];
rStream.SetIndex[rH.currentLog.logSeqNoPos];
rStream.PutRope["Expunge"];
rStream.SetIndex[rH.expungeLog.logSeqNoPos];
rStream.PutRope[IO.PutFR1["%07d", [integer[rH.currentLog.logSeqNo]]]];
<<FS.SetByteCountAndCreatedTime[of, -1, newRootStamp];>>
WalnutStream.SetHighWaterMark[rH.currentLog.writeStream, 0, 0];
newLogLen ¬ rH.expungeLog.writeStream.GetLength[];
rStream.Flush[];
MarkDBTrans[rH.dbTransH]; -- clean up the transaction
rStream.Close[];
<<rH.createDate ¬ newRootStamp;>>
rH.createDate ¬ FS.GetInfo[FS.Open[name: opsH.rootName, lock: $read]].created;
CloseInternalLog[rH.currentLog]; -- release the currentLog streams
CloseInternalLog[rH.expungeLog]; -- will want only read Lock
-- swap the rootLog variables currentLog & expungeLog
tempLog ¬ rH.currentLog;
rH.currentLog ¬ rH.expungeLog;
rH.expungeLog ¬ tempLog;
rH.currentLog.logSeqNo ¬ rH.expungeLog.logSeqNo;
rH.expungeLog.logSeqNo ¬ -1;
EXITS
error => ERROR WalnutDefs.Error[$root, reason, "During SwapLogs"];
END;
};
StopExpunge:
PUBLIC
PROC[opsH: WalnutOpsHandle] = {
IF opsH.rootHandle = NIL OR opsH.rootHandle.expungeLog = NIL THEN RETURN;
IF opsH.rootHandle.expungeLog.writeStream #
NIL
THEN {
opsH.rootHandle.expungeLog.writeStream.Close[ !
FS.Error,
IO.Error =>
CONTINUE];
opsH.rootHandle.expungeLog.writeStream ¬ NIL;
};
};
Managing the TempLogs
PrepareToCopyTempLog:
PUBLIC
PROC[ opsH: WalnutOpsHandle, which: WhichTempLog, pagesAlreadyCopied:
INT, reportProc: WalnutDefs.CheckReportProc]
RETURNS[ok:
BOOL¬
FALSE] = {
makes sure there is enough space to copy the WhichTempLog onto the currentLog; returns NIL for the streams if there is not enough space; currentStream & tempStream are opened under the same transaction
tempLogLen, pagesNeeded, actualPages, currentInUsePages: INT;
rH: RootHandle = opsH.rootHandle;
current: STREAM = rH.currentLog.writeStream;
tempStream: STREAM;
of: FS.OpenFile;
mli: MiscLogInfo ¬ OpenTempStream[rH, which, TRUE];
IF (tempStream ¬ mli.stream) = NIL THEN RETURN[FALSE];
currentInUsePages ¬ FS.PagesForBytes[current.GetLength[]];
tempLogLen ¬ tempStream.GetLength[];
extra bytes are for start/endcopy entries
pagesNeeded ¬ FS.PagesForBytes[tempLogLen+200] + currentInUsePages - pagesAlreadyCopied;
actualPages ¬ FS.GetInfo[of¬ FS.OpenFileFromStream[current]].pages;
BEGIN
IF actualPages < pagesNeeded
THEN {
<<FS.SetPageCount[of, pagesNeeded ! FS.Error =>
IF error.code = $quotaExceeded THEN {
reportProc[opsH, "\n%g\n", [rope[error.explanation]] ]; GOTO quota }
ELSE REJECT];>>
Setting the size of the file sets a lock on the owner database. We remove this read lock after committing the transaction (so it will be downgraded); note we have used the same transaction for the new log as for the root
BEGIN
MarkDBTrans[rH.dbTransH];
END;
};
<<EXITS
quota => {
mli.stream.Close[ ! IO.Error, FS.Error => CONTINUE];
tempStream ← mli.stream ← NIL;
RETURN
};>>
END;
RETURN[tempStream # NIL];
};
GetStreamsForCopy:
PUBLIC
PROC[opsH: WalnutOpsHandle, which: WhichTempLog]
RETURNS[tempStream:
STREAM] = {
tempStream ¬ OpenTempStream[opsH.rootHandle, which, TRUE].stream;
};
FinishCopy:
PUBLIC
PROC[opsH: WalnutOpsHandle, which: WhichTempLog] = {
sets the length of the tempLog to something reasonable and closes the tempStream
Also releases the write lock held on the temp log
mli: MiscLogInfo;
fileHandle: FS.OpenFile;
pages: INT ¬ 200;
hwmBytes: INT ← FS.BytesForPages[200];
prevPages: INT ← -1;
SELECT which
FROM
newMail => mli ¬ opsH.rootHandle.newMailLog;
readArchive => { mli ¬ opsH.rootHandle.readArchiveLog; pages ¬ 0};
ENDCASE => ERROR WalnutDefs.Error[$log, $UnknownLogFileType];
IF mli.stream = NIL THEN mli ¬ OpenTempStream[opsH.rootHandle, which];
fileHandle ¬ FS.OpenFileFromStream[mli.stream];
IF which = newMail THEN prevPages ← FS.GetInfo[fileHandle].pages;
<< SetLength does not appear to work for mli.stream
WalnutStream.SetHighWaterMark[
strm: mli.stream, hwmBytes: 0, numPages: pages, setCreateDate: TRUE];
>>
mli.stream.Close[ ! FS.Error, IO.Error => CONTINUE];
BEGIN
FS.Close[fileHandle ! FS.Error => CONTINUE];
mli.stream ¬ NIL;
IF which = newMail
THEN {
-- to get rid of potential lock conflict on newMailLog
CloseTransaction[opsH];
[] ¬ StartTransaction[opsH, TRUE];
};
END;
};
AbortTempCopy:
PUBLIC
PROC[opsH: WalnutOpsHandle, which: WhichTempLog] = {
mli: MiscLogInfo;
closeError: BOOL ¬ FALSE;
SELECT which
FROM
newMail => mli ¬ opsH.rootHandle.newMailLog;
readArchive => mli ¬ opsH.rootHandle.readArchiveLog;
ENDCASE => ERROR WalnutDefs.Error[$log, $UnknownLogFileType];
IF mli.stream = NIL THEN RETURN;
mli.stream.Close[ ! FS.Error, IO.Error => { closeError ¬ TRUE; CONTINUE} ];
mli.stream ¬ NIL;
};
Access to streams for the various logs, other values parsed from the rootFile
GetNewMailStream:
PUBLIC
PROC[opsH: WalnutOpsHandle, lengthRequired, pagesWanted:
INT]
RETURNS[ok: BOOL¬FALSE, actualLen: INT] = {
mStream: STREAM;
mailLog: MiscLogInfo;
rH: RootHandle = opsH.rootHandle;
CheckStamp[rH, GetRootFileStamp[opsH]];
IF (mailLog ¬ rH.newMailLog).stream # NIL THEN RETURN[FALSE, -1];
IF seriousDebugging THEN StatsReport[opsH, "\n### Trans for newMailLog"];
[mailLog.rootOpenFile--, mailLog.trans--] ¬ GetRootReadLock[opsH];
mStream ¬ mailLog.stream ¬ StreamOpen[
name: mailLog.name--, trans: rH.newMailLog.trans--, exclusive: TRUE];
IF (actualLen ¬ mStream.GetLength[]) < lengthRequired
THEN {
CloseMLog[opsH, mailLog];
RETURN[FALSE, actualLen];
};
IF actualLen > lengthRequired
THEN {
WalnutStream.SetHighWaterMark[mStream, lengthRequired, pagesWanted, TRUE];
mStream.Flush[];
FinishMiscTrans[alpTH: mailLog.trans, abort: FALSE, continue: TRUE, opsH: opsH];
};
RETURN[TRUE, actualLen]
};
GetReadArchiveStream:
PUBLIC
PROC[opsH: WalnutOpsHandle, pagesWanted:
INT ¬ -1]
RETURNS[STREAM] = {
readArchive: MiscLogInfo;
IF (readArchive ¬ opsH.rootHandle.readArchiveLog).stream # NIL THEN RETURN[NIL];
CheckStamp[opsH.rootHandle, GetRootFileStamp[opsH]];
readArchive.stream ¬ StreamOpen[
name: readArchive.name,
trans: opsH.rootHandle.alpTH,
pages: pagesWanted,
exclusive: TRUE];
RETURN[readArchive.stream]
};
ReturnNewMailStream:
PUBLIC
PROC[opsH: WalnutOpsHandle] = {
mailLog: MiscLogInfo = opsH.rootHandle.newMailLog;
mStrm: STREAM ¬ mailLog.stream;
mailLog.stream ¬ NIL;
mStrm.Close[ ! FS.Error, IO.Error => CONTINUE];
IF mailLog.rootOpenFile #
FS.nullOpenFile
THEN {
trans: AlpTHandle ¬ mailLog.trans;
FS.Close[mailLog.rootOpenFile ! FS.Error => CONTINUE];
mailLog.rootOpenFile ¬ FS.nullOpenFile;
mailLog.trans ¬ NIL;
FinishMiscTrans[alpTH: trans, abort: FALSE, continue: FALSE, opsH: opsH];
};
};
ReturnReadArchiveStream:
PUBLIC PROC[opsH: WalnutOpsHandle] = {
raStrm: STREAM ¬ opsH.rootHandle.readArchiveLog.stream;
opsH.rootHandle.readArchiveLog.stream ¬ NIL;
raStrm.Close[ ! FS.Error, IO.Error => CONTINUE]; -- assume flushed
};
Utilities
RegisterStatsProc:
PUBLIC
PROC[opsH: WalnutOpsHandle, proc: WalnutDefs.CheckReportProc] =
{ IF opsH.rootHandle # NIL THEN opsH.rootHandle.statsProc ¬ proc };
UnregisterStatsProc:
PUBLIC
PROC[opsH: WalnutOpsHandle, proc: WalnutDefs.CheckReportProc] = {
IF opsH.rootHandle = NIL THEN RETURN;
IF opsH.rootHandle.statsProc = proc THEN opsH.rootHandle.statsProc ¬ NIL
};
StatsReport:
PUBLIC WalnutDefs.CheckReportProc = {
IF opsH.rootHandle = NIL THEN RETURN;
IF opsH.rootHandle.statsProc # NIL THEN opsH.rootHandle.statsProc[opsH, format, v1, v2, v3]
};
Local Utilities
CreateTrans:
PROC[opsH: WalnutOpsHandle]
RETURNS[alpTH: AlpTHandle] = {
rH: RootHandle = opsH.rootHandle;
alpTH ¬ NIL;
};
FinishMiscTrans:
PROC[alpTH: AlpTHandle, abort:
BOOL, continue:
BOOL, opsH: WalnutOpsHandle] = {
IF alpTH = NIL THEN RETURN;
};
GetRootReadLock:
PROC[opsH: WalnutOpsHandle]
RETURNS[of: FS.OpenFile, alpTH: AlpTHandle] = {
reason: ATOM;
rootFileStamp: GMT;
alpTH ¬ CreateTrans[opsH];
BEGIN
ENABLE
FS.Error => { reason ¬ error.code;
GOTO error};
of ¬ FS.Open[
name: opsH.rootName];
rootFileStamp ¬ FS.GetInfo[of].created;
EXITS
error => {
FS.Close[of ! FS.Error => CONTINUE];
FinishMiscTrans[alpTH, FALSE, FALSE, opsH];
ERROR WalnutDefs.Error[$root, reason, opsH.rootName];
};
END;
CheckStamp[opsH.rootHandle, rootFileStamp];
};
CloseInternalLog:
PROC[li: InternalLogInfo] = {
IF li = NIL THEN RETURN;
IF li.writeStream #
NIL
THEN {
li.writeStream.Close[ ! FS.Error, IO.Error => CONTINUE];
li.writeStream ¬ NIL;
};
IF li.readStream #
NIL
THEN {
li.readStream.Close[ ! FS.Error, IO.Error => CONTINUE];
li.readStream ¬ NIL;
};
};
CloseMLog:
PROC[opsH: WalnutOpsHandle, mli: MiscLogInfo, abortIfExternal:
BOOL ¬
FALSE] = {
if abortIfExternal and rootOpenFile#nullOpenFile then abort
strm: STREAM;
IF mli = NIL THEN RETURN;
strm ¬ mli.stream;
mli.stream ¬ NIL;
IF mli.rootOpenFile #
NIL
THEN {
-- is external
of: FS.OpenFile ¬ mli.rootOpenFile;
mli.rootOpenFile ¬ FS.nullOpenFile;
IF strm # NIL THEN strm.Close[! IO.Error, FS.Error => CONTINUE];
IF mli.trans #
NIL AND mli.trans # opsH.rootHandle.alpTH
THEN
FinishMiscTrans[alpTH: mli.trans, abort: abortIfExternal, continue: FALSE, opsH: opsH ! FS.Error => CONTINUE];
FS.Close[of ! FS.Error => CONTINUE];
}
ELSE
IF strm # NIL THEN strm.Close[ ! FS.Error, IO.Error => CONTINUE];
};
OpenTempStream:
PROC[rH: RootHandle, which: WhichTempLog, readOnly:
BOOLEAN ¬
FALSE]
RETURNS[mli: MiscLogInfo] = {
SELECT which
FROM
newMail => mli ¬ rH.newMailLog;
readArchive => mli ¬ rH.readArchiveLog;
ENDCASE => ERROR WalnutDefs.Error[$log, $UnknownLogFileType];
IF mli.stream #
NIL
THEN {
Close the stream even though it is going to be re-opened immediately because the desired access (read vs. write) may be different.
mli.stream.Close[ ! IO.Error, FS.Error => CONTINUE];
mli.stream ¬ NIL;
};
mli.stream ¬ StreamOpen[name: mli.name, trans: rH.alpTH, readOnly: readOnly, exclusive: FALSE, closeFSOpenFile: FALSE];
mli.trans ¬ rH.alpTH
};
CheckStamp:
PROC[rH: RootHandle, created:
GMT] = {
IF created # rH.createDate
THEN
ERROR WalnutDefs.Error[$root, $WrongCreateDate,
IO.PutFR[" Create date is : %g, Create date expected is: %g",
[time[created]], [time[rH.createDate]]] ];
};
NextRootEntry:
PROC[strm:
STREAM]
RETURNS[rte: WalnutKernelDefs.RootEntry] = {
[] ¬ strm.SkipWhitespace[flushComments: TRUE];
rte.ident ¬ strm.GetTokenRope[].token;
IF rte.ident.Equal["End", FALSE] THEN RETURN;
[] ¬ strm.SkipWhitespace[flushComments: TRUE];
IF rte.ident.Equal["LogInfo",
FALSE]
THEN {
lif: WalnutKernelDefs.LogInfoFromRoot;
lif.fileName ¬ strm.GetTokenRope[IO.IDProc].token;
IF lif.fileName.Fetch[0] = '[
OR lif.fileName.Fetch[0] = '/
THEN
ERROR WalnutDefs.Error[$root, $OldRootFormat, "You must convert your root file to the new format"];
[]¬ strm.SkipWhitespace[flushComments: TRUE];
lif.internalFileID ¬ strm.GetInt[];
[]¬ strm.SkipWhitespace[flushComments: TRUE];
lif.seqNoPos ¬ strm.GetIndex[];
lif.logSeqNo ¬ strm.GetTokenRope[].token;
[]¬ strm.SkipWhitespace[flushComments: TRUE];
rte.info ¬ lif;
}
ELSE
IF rte.ident.Equal["Key", FALSE] THEN rte.value ¬ strm.GetLineRope[]
ELSE {
rte.value ¬ strm.GetTokenRope[IO.IDProc].token;
[]¬ strm.SkipWhitespace[flushComments: TRUE];
};
RETURN[rte];
};
has special options (see below)
StreamOpen:
PROC[name:
ROPE, readOnly:
BOOL¬
FALSE, pages:
INT ¬ 200,
trans: AlpTHandle¬ NIL,
exclusive: BOOL ¬ FALSE,
closeFSOpenFile: BOOL ¬ TRUE] RETURNS [strm: STREAM] = {
openFile: FS.OpenFile ¬ [];
IF readOnly
THEN {
openFile ¬ FS.Open[
name: name]; -- under trans
strm ¬ FS.StreamFromOpenFile[openFile: openFile, streamOptions: rootStreamOptions, streamBufferParms: streamBufferOption];
}
ELSE {
actualPages: INT;
openFile ¬ FS.Open[
name: name,
lock: $write]; -- under trans
actualPages ¬ FS.GetInfo[openFile].pages;
<<IF pages > actualPages THEN FS.SetPageCount[openFile, pages];>>
If the file is going to be unlocked, then this option must be false
rootStreamOptions[closeFSOpenFileOnClose] ¬ closeFSOpenFile;
strm ¬ FS.StreamFromOpenFile[openFile: openFile, accessRights: $write, initialPosition: $start, streamOptions: rootStreamOptions, streamBufferParms: streamBufferOption];
};
};
streamBufferOption: FS.StreamBufferParms = [vmPagesPerBuffer: 4, nBuffers: 4];
DeclareDBSegment:
PROC[opsH: WalnutOpsHandle, newSegmentOk:
BOOL] = {
protectionViolation: BOOL ¬ FALSE;
opsH.db ¬ LoganBerry.Open[dbName: opsH.dbName];
};
OpenDBTrans:
PROC[opsH: WalnutOpsHandle]
RETURNS[schemaInvalid:
BOOL] = {
schemaInvalid ¬ FALSE;
};
CloseDBTrans:
PROC[opsH: WalnutOpsHandle]
RETURNS[] = {
NULL;
};
MarkDBTrans:
PROC[trans: WalnutRoot.TransactionHandle]
RETURNS[] = {
NULL;
};