<> <> <> DIRECTORY AlpineFS USING [StreamOpen], BasicTime USING [Now], Commander USING [CommandProc, Register], CommandTool USING [CurrentWorkingDirectory], FS USING [ComponentPositions, Error, ErrorDesc, BytesForPages, ErrorFromStream, 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 [AbortTransaction, CommitAndContinue, Open, RegisterStatsProc, Shutdown, StartTransaction, SwapLogs, UnregisterStatsProc], WalnutStream USING [FindNextEntry, Open, PeekEntry]; WalnutRescue: CEDAR PROGRAM IMPORTS AlpineFS, BasicTime, 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: STREAM _ NIL; debugging: BOOL _ FALSE; 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"; eachOrphan: ROPE = "\n\t%g orphan bytes, starting at pos: %g\n\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] ]; strm.Close[ ! IO.Error, FS.Error => CONTINUE]; tsLogStrm.Close[]; RETURN }; out.PutF[scan, IO.rope[fullLogName], IO.int[startPos] ]; tsLogStrm.PutF[scan, IO.rope[fullLogName], IO.int[startPos] ]; BEGIN ENABLE { IO.Error => { IOErr: ROPE = "\n\n\t\t*** IO.Error: %g, @ %g - quitting\n"; ed: FS.ErrorDesc; ed _ FS.ErrorFromStream[stream ! IO.Error, FS.Error => CONTINUE]; out.PutF[IOErr, IO.rope[ed.explanation], IO.time[]]; tsLogStrm.PutF[IOErr, IO.rope[ed.explanation], IO.time[]]; GOTO error; }; UNWIND => NULL; }; DoScan[strm, startPos, logLength]; EXITS error => NULL; END; strm.Close[ ! IO.Error, FS.Error => CONTINUE]; 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]; }; SetupQFix: PROC[commandLine, wDir: ROPE] = { clStream: STREAM _ FSRope.StreamFromRope[commandLine]; rootFile: ROPE; rootFile _ clStream.GetTokenRope[IO.IDProc ! IO.EndOfStream => CONTINUE].token; QuickFixWalnutLog[rootFile, wDir]; }; 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: ATOM _ NARROW[ei.first]; toBeIgnored _ CONS[ax, toBeIgnored]; ENDLOOP; FixWalnutLog[rootFile, wDir, FALSE, toBeIgnored]; }; FixWalnutLog: PROC[ rootFile, wDir: ROPE, remoteOrphans: BOOL _ FALSE, toBeIgnored: LIST OF ATOM _ NIL] = { 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[eachOrphan, IO.rope[orphan] ]; tsLogStrm.PutF[eachOrphan, 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: BOOL _ FALSE; nextIdent: ATOM; nextMsgID: REF TEXT; nextAt: INT; thisStatus: WalnutLogExpunge.EntryStatus; atEnd: BOOL _ FALSE; <> 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 atEnd _ TRUE -- need to dump the last entry 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; }; }; }; <<>> <> 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 ident = $WriteExpungeLog 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 atEnd THEN EXIT; 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, BasicTime.Now[]]; 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; }; orphanRope: ROPE = "\n*** Writing the orphan bytes on %g\n"; orphanStart: ROPE = "\nOrphaned bits from fixing %g, started at %g\n\n"; QuickFixWalnutLog: PROC[rootFile, wDir: ROPE] = { badBitsStrm: STREAM; BEGIN at: INT _ 0; logLength, endFirst: INT; expungeID, bytesLeftToCopy: INT; num, numSinceFlush: INT _ 0; pagesNeeded: INT; currentLogPos, expungeLogPos: INT _ 0; reTries: INT _ 2; startFix: ROPE = "\t\tQuickFix Log, started at %g\n"; fixWho: ROPE = "\tQuickFixing the rootfile: %g\n"; ident: ATOM; msgID: REF TEXT; starting: BOOL _ TRUE; DumpBadBits: PROC[startPos, endPos: INT] = { numBad: INT _ endPos - startPos; IF badBitsStrm = NIL THEN { orphan: ROPE _ FS.ExpandName["WalnutRescue.orphanBytesLog", wDir].fullFName; out.PutF[orphanRope, IO.rope[orphan] ]; tsLogStrm.PutF[orphanRope, IO.rope[orphan] ]; badBitsStrm _ FS.StreamOpen[fileName: orphan, accessOptions: $create, keep: 10]; badBitsStrm.SetIndex[0]; badBitsStrm.PutF[orphanStart, IO.rope[rootFile], IO.time[]]; badBitsStrm.Flush[]; }; badBitsStrm.PutRope[starsRope]; badBitsStrm.PutF[eachOrphan, 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] ]; WalnutRoot.RegisterStatsProc[Report]; IF WalnutRoot.Open[rootFile].isReadOnly THEN { readOnly: ROPE = "\n %g is readOnly - can't Fix\n"; out.PutF[readOnly, IO.rope[rootFile]]; tsLogStrm.PutF[readOnly, 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[]; IF ident # $LogFileInfo THEN { firstNot: ROPE = "\n First entry has ident %g - quitting\n"; out.PutF[firstNot, IO.atom[ident]]; tsLogStrm.PutF[firstNot, IO.atom[ident]]; WalnutLogExpunge.Shutdown[]; WalnutRoot.Shutdown[]; RETURN; }; [] _ WalnutLogExpunge.SkipEntry[]; endFirst _ WalnutLogExpunge.GetIndex[]; [] _ WalnutLogExpunge.SkipEntry[]; [ident, msgID, at] _ WalnutLogExpunge.PeekEntry[]; IF ident # NIL THEN { wrong: ROPE = "\nSecond entry is not NIL (has ident $%g) - quitting\n"; out.PutF[wrong, IO.atom[ident]]; tsLogStrm.PutF[wrong, IO.atom[ident]]; WalnutLogExpunge.Shutdown[]; WalnutRoot.Shutdown[]; RETURN; }; at _ endFirst + WalnutLogExpunge.SetPosition[endFirst]; IF at <= endFirst THEN { wrong: ROPE = "\n Entry 'after' %g is at %g - quitting\n"; out.PutF[wrong, IO.int[endFirst], IO.int[at]]; tsLogStrm.PutF[wrong, IO.int[endFirst], IO.int[at]]; WalnutLogExpunge.Shutdown[]; WalnutRoot.Shutdown[]; RETURN; }; DumpBadBits[endFirst, at]; WalnutLogExpunge.SetIndex[at]; -- be paranoid <<>> <<***********>> DO BEGIN ENABLE { WalnutDefs.Error => { ec: ROPE = "\n *** WalnutDefs Error: code: %g, info: %g @ %g"; qt: ROPE = "\n quitting ...\n"; IF code = $TransactionAbort THEN { tooMany: ROPE = "\n Too many transaction aborts with no progress\n"; IF (reTries _ reTries - 1) > 0 THEN GOTO reTry; out.PutRope[tooMany]; tsLogStrm.PutRope[tooMany]; }; 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; }; copy: ROPE = "\n Copying %g pages of log in 100 page chunks, starting at pos %g ...\n"; newLen: INT; bytesToCopyAtOneTime: INT = FS.BytesForPages[100]; old: ROPE = "\n The old log was %g bytes, the new log is %g bytes"; many: ROPE = "(%g)"; count: INT _ 1; bytesLeftToCopy _ logLength - at; IF starting THEN { out.PutF[copy, IO.int[FS.PagesForBytes[bytesLeftToCopy]], IO.int[at]]; tsLogStrm.PutF[copy, IO.int[FS.PagesForBytes[bytesLeftToCopy]], IO.int[at]]; starting _ FALSE; }; DO bytesToCopyThisTime: INT _ MIN[bytesLeftToCopy, bytesToCopyAtOneTime]; WalnutLogExpunge.CopyBytesToExpungeLog[bytesToCopyThisTime]; [currentLogPos, expungeLogPos] _ WalnutLogExpunge.GetExpungeProgress[]; WalnutRoot.CommitAndContinue[]; reTries _ 2; IF (count _ count + 1) MOD 10 = 0 THEN { out.PutF[many, IO.int[count*100]]; tsLogStrm.PutF[many, IO.int[count*100]] } ELSE { out.PutChar['#]; tsLogStrm.PutChar['#] }; bytesLeftToCopy _ bytesLeftToCopy - bytesToCopyThisTime; IF bytesLeftToCopy = 0 THEN EXIT; ENDLOOP; WalnutLogExpunge.EndExpunge[]; WalnutRoot.CommitAndContinue[]; newLen _ WalnutRoot.SwapLogs[expungeID, BasicTime.Now[]]; WalnutRoot.UnregisterStatsProc[Report]; WalnutRoot.Shutdown[]; out.PutF[old, IO.int[logLength], IO.int[newLen]]; tsLogStrm.PutF[old, IO.int[logLength], IO.int[newLen]]; tsLogStrm.Close[]; badBitsStrm.Close[]; RETURN; EXITS error => { WalnutLogExpunge.EndExpunge[]; WalnutRoot.UnregisterStatsProc[Report]; WalnutLogExpunge.Shutdown[]; WalnutLog.ShutdownLog[]; WalnutRoot.Shutdown[]; IF badBitsStrm # NIL THEN badBitsStrm.Close[]; tsLogStrm.Close[]; }; reTry => { WalnutLogExpunge.Shutdown[]; WalnutRoot.AbortTransaction[]; WalnutRoot.StartTransaction[]; WalnutLog.OpenLogStreams[]; -- awkward logLength _ WalnutLog.LogLength[]; WalnutLog.ReturnCurrentLogStreams[]; IF ~WalnutLogExpunge.RestartExpunge[currentLogPos, expungeLogPos] THEN { noRestart: ROPE = "\nCouldn't restart at [%g, %g]\n - quitting\n"; out.PutF[noRestart, IO.int[currentLogPos], IO.int[expungeLogPos]]; tsLogStrm.PutF[noRestart, IO.int[currentLogPos], IO.int[expungeLogPos]]; WalnutLogExpunge.EndExpunge[]; WalnutRoot.Shutdown[]; IF badBitsStrm # NIL THEN badBitsStrm.Close[]; tsLogStrm.Close[]; RETURN; }; bytesLeftToCopy _ logLength - currentLogPos; }; END; ENDLOOP; END; }; 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]; $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: ATOM _ NARROW[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] ] }; }; CmdQFix: Commander.CommandProc = { wDir: ROPE = CommandTool.CurrentWorkingDirectory[]; TRUSTED { Process.Detach[FORK SetupQFix[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}\""]; Commander.Register["QuickFixWalnutLog", CmdQFix, "Remove an initial bad entry from a Walnut log; syntax is \"QuickFixWalnutLog rootFile\""]; END.