<<>> <> <> <> <> DIRECTORY <> BasicTime USING [GMT, Now], Commander USING [CommandProc, Handle, Register], CommanderOps USING [NextArgument], Convert USING [Error, IntFromRope], FS, 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 [CheckReportProc, Error, WalnutOpsHandle], WalnutKernelDefs USING [LogEntry], WalnutOps USING [CreateWalnutOpsHandle, GetHandleForRootfile], WalnutLog USING [LogLength, OpenLogStreams, ForgetLogStreams, ShutdownLog], WalnutLogExpunge -- using almost everything -- , WalnutRegistry USING [CurrentWalnutState], WalnutRoot USING [CommitAndContinue, GetStreamsForExpunge, Open, RegisterStatsProc, Shutdown, StartTransaction, SwapLogs, UnregisterStatsProc], WalnutStream USING [FindNextEntry, Open, PeekEntry, ReadEntry, WriteEntry]; WalnutRescueImpl: CEDAR PROGRAM IMPORTS -- Atom, -- BasicTime, Commander, CommanderOps, Convert, FS, IO, Process, RefTab, Rope, UserProfile, ViewerIO, ViewerOps, WalnutDefs, WalnutOps, WalnutLog, WalnutLogExpunge, WalnutRegistry, WalnutRoot, WalnutStream EXPORTS WalnutDefs = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; WalnutOpsHandle: TYPE = WalnutDefs.WalnutOpsHandle; ExpungeHandle: TYPE = WalnutLogExpunge.ExpungeHandle; ExpungeHandleRec: PUBLIC TYPE = WalnutLogExpunge.ExpungeHandleRec; tsOut, tsOutLogStrm: STREAM ¬ NIL; debugging: BOOL ¬ FALSE; Xyz: TYPE = REF ValueObject; ValueObject: TYPE = RECORD[num, bytes: INT ¬ 0]; starsRope: PUBLIC 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"; eachOrphan: ROPE = "\n\t%g orphan bytes, starting at pos: %g\n\n"; TSStream: PUBLIC PROC[name, tsLogFile, wDir: ROPE] RETURNS [out, tsLogStrm, in: STREAM] = { this: ROPE = IF name # NIL THEN name ELSE "Walnut Rescue"; logFile: ROPE = IF tsLogFile # NIL THEN tsLogFile ELSE "/tmp/WalnutRescue.log"; v: ViewerClasses.Viewer ¬ ViewerOps.FindViewer[this]; [in, out] ¬ ViewerIO.CreateViewerStreams[this, v, NIL, FALSE]; tsLogStrm ¬ FS.StreamOpen[fileName: logFile, accessOptions: $create, wDir: wDir]; IF v # NIL THEN IF v.iconic THEN ViewerOps.OpenIcon[v]; tsOut ¬ out; tsOutLogStrm ¬ tsLogStrm; }; SetupScan: PROC[cmd: Commander.Handle, wDir: ROPE] = { logFile: ROPE = CommanderOps.NextArgument[cmd]; startPosRope: ROPE = CommanderOps.NextArgument[cmd]; startPos: INT ¬ 0; IF startPosRope # NIL THEN startPos ¬ Convert.IntFromRope[startPosRope ! Convert.Error => CONTINUE]; TRUSTED { Process.Detach[FORK 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"; now: BasicTime.GMT = BasicTime.Now[]; [tsOut, tsOutLogStrm, ] ¬ TSStream[NIL, NIL, NIL]; Report[NIL, starsRope]; Report[NIL, scanStart, [time[now]] ]; IF logFile = NIL THEN { [logFile, strm] ¬ TryUsingRootFileName[]; IF logFile = NIL THEN { Report[NIL, noInputFile]; tsOutLogStrm.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 { Report[NIL, "\n***Could not open %g\n", [rope[fullLogName]] ]; tsOutLogStrm.Close[]; RETURN }; IF (logLength ¬ strm.GetLength[]) = 0 THEN { empty: ROPE = "The log file %g is empty\n"; Report[NIL, empty, [rope[fullLogName]] ]; strm.Close[ ! IO.Error, FS.Error => CONTINUE]; tsOutLogStrm.Close[]; RETURN }; Report[NIL, scan, [rope[fullLogName]], [integer[startPos]] ]; BEGIN ENABLE { IO.Error => { ioErr: ROPE = "\n\n\t\t*** IO.Error: %g, @ %g, logPos %g - quitting\n"; Report[NIL, ioErr, [rope[msg]], [time[BasicTime.Now[]]], [integer[strm.GetIndex[]]] ]; GOTO error; }; UNWIND => NULL; }; tsOut.PutRope["(0)"]; DoScan[strm, startPos, logLength]; EXITS error => NULL; END; strm.Close[ ! IO.Error, FS.Error => CONTINUE]; tsOutLogStrm.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 }; FixWalnutLog: PROC[rootFile, wDir: ROPE, toBeIgnored: LIST OF ATOM] = { opsH: WalnutOpsHandle; badBitsStrm: STREAM; serverAndDir: ROPE; BEGIN previousAt, at: INT ¬ -1; ident: ATOM; logLength, newLen: INT; expungeID: INT; num, numSinceFlush: INT ¬ 0; bytesBetweenFlushes: INT = 50 * 8096; -- guess msgID: ROPE; 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 ¬ Rope.Concat[wDir, "WalnutRescue.orphanBytesLog"]; Report[NIL, eachOrphan, [rope[orphan]], [integer[startPos]] ]; badBitsStrm ¬ FS.StreamOpen[fileName: orphan, accessOptions: $create]; badBitsStrm.SetIndex[0]; badBitsStrm.PutF["\nOrphaned bits from fixing %g, started at %g\n\n", [rope[rootFile]], [time[BasicTime.Now[]]] ]; badBitsStrm.Flush[]; }; badBitsStrm.PutRope[starsRope]; badBitsStrm.PutF["\n\t%g orphan bytes, starting at pos: %g\n", [integer[numBad]], [integer[startPos]] ]; WalnutLogExpunge.SetIndex[opsH, startPos]; WalnutLogExpunge.CopyBytes[opsH, badBitsStrm, numBad]; badBitsStrm.Flush[]; }; [tsOut, tsOutLogStrm, ] ¬ TSStream[NIL, NIL, NIL]; Report[NIL, starsRope]; Report[NIL, startFix, [time[BasicTime.Now[]]] ]; IF rootFile = NIL THEN { rootFile ¬ UserProfile.Token[key: "Walnut.WalnutRootFile", default: ""]; IF rootFile.Length[] = 0 THEN { Report[NIL, noRootFile]; tsOutLogStrm.Close[]; RETURN; }; }; Report[NIL, fixWho, [rope[rootFile]] ]; IF toBeIgnored = NIL THEN Report[NIL, "\n No entries except invalid ones will be deleted\n"] ELSE { Report[NIL, "\n Deleting the following entries: "]; FOR ignore: LIST OF ATOM ¬ toBeIgnored, ignore.rest UNTIL ignore = NIL DO Report[NIL, " %g,", [atom[ignore.first]]]; ENDLOOP; Report[NIL, "\n"]; }; opsH ¬ WalnutOps.GetHandleForRootfile[rootFile]; IF opsH = NIL THEN opsH ¬ WalnutOps.CreateWalnutOpsHandle[rootFile]; WalnutRoot.RegisterStatsProc[opsH, Report]; BEGIN ENABLE { WalnutDefs.Error => { ec: ROPE = "\n *** WalnutDefs Error: code: %g, info: %g @ %g\n quitting ...\n"; Report[opsH, ec, [atom[code]], [rope[explanation]], [time[BasicTime.Now[]]] ]; GOTO error; }; UNWIND => GOTO error; }; [] ¬ WalnutRoot.Open[opsH, FALSE]; IF opsH.readOnly THEN { Report[opsH, "\n %g is readOnly - can't Fix\n", [rope[rootFile]]]; WalnutRoot.UnregisterStatsProc[opsH, Report]; WalnutRoot.Shutdown[opsH]; RETURN; }; [] ¬ WalnutRoot.StartTransaction[opsH, FALSE]; [] ¬ WalnutLog.OpenLogStreams[opsH]; logLength _ WalnutLog.LogLength[opsH]; WalnutLog.ForgetLogStreams[opsH]; expungeID ¬ WalnutLogExpunge.StartExpunge[opsH, 0]; [] ¬ WalnutLogExpunge.SetPosition[opsH, 0]; [ident, msgID, at] ¬ WalnutLogExpunge.PeekEntry[opsH]; DO bytesThisCopy, newPos: INT; doSkip: BOOL ¬ FALSE; nextIdent: ATOM; nextMsgID: ROPE; nextAt: INT; thisStatus: WalnutLogExpunge.EntryStatus; atEnd: BOOL ¬ FALSE; <> thisStatus ¬ WalnutLogExpunge.ExamineThisEntry[opsH]; -- 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"; IF at = logLength THEN EXIT; -- at the end so ok Report[NIL, eosR, [integer[logLength]], [integer[at]]]; Report[NIL, badE, [atom[ident]], [integer[logLength - at]] ]; DumpBadBits[at, logLength]; EXIT }; [nextIdent, nextMsgID, nextAt] ¬ WalnutLogExpunge.PeekEntry[opsH ! WalnutDefs.Error => { nextIdent ¬ NIL; CONTINUE } ]; IF nextIdent = NIL THEN { curPos: INT ¬ WalnutLogExpunge.GetIndex[opsH]; IF curPos = logLength THEN atEnd ¬ TRUE -- need to dump the last entry ELSE { -- we have a problem Report[opsH, noEntry, [integer[curPos]], [integer[at]] ]; nextAt ¬ at + WalnutLogExpunge.SetPosition[opsH, at+1] + 1; IF nextAt = at THEN { Report[opsH, "No more entries found\n"]; DumpBadBits[at, logLength]; EXIT }; IF nextAt <= curPos THEN { -- previous entry is not ok Report[opsH, bad, [atom[ident]], [integer[curPos - at]], [integer[nextAt - at]] ]; IF msgID # NIL THEN { id: ROPE = "\t The msgID was: \"%g\"\n"; Report[opsH, id, [rope[msgID]] ]; }; Report[opsH, nextValid, [integer[nextAt]] ]; DumpBadBits[at, nextAt]; WalnutLogExpunge.SetIndex[opsH, nextAt]; [ident, msgID, at] ¬ WalnutLogExpunge.PeekEntry[opsH]; LOOP; }; }; }; <<>> <> WalnutLogExpunge.SetIndex[opsH, at]; IF at = previousAt THEN { -- probably transAbort [] ¬ WalnutLogExpunge.SkipEntry[opsH]; Report[opsH, "\n At pos %g a second time\n", [integer[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 ~ValidIdent[ident, at, TRUE] THEN [] ¬ WalnutLogExpunge.SkipEntry[opsH] ELSE SELECT ident FROM $LogFileInfo, $ExpungeMsgs, $WriteExpungeLog => [] ¬ WalnutLogExpunge.SkipEntry[opsH]; $DestroyMsgSet => { -- be very careful now: INT = WalnutLogExpunge.GetIndex[opsH]; le: WalnutKernelDefs.LogEntry ¬ WalnutStream.ReadEntry[opsH.expungeHandle.rStrm, TRUE].le; Report[NIL, "\n Re-writing DestroyMsgSet entry at %g\n", [integer[now]] ]; [] ¬ WalnutStream.WriteEntry[opsH.expungeHandle.eStrm, le]; -- write correctly WalnutLogExpunge.SetIndex[opsH, now]; [] ¬ WalnutLogExpunge.SkipEntry[opsH]; -- skip bad one }; $CreateMsg => { [newPos, bytesThisCopy] ¬ WalnutLogExpunge.CopyEntry[opsH]; num ¬ IncrementCount[opsH, num]; }; ENDCASE => [newPos, bytesThisCopy] ¬ WalnutLogExpunge.CopyEntry[opsH]; numSinceFlush ¬ numSinceFlush + bytesThisCopy; IF numSinceFlush >= bytesBetweenFlushes THEN { [] ¬ WalnutLogExpunge.GetExpungeProgress[opsH]; WalnutRoot.CommitAndContinue[opsH]; numSinceFlush ¬ 0; }; IF atEnd THEN EXIT; IF nextIdent = NIL THEN { -- previous entry not followed by a valid entry; go find it curPos: INT = WalnutLogExpunge.GetIndex[opsH]; noEntry: ROPE = "\nNo entry found at pos %g; next valid entry was %g bytes later"; next: INT = curPos + WalnutLogExpunge.SetPosition[opsH, curPos]; IF next = -1 THEN { Report[opsH, "No more entries found\n"]; DumpBadBits[curPos, logLength]; EXIT }; Report[opsH, noEntry, [integer[curPos]], [integer[next - curPos]]]; DumpBadBits[curPos, next]; WalnutLogExpunge.SetIndex[opsH, next]; [ident, msgID, at] ¬ WalnutLogExpunge.PeekEntry[opsH]; LOOP; }; ident ¬ nextIdent; msgID ¬ nextMsgID; at ¬ nextAt; ENDLOOP; [] ¬ WalnutLogExpunge.GetExpungeProgress[opsH]; WalnutLogExpunge.EndExpunge[opsH]; WalnutRoot.CommitAndContinue[opsH]; [] ¬ WalnutRoot.GetStreamsForExpunge[opsH: opsH, starting: FALSE, pagesWanted: -1]; newLen ¬ WalnutRoot.SwapLogs[opsH, expungeID, BasicTime.Now[]]; WalnutRoot.UnregisterStatsProc[opsH, Report]; WalnutRoot.Shutdown[opsH]; 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"; Report[opsH, old, [integer[logLength]], [integer[newLen]]]; Report[opsH, had, [integer[num]]]; tsOutLogStrm.Close[]; IF badBitsStrm # NIL THEN badBitsStrm.Close[]; END; EXITS error => { WalnutLogExpunge.EndExpunge[opsH]; WalnutRoot.UnregisterStatsProc[opsH, Report]; WalnutLog.ShutdownLog[opsH]; WalnutRoot.Shutdown[opsH]; IF badBitsStrm # NIL THEN badBitsStrm.Close[]; tsOutLogStrm.Close[]; }; END; END; }; orphanRope: ROPE = "\n*** Writing the orphan bytes on %g\n"; orphanStart: ROPE = "\nOrphaned bits from fixing %g, started at %g\n\n"; ValidIdent: PROC[ident: ATOM, at: INT, doReport: BOOL] RETURNS[valid: BOOL] = { <> SELECT ident FROM $LogFileInfo => RETURN[TRUE]; $CreateMsg => RETURN[TRUE]; $ExpungeMsgs => RETURN[TRUE]; $WriteExpungeLog => RETURN[TRUE]; $CreateMsgSet => RETURN[TRUE]; $DestroyMsgSet => RETURN[TRUE]; $EndOfLog => 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"; Report[NIL, inv, [atom[ident]], [integer[at]] ]; }; RETURN[FALSE] }; DoScan: PROC[strm: STREAM, startPos, logLength: INT] = { ident: ATOM; msgID: ROPE; 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; oneKBytes, twoKBytes, onePointTwoKBytes: INT ¬ 0; oneKNum, onePointTwoKNum, twoKNum: INT ¬ 0; logInfoSeqNo: INT ¬ 0; ForPrinting: RefTab.EachPairAction = { ident: ATOM ¬ NARROW[key]; this: Xyz ¬ NARROW[val]; pr: ROPE = "Ident: %g, num: %g, bytes: %g\n"; Report[NIL, pr, [atom[ident]], [integer[this.num]], [integer[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: ROPE ¬ msgID; [ident, msgID, length] ¬ WalnutStream.PeekEntry[strm, TRUE]; IF ident = $EndOfLog THEN EXIT; IF ident = NIL THEN { curPos: INT ¬ strm.GetIndex[]; IF curPos = logLength THEN EXIT; Report[NIL, noEntry, [integer[curPos]], [integer[previousAt]] ]; strm.SetIndex[previousAt+1]; at ¬ WalnutStream.FindNextEntry[strm]; IF at = -1 THEN { Report[NIL, "No more entries found\n"]; EXIT }; Report[NIL, bad, [atom[previousIdent]], [integer[curPos - previousAt]], [integer[at - previousAt]] ]; IF previousMsgID # NIL THEN Report[NIL, "\t The msgID was: \"%g\"\n", [rope[previousMsgID]] ]; Report[NIL, nextValid, [integer[at]] ]; LOOP; }; previousAt ¬ at; SELECT ident FROM $LogFileInfo => { wle: WalnutKernelDefs.LogEntry; wle ¬ WalnutStream.ReadEntry[strm, FALSE].le; -- waiting at the entry TRUSTED { WITH le: wle SELECT FROM LogFileInfo => logInfoSeqNo ¬ le.logSeqNo; ENDCASE => NULL; }; }; $CreateMsg => { IF length > 1000 THEN { oneKBytes ¬ oneKBytes + length; oneKNum ¬ oneKNum + 1; }; IF length > 1200 THEN { onePointTwoKBytes ¬ onePointTwoKBytes + length; onePointTwoKNum ¬ onePointTwoKNum + 1; }; IF length > 2000 THEN { twoKBytes ¬ twoKBytes + length; twoKNum ¬ twoKNum + 1; }; }; ENDCASE => NULL; 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"; Report[NIL, eosR, [integer[curPos]], [integer[at]]]; Report[NIL, endS, [atom[ident]], [integer[length]], [integer[lastLen]] ]; IF msgID # NIL THEN Report[NIL, "\t The msgID was: \"%g\"\n", [rope[msgID]] ]; strm.SetIndex[curPos]; CONTINUE; } ]; IF ValidIdent[ident, at, 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]; count ¬ IncrementCount[NIL, count]; ENDLOOP; IF count = 0 THEN { Report[NIL, "\n\t\tThe log (%g bytes) contained no entries\n", [integer[logLength]] ]; RETURN }; BEGIN Report[NIL, "\n"]; IF logInfoSeqNo = -1 THEN Report[NIL, "\n*****You'll have to do a FixWalnutLog and Scavenge - the log sequence number is incorrect\n"]; Report[NIL, "\n\tThe log (%g bytes) contained the following entries:\n", [integer[logLength]]]; IF validCount # 0 THEN { Report[NIL, "\n\tThe table of valid entries (%g) is:\n", [integer[validCount]]]; [] ¬ RefTab.Pairs[validTable, ForPrinting]; }; IF invalidCount # 0 THEN { Report[NIL, "\n\tThe table of invalid entries (%g) is:\n", [integer[invalidCount]]]; [] ¬ RefTab.Pairs[invalidTable, ForPrinting]; }; IF oneKBytes > 0 THEN tsOut.PutF["\n %g msgs (%g bytes) with more than 1000 chars\n", [integer[oneKNum]], [integer[oneKBytes]] ]; IF onePointTwoKBytes > 0 THEN tsOut.PutF[" %g msgs (%g bytes) with more than 1200 chars\n", [integer[onePointTwoKNum]], [integer[onePointTwoKBytes]] ]; IF twoKBytes > 0 THEN tsOut.PutF[" %g msgs (%g bytes) with more than 2000 chars\n", [integer[twoKNum]], [integer[twoKBytes]] ]; END; }; IncrementCount: PUBLIC PROC[opsH: WalnutOpsHandle, count: INT] RETURNS[new: INT] = { IF (new ¬ count+1) MOD 100 = 0 THEN IF new MOD 1000 = 0 THEN Report[NIL, " (%g)", [integer[new]] ] ELSE Report[NIL, "~"]; }; Report: PUBLIC WalnutDefs.CheckReportProc = { tsOut.PutF[format, v1, v2, v3]; tsOutLogStrm.PutF[format, v1, v2, v3]; }; CmdScan: Commander.CommandProc = { wDir: ROPE = FS.GetWDir[]; IF WalnutIsActive[cmd.out] THEN RETURN; SetupScan[cmd, wDir]; }; WalnutIsActive: PUBLIC PROC[out: STREAM] RETURNS[BOOL] = { IF WalnutRegistry.CurrentWalnutState[] = active THEN { out.PutRope["*** You must quit out of Walnut first\n"]; RETURN[TRUE]; }; RETURN[FALSE] }; <<* * * * * * * * * * * * * * *>> Commander.Register["WalnutScanLog", CmdScan, "Summarizes a Walnut log; syntax is \"ScanWalnutLog logFile {startpos}\""]; END. CmdFix: Commander.CommandProc = { wDir: ROPE = FS.GetWDir[]; rootFile: ROPE; entriesToIgnore, lastToIgnore: LIST OF ATOM; IF WalnutIsActive[cmd.out] THEN RETURN; rootFile ¬ CommanderOps.NextArgument[cmd]; DO this: ATOM; temp: ROPE = CommanderOps.NextArgument[cmd]; IF temp = NIL THEN EXIT; this ¬ Atom.MakeAtom[temp]; IF entriesToIgnore = NIL THEN entriesToIgnore ¬ lastToIgnore ¬ LIST[this] ELSE { lastToIgnore.rest ¬ LIST[this]; lastToIgnore ¬ lastToIgnore.rest; }; ENDLOOP; TRUSTED { Process.Detach[FORK FixWalnutLog[rootFile, wDir, entriesToIgnore] ] }; }; Commander.Register["FixWalnutLog", CmdFix, "Remove entries from a Walnut log; syntax is \"FixWalnutLog rootFile {AtomNames of entries to remove}\""];