LogOps.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by: Willie-Sue, October 30, 1985 2:17:13 pm PST
Woosh, August 14, 1985 4:01:15 pm PDT
DIRECTORY
AlpineFS USING [StreamOpen],
Commander USING [CommandProc, Register],
CommandTool USING [CurrentWorkingDirectory],
FS USING [ComponentPositions, Error, BytesForPages, ExpandName, PagesForBytes, StreamOpen],
FSRope USING [StreamFromRope],
IO,
Process USING [Detach],
RefTab USING [EachPairAction, Ref, Val, Create, Fetch, Store, Pairs],
Rope,
UserProfile USING [Token],
ViewerClasses USING [Viewer],
ViewerIO USING [CreateViewerStreams],
ViewerOps USING [FindViewer, OpenIcon],
WalnutDefs USING [Error],
WalnutLog USING [LogLength, OpenLogStreams, ReturnCurrentLogStreams, ShutdownLog],
WalnutLogExpunge -- using almost everything -- ,
WalnutRoot USING [CommitAndContinue, Open, RegisterStatsProc, Shutdown, StartTransaction, SwapLogs, UnregisterStatsProc],
WalnutStream USING [FindNextEntry, Open, PeekEntry];
LogOps: CEDAR PROGRAM
IMPORTS
AlpineFS, Commander, CommandTool, FS, FSRope, IO, Process, RefTab, Rope,
UserProfile, ViewerIO, ViewerOps,
WalnutDefs, WalnutLog, WalnutLogExpunge, WalnutRoot, WalnutStream
= BEGIN
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
out, tsLogStrm: STREAMNIL;
debugging: BOOLFALSE;
Xyz: TYPE = REF ValueObject;
ValueObject: TYPE = RECORD[num, bytes: INT ← 0];
starsRope: ROPE =
"\n\n************************************************************\n";
noEntry: ROPE = "\n ***** No entry found at %g, backing up to prevPos: %g\n";
bad: ROPE = "\tBad entry had ident $%g, length %g; next entry was %g bytes later\n";
nextValid: ROPE = "\n\tNext valid entry found at %g - continuing\n\n";
noInputFile: ROPE =
"\nNo input file specified & no Walnut.WalnutRootFile entry in profile - quitting\n";
noRootFile: ROPE =
"\nNo rootFile file specified & no Walnut.WalnutRootFile entry in profile - quitting\n";
eosR: ROPE =
"\n\n **** EndOfStream encountered at pos %g, when trying for pos %g\n";
endS: ROPE = "Bad entry had ident %g, length %g, end was %g bytes later\n";
TSStream: PROC[name, tsLogFile, wDir: ROPE] RETURNS [out, tsLogStrm: STREAM] = {
v: ViewerClasses.Viewer ← ViewerOps.FindViewer[name];
out ← ViewerIO.CreateViewerStreams[name, v, NIL, FALSE].out;
IF tsLogFile # NIL THEN
tsLogStrm ← FS.StreamOpen[
fileName: tsLogFile, accessOptions: $create, keep: 10, wDir: wDir];
IF v#NIL THEN IF v.iconic THEN ViewerOps.OpenIcon[v];
};
SetupScan: PROC[commandLine, wDir: ROPE] = {
clStream: STREAM ← FSRope.StreamFromRope[commandLine];
logFile: ROPE;
startPos: INT;
logFile ← clStream.GetTokenRope[IO.IDProc ! IO.EndOfStream => CONTINUE].token;
startPos ← clStream.GetInt[ ! IO.EndOfStream => {startPos ← 0; CONTINUE }];
Scan[logFile, wDir, startPos];
};
Scan: PROC[logFile, wDir: ROPE, startPos: INT] = {
strm: STREAM;
logLength: INT;
fullLogName: ROPE;
scanStart: ROPE = "\t\t Scan Log for Entries, started @ %g\n";
scan: ROPE = "Scanning the logfile: %g, starting at pos: %g:\n\n(0)";
[out, tsLogStrm] ← TSStream["Walnut Rescue", "WalnutRescue.Log", wDir];
out.PutRope[starsRope]; tsLogStrm.PutRope[starsRope];
out.PutF[scanStart, IO.time[] ]; tsLogStrm.PutF[scanStart, IO.time[] ];
IF logFile = NIL THEN {
[logFile, strm] ← TryUsingRootFileName[];
IF logFile = NIL THEN {
out.PutRope[noInputFile]; tsLogStrm.PutRope[noInputFile];
tsLogStrm.Close[];
RETURN;
};
};
fullLogName ← FS.ExpandName[logFile, wDir].fullFName;
IF strm = NIL THEN
strm ← WalnutStream.Open[name: fullLogName, readOnly: TRUE ! FS.Error => CONTINUE];
IF strm = NIL THEN {
noOpen: ROPE = "Could not open %g\n";
out.PutF[noOpen, IO.rope[fullLogName] ]; tsLogStrm.PutF[noOpen, IO.rope[fullLogName] ];
tsLogStrm.Close[];
RETURN
};
IF (logLength ← strm.GetLength[]) = 0 THEN {
empty: ROPE = "The log file %g is empty\n";
out.PutF[empty, IO.rope[fullLogName] ]; tsLogStrm.PutF[empty, IO.rope[fullLogName] ];
tsLogStrm.Close[];
RETURN
};
out.PutF[scan, IO.rope[fullLogName], IO.int[startPos] ];
tsLogStrm.PutF[scan, IO.rope[fullLogName], IO.int[startPos] ];
DoScan[strm, startPos, logLength];
tsLogStrm.Close[];
};
TryUsingRootFileName: PROC RETURNS[logFile: ROPE, strm: STREAM] = {
rootFile: ROPE ← UserProfile.Token[key: "Walnut.WalnutRootFile", default: ""];
cp: FS.ComponentPositions;
IF rootFile.Length[] = 0 THEN RETURN[NIL, NIL];
cp ← FS.ExpandName[rootFile].cp;
logFile ← Rope.Concat[Rope.Substr[rootFile, 0, cp.ext.start], "LogX"];  -- try logX
strm ← WalnutStream.Open[name: logFile, readOnly: TRUE ! FS.Error => CONTINUE];
IF strm = NIL THEN RETURN[logFile, NIL];
IF strm.GetLength[] # 0 THEN RETURN;
strm.Close[];  -- try the other log
strm ← NIL;
logFile ← Rope.Concat[Rope.Substr[rootFile, 0, cp.ext.start], "LogY"];  -- try logY
};
SetupFix: PROC[commandLine, wDir: ROPE] = {
clStream: STREAM ← FSRope.StreamFromRope[commandLine];
rootFile: ROPE;
entriesToIgnore, lastToIgnore: LIST OF ATOM;
this: ATOM;
rootFile ← clStream.GetTokenRope[IO.IDProc ! IO.EndOfStream => CONTINUE].token;
DO
this ← clStream.GetAtom[ ! IO.EndOfStream => { this ← NIL; CONTINUE} ];
IF this = NIL THEN EXIT;
IF entriesToIgnore = NIL THEN entriesToIgnore ← lastToIgnore ← LIST[this]
ELSE { lastToIgnore.rest ← LIST[this]; lastToIgnore ← lastToIgnore.rest; };
ENDLOOP;
FixWalnutLog[rootFile, wDir, FALSE, entriesToIgnore];
};
Fix: PROC[rootFile, wDir: ROPE, entriesToIgnore: LIST OF REF ANY] = {
toBeIgnored: LIST OF ATOM;
FOR ei: LIST OF REF ANY ← entriesToIgnore, ei.rest UNTIL ei = NIL DO
ax: ATOMNARROW[ei.first];
toBeIgnored ← CONS[ax, toBeIgnored];
ENDLOOP;
FixWalnutLog[rootFile, wDir, FALSE, toBeIgnored];
};
FixWalnutLog: PUBLIC PROC[
 rootFile, wDir: ROPE, remoteOrphans: BOOLFALSE, toBeIgnored: LIST OF ATOMNIL] = {
badBitsStrm: STREAM;
serverAndDir: ROPE;
BEGIN
previousAt, at: INT ← -1;
ident: ATOM;
logLength, newLen: INT;
expungeID: INT;
num, numSinceFlush: INT ← 0;
bytesBetweenFlushes: INT = FS.BytesForPages[100];
pagesNeeded: INT;
msgID: REF TEXT;
startFix: ROPE = "\t\tFix Log, started at %g\n";
fixWho: ROPE = "\tFixing the rootfile: %g\n";
DumpBadBits: PROC[startPos, endPos: INT] = {
numBad: INT ← endPos - startPos;
IF badBitsStrm = NIL THEN {
orphan: ROPE ← IF remoteOrphans THEN
serverAndDir.Concat["WalnutRescue.orphanBytesLog"]
ELSE FS.ExpandName["WalnutRescue.orphanBytesLog", wDir].fullFName;
out.PutF["\n*** Writing the orphan bytes on %g\n", IO.rope[orphan] ];
tsLogStrm.PutF["\n*** Writing the orphan bytes on %g%g\n",
  IO.rope[wDir], IO.rope[orphan] ];
badBitsStrm ←
IF remoteOrphans THEN
AlpineFS.StreamOpen[name: orphan, accessOptions: $create, keep: 2]
ELSE FS.StreamOpen[fileName: orphan, accessOptions: $create, keep: 10];
badBitsStrm.SetIndex[0];
badBitsStrm.PutF["\nOrphaned bits from fixing %g, started at %g\n\n",
IO.rope[rootFile], IO.time[]];
badBitsStrm.Flush[];
};
badBitsStrm.PutRope[starsRope];
badBitsStrm.PutF["\n\t%g orphan bytes, starting at pos: %g\n",
  IO.int[numBad], IO.int[startPos] ];
WalnutLogExpunge.SetIndex[startPos];
WalnutLogExpunge.CopyBytes[badBitsStrm, numBad];
badBitsStrm.Flush[];
};
[out, tsLogStrm] ← TSStream["Walnut Rescue", "WalnutRescue.log", wDir];
out.PutRope[starsRope]; tsLogStrm.PutRope[starsRope];
out.PutF[startFix, IO.time[] ]; tsLogStrm.PutF[startFix, IO.time[] ];
IF rootFile = NIL THEN {
rootFile ← UserProfile.Token[key: "Walnut.WalnutRootFile", default: ""];
IF rootFile.Length[] = 0 THEN {
out.PutRope[noRootFile]; tsLogStrm.PutRope[noRootFile];
tsLogStrm.Close[];
RETURN;
};
};
out.PutF[fixWho, IO.rope[rootFile] ]; tsLogStrm.PutF[fixWho, IO.rope[rootFile] ];
IF remoteOrphans THEN serverAndDir ← ParseRootName[rootFile];
IF toBeIgnored = NIL THEN {
r1: ROPE = " No entries except invalid ones will be ignored\n";
out.PutRope[r1]; tsLogStrm.PutRope[r1];
}
ELSE {
r2: ROPE = "\n Ignoring the following entries: ";
r3: ROPE = " %g,";
out.PutRope[r2]; tsLogStrm.PutRope[r2];
FOR ignore: LIST OF ATOM ← toBeIgnored, ignore.rest UNTIL ignore = NIL DO
out.PutF[r3, IO.atom[ignore.first]]; tsLogStrm.PutF[r3, IO.atom[ignore.first]];
ENDLOOP;
out.PutChar['\n]; tsLogStrm.PutChar['\n];
};
WalnutRoot.RegisterStatsProc[Report];
BEGIN ENABLE {
WalnutDefs.Error => {
ec: ROPE = "\n *** WalnutDefs Error: code: %g, info: %g @ %g";
qt: ROPE = "\n quitting ...\n";
out.PutF[ec, IO.atom[code], IO.rope[explanation], IO.time[] ];
tsLogStrm.PutF[ec, IO.atom[code], IO.rope[explanation], IO.time[] ];
out.PutRope[qt]; tsLogStrm.PutRope[qt];
GOTO error;
};
UNWIND => GOTO error;
};
IF WalnutRoot.Open[rootFile].isReadOnly THEN {
out.PutF["\n %g is readOnly - can't Fix\n", IO.rope[rootFile]];
tsLogStrm.PutF["\n %g is readOnly - can't Fix\n", IO.rope[rootFile]];
WalnutRoot.UnregisterStatsProc[Report];
WalnutRoot.Shutdown[];
RETURN;
};
[] ← WalnutRoot.StartTransaction[];
[] ← WalnutLog.OpenLogStreams[];
pagesNeeded ← FS.PagesForBytes[logLength ← WalnutLog.LogLength[]];
WalnutLog.ReturnCurrentLogStreams[];
expungeID ← WalnutLogExpunge.StartExpunge[pagesNeeded];
[] ← WalnutLogExpunge.SetPosition[0];
[ident, msgID, at] ← WalnutLogExpunge.PeekEntry[];
DO
bytesThisCopy, newPos: INT;
doSkip: BOOLFALSE;
nextIdent: ATOM;
nextMsgID: REF TEXT;
nextAt: INT;
thisStatus: WalnutLogExpunge.EntryStatus;
see if this is a well-formed entry
thisStatus ← WalnutLogExpunge.ExamineThisEntry[];  -- advances log if valid entry
IF thisStatus = EndOfStream THEN {
eosR: ROPE = "\n***** EndOfStream encountered at pos %g, last entry was at pos %g\n";
badE: ROPE = "\t\tBad entry had ident %g, end was %g bytes later\n";
out.PutF[eosR, IO.int[logLength], IO.int[at]];
tsLogStrm.PutF[eosR, IO.int[logLength], IO.int[at]];
out.PutF[badE, IO.atom[ident], IO.int[logLength - at] ];
tsLogStrm.PutF[badE, IO.atom[ident], IO.int[logLength - at] ];
DumpBadBits[at, logLength];
EXIT
};
[nextIdent, nextMsgID, nextAt] ← WalnutLogExpunge.PeekEntry[];
IF nextIdent = NIL THEN {
curPos: INT ← WalnutLogExpunge.GetIndex[];
IF curPos = logLength THEN EXIT
ELSE {  -- we have a problem
out.PutF[noEntry, IO.int[curPos], IO.int[at] ];
tsLogStrm.PutF[noEntry, IO.int[curPos], IO.int[at] ];
nextAt ← at + WalnutLogExpunge.SetPosition[at+1] + 1;
IF nextAt = at THEN {
noMore: ROPE = "No more entries found\n";
out.PutRope[noMore]; tsLogStrm.PutRope[noMore];
DumpBadBits[at, logLength];
EXIT
};
IF nextAt <= curPos THEN {  -- previous entry is not ok
out.PutF[bad,
IO.atom[ident], IO.int[curPos - at], IO.int[nextAt - at] ];
tsLogStrm.PutF[bad,
IO.atom[ident], IO.int[curPos - at], IO.int[nextAt - at] ];
IF msgID # NIL THEN {
id: ROPE = "\t The msgID was: \"%g\"\n";
out.PutF[id, IO.text[msgID] ]; tsLogStrm.PutF[id, IO.text[msgID] ];
};
out.PutF[nextValid, IO.int[nextAt] ]; tsLogStrm.PutF[nextValid, IO.int[nextAt] ];
DumpBadBits[at, nextAt];
WalnutLogExpunge.SetIndex[nextAt];
[ident, msgID, at] ← WalnutLogExpunge.PeekEntry[];
LOOP;
};
};
};
back up to the good entry
WalnutLogExpunge.SetIndex[at];
IF at = previousAt THEN {  -- probably transAbort
r4: ROPE = "\n At pos %g a second time\n";
[] ← WalnutLogExpunge.SkipEntry[];
out.PutF[r4, IO.int[at]]; tsLogStrm.PutF[r4, IO.int[at]];
ident ← nextIdent;
msgID ← nextMsgID;
at ← nextAt;
LOOP;
};
FOR ignore: LIST OF ATOM ← toBeIgnored, ignore.rest UNTIL ignore = NIL DO
IF ident = ignore.first THEN
{ doSkip ← TRUE; EXIT; };
ENDLOOP;
IF doSkip OR ident=$LogFileInfo OR ident=$ExpungeMsgs OR ~ValidIdent[ident, at, TRUE] THEN
[] ← WalnutLogExpunge.SkipEntry[]
ELSE {
[newPos, bytesThisCopy] ← WalnutLogExpunge.CopyEntry[];
IF ident = $CreateMsg THEN {
IF (num← num + 1) MOD 10 = 0 THEN
IF num MOD 100 = 0 THEN
{ out.PutF["(%g)", IO.int[num]]; tsLogStrm.PutF["(%g)", IO.int[num]] }
ELSE { out.PutChar['.]; tsLogStrm.PutChar['.] };
};
numSinceFlush ← numSinceFlush + bytesThisCopy;
IF numSinceFlush >= bytesBetweenFlushes THEN {
[] ← WalnutLogExpunge.GetExpungeProgress[];
WalnutRoot.CommitAndContinue[];
numSinceFlush ← 0;
};
};
IF nextIdent = NIL THEN { -- previous entry not followed by a valid entry; go find it
curPos: INT = WalnutLogExpunge.GetIndex[];
noEntry: ROPE = "\nNo entry found at pos %g; next valid entry was %g bytes later";
next: INT = curPos + WalnutLogExpunge.SetPosition[curPos];
IF next = -1 THEN {
noMore: ROPE = "No more entries found\n";
out.PutRope[noMore]; tsLogStrm.PutRope[noMore];
DumpBadBits[curPos, logLength];
EXIT
};
out.PutF[noEntry, IO.int[curPos], IO.int[next - curPos]];
tsLogStrm.PutF[noEntry, IO.int[curPos], IO.int[next - curPos]];
DumpBadBits[curPos, next];
WalnutLogExpunge.SetIndex[next];
[ident, msgID, at] ← WalnutLogExpunge.PeekEntry[];
LOOP;
};
ident ← nextIdent;
msgID ← nextMsgID;
at ← nextAt;
ENDLOOP;
[] ← WalnutLogExpunge.GetExpungeProgress[];
WalnutLogExpunge.EndExpunge[];
WalnutRoot.CommitAndContinue[];
[ , newLen] ← WalnutRoot.SwapLogs[expungeID];
WalnutRoot.UnregisterStatsProc[Report];
WalnutRoot.Shutdown[];
BEGIN
old: ROPE = "\n The old log was %g bytes, the new log is %g bytes";
had: ROPE = "\n The log file contains %g messages\n";
out.PutF[old, IO.int[logLength], IO.int[newLen]];
tsLogStrm.PutF[old, IO.int[logLength], IO.int[newLen]];
out.PutF[had, IO.int[num]]; tsLogStrm.PutF[had, IO.int[num]];
tsLogStrm.Close[];
IF badBitsStrm # NIL THEN badBitsStrm.Close[];
END;
EXITS
error => {
WalnutLogExpunge.EndExpunge[];
WalnutRoot.UnregisterStatsProc[Report];
WalnutLogExpunge.Shutdown[];
WalnutLog.ShutdownLog[];
WalnutRoot.Shutdown[];
IF badBitsStrm # NIL THEN badBitsStrm.Close[];
tsLogStrm.Close[];
};
END;
END;
};
ValidIdent: PROC[ident: ATOM, at: INT, doReport: BOOL] RETURNS[valid: BOOL] = {
this changes whenever WalnutKernelDefs.LogEntry changes
SELECT ident FROM
$LogFileInfo => RETURN[TRUE];
$CreateMsg => RETURN[TRUE];
$ExpungeMsgs => RETURN[TRUE];
$WriteExpungeLog => RETURN[TRUE];
$CreateMsgSet => RETURN[TRUE];
$DestroyMsgSet => RETURN[TRUE];
$EmptyMsgSet => RETURN[TRUE];
$HasBeenRead => RETURN[TRUE];
$AddMsg => RETURN[TRUE];
$RemoveMsg => RETURN[TRUE];
$MoveMsg => RETURN[TRUE];
$RecordNewMailInfo => RETURN[TRUE];
$StartCopyNewMail => RETURN[TRUE];
$EndCopyNewMailInfo => RETURN[TRUE];
$AcceptNewMail => RETURN[TRUE];
$StartReadArchiveFile => RETURN[TRUE];
$EndReadArchiveFile => RETURN[TRUE];
$StartCopyReadArchive => RETURN[TRUE];
$EndCopyReadArchiveInfo => RETURN[TRUE];
ENDCASE => NULL;
IF doReport THEN {
inv: ROPE = "\n~~~~ Invalid Entry with identifier $%g at log pos %g\n";
out.PutF[inv, IO.atom[ident], IO.int[at] ];
tsLogStrm.PutF[inv, IO.atom[ident], IO.int[at] ];
};
RETURN[FALSE]
};
DoScan: PROC[strm: STREAM, startPos, logLength: INT] = {
ident: ATOM;
msgID: REF TEXT;
length, previousAt: INT;
validTable: RefTab.Ref ← RefTab.Create[];
invalidTable: RefTab.Ref ← RefTab.Create[];
table: RefTab.Ref;
found: BOOL;
val: RefTab.Val;
validCount, invalidCount, count: INT ← 0;
at: INT ← startPos;
ForPrinting: RefTab.EachPairAction = {
ident: ATOMNARROW[key];
this: Xyz ← NARROW[val];
pr: ROPE = "Ident: %g, num: %g, bytes: %g\n";
out.PutF[pr, IO.atom[ident], IO.int[this.num], IO.int[this.bytes] ];
tsLogStrm.PutF[pr, IO.atom[ident], IO.int[this.num], IO.int[this.bytes] ];
RETURN[FALSE];  -- don't quit
};
strm.SetIndex[startPos];
IF startPos # 0 THEN at ← WalnutStream.FindNextEntry[strm];
previousAt ← at;
DO
this: Xyz;
previousIdent: ATOM ← ident;
previousMsgID: REF TEXT ← msgID;
[ident, msgID, length] ← WalnutStream.PeekEntry[strm];
IF ident = NIL THEN {
curPos: INT ← strm.GetIndex[];
IF curPos = logLength THEN EXIT;
out.PutF[noEntry, IO.int[curPos], IO.int[previousAt] ];
tsLogStrm.PutF[noEntry, IO.int[curPos], IO.int[previousAt] ];
strm.SetIndex[previousAt+1];
at ← WalnutStream.FindNextEntry[strm];
IF at = -1 THEN {
noMore: ROPE = "No more entries found\n";
out.PutRope[noMore]; tsLogStrm.PutRope[noMore];
EXIT
};
out.PutF[bad,
IO.atom[previousIdent], IO.int[curPos - previousAt], IO.int[at - previousAt] ];
tsLogStrm.PutF[bad,
IO.atom[previousIdent], IO.int[curPos - previousAt], IO.int[at - previousAt] ];
IF previousMsgID # NIL THEN {
id: ROPE = "\t The msgID was: \"%g\"\n";
out.PutF[id, IO.text[previousMsgID] ]; tsLogStrm.PutF[id, IO.text[previousMsgID] ];
};
out.PutF[nextValid, IO.int[at] ]; tsLogStrm.PutF[nextValid, IO.int[at] ];
LOOP;
};
previousAt ← at;
strm.SetIndex[at ← at + length ! IO.EndOfStream => {
curPos: INT = strm.GetLength[];
lastLen: INT = curPos - previousAt;
eosR: ROPE =
"\n\n **** EndOfStream encountered at pos %g, when trying for pos %g\n";
endS: ROPE = "Bad entry had ident %g, length %g, end was %g bytes later\n";
out.PutF[eosR, IO.int[curPos], IO.int[at]];
tsLogStrm.PutF[eosR, IO.int[curPos], IO.int[at]];
out.PutF[endS, IO.atom[ident], IO.int[length], IO.int[lastLen] ];
tsLogStrm.PutF[endS, IO.atom[ident], IO.int[length], IO.int[lastLen] ];
IF msgID # NIL THEN {
id: ROPE = "\t The msgID was: \"%g\"\n";
out.PutF[id, IO.text[msgID] ]; tsLogStrm.PutF[id, IO.text[msgID] ];
};
strm.SetIndex[curPos];
CONTINUE;
} ];
IF ValidIdent[ident, 0, FALSE] THEN {
table ← validTable;
validCount ← validCount + 1;
}
ELSE {
table ← invalidTable;
invalidCount ← invalidCount + 1;
};
[found, val] ← RefTab.Fetch[table, ident];
IF found THEN {
this ← NARROW[val];
this.num ← this.num + 1;
this.bytes ← this.bytes + length;
}
ELSE this ← NEW[ValueObject ← [1, length]];
[] ← RefTab.Store[table, ident, this];
IF (count ← count + 1) MOD 10 = 0 THEN {
IF count MOD 100 = 0 THEN
{ out.PutF["(%g)", IO.int[count]]; tsLogStrm.PutF["(%g)", IO.int[count]] }
ELSE { out.PutChar['~]; tsLogStrm.PutChar['~] };
};
ENDLOOP;
IF count = 0 THEN {
cnt: ROPE = "\n\t\tThe log (%g bytes) contained no entries\n";
out.PutF[cnt, IO.int[logLength] ]; tsLogStrm.PutF[cnt, IO.int[logLength] ];
RETURN
};
BEGIN
msg: ROPE = "\n\n\tThe log (%g bytes) contained the following entries:\n";
val: ROPE = "\n\tThe table of valid entries (%g) is:\n";
inVal: ROPE = "\n\tThe table of invalid entries (%g) is:\n";
out.PutF[msg, IO.int[logLength]]; tsLogStrm.PutF[msg, IO.int[logLength]];
IF validCount # 0 THEN {
out.PutF[val, IO.int[validCount]]; tsLogStrm.PutF[val, IO.int[validCount]];
[] ← RefTab.Pairs[validTable, ForPrinting];
};
IF invalidCount # 0 THEN {
out.PutF[inVal, IO.int[invalidCount]]; tsLogStrm.PutF[inVal, IO.int[invalidCount]];
[] ← RefTab.Pairs[invalidTable, ForPrinting];
};
END;
};
Report: PROC[msg: ROPE] = {
out.PutF["\n %g @ %g\n", IO.rope[msg], IO.time[]];
tsLogStrm.PutF["\n %g @ %g\n", IO.rope[msg], IO.time[]];
};
CmdScan: Commander.CommandProc = {
wDir: ROPE = CommandTool.CurrentWorkingDirectory[];
TRUSTED { Process.Detach[FORK SetupScan[cmd.commandLine, wDir] ] };
};
CmdFix: Commander.CommandProc = {
wDir: ROPE = CommandTool.CurrentWorkingDirectory[];
TRUSTED { Process.Detach[FORK SetupFix[cmd.commandLine, wDir] ] };
};
ParseRootName: PROC[rootFile: ROPE] RETURNS[serverAndDir: ROPE] = {
cp: FS.ComponentPositions;
full: ROPE;
[full, cp, ] ← FS.ExpandName[rootFile];
RETURN[full.Substr[0, cp.base.start]];
};
Commander.Register["ScanWalnutLog", CmdScan,
"Summarizes a Walnut log; syntax is \"ScanWalnutLog logFile\""];
Commander.Register["FixWalnutLog", CmdFix,
"Remove entries from a Walnut log; syntax is \"FixWalnutLog rootFile {AtomNames of entries to remove}\""];
END.