-- Copyright (C) 1981, 1984, 1985 by Xerox Corporation. All rights reserved. -- Log.mesa, Transport Mechanism Mail Server: disk log -- -- HGM, 16-Sep-85 22:47:18 -- Andrew Birrell September 14, 1982 2:03 pm -- -- Redell 20-May-81 17:13:35 -- -- M. D. Schroeder June 7, 1982 7:07 PM -- -- Hankins 13-Aug-84 16:27:50 (add ReleasePage in DoSpill) -- Wobber 29-Aug-84 17:58:36 (fix above faulty fix) DIRECTORY Ascii USING [CR, SP], Heap USING [systemZone], LogDefs USING [], LogWatchDefs USING [], Process USING [DisableTimeout, SecondsToTicks, SetTimeout], String USING [ AppendChar, AppendDecimal, AppendString, AppendSubString, EqualSubString, SubString, SubStringDescriptor], Time USING [Append, Current, Unpack], VMDefs USING [ AbandonFile, CantOpen, CantReadBackingStore, CantWriteBackingStore, CloseFile, Error, FileHandle, FileSystem, Login, Logout, LookAheadCount, MarkStart, OpenFile, Page, PageAddress, PageByteIndex, pageByteSize, PageNumber, Position, ReadPage, Release, SetCreationTime, SetFileLength, UnableToLogin, UsePage, Wait]; Log: MONITOR IMPORTS Heap, Process, String, Time, VMDefs EXPORTS LogDefs, LogWatchDefs = BEGIN OPEN Ascii, String, Time, VMDefs; halfPagesInLogFile: CARDINAL = 60; pagesInLogFile: CARDINAL = halfPagesInLogFile * 2; endMarker: CHARACTER = '¬; dayLength: CARDINAL = 10; -- format is "dd-mmm-yy " timeLength: CARDINAL = 8; --format is "hh:mm:ss" spillLimit: CARDINAL = 40; -- cycle length for spilling log files logSequence: [0..spillLimit); -- number for backing up this log file cycle logFileHandle: FileHandle; current: Position; pageArray: LONG POINTER TO PACKED ARRAY PageByteIndex OF CHARACTER ¬ NIL; firstPage: Page ¬ NIL; lastDay: STRING = [dayLength]; lastDaySSD: SubStringDescriptor ¬ [lastDay, 0, dayLength]; lastDaySS: SubString = @lastDaySSD; logChange: CONDITION ¬ [timeout: Process.SecondsToTicks[2]]; -- procedure to write a log entry -- WriteLogEntry: PUBLIC ENTRY PROCEDURE [s: LONG STRING] = BEGIN dayTimeString: STRING ¬ [dayLength + timeLength]; timeString: STRING ¬ [timeLength]; daySSD: SubStringDescriptor ¬ [dayTimeString, 0, dayLength]; daySS: SubString = @daySSD; timeSSD: SubStringDescriptor ¬ [dayTimeString, dayLength, timeLength]; timeSS: SubString = @timeSSD; front: STRING = " "L; crString: STRING = " "L; Append[dayTimeString, Unpack[Current[]]]; IF ~EqualSubString[daySS, lastDaySS] THEN BEGIN lastDay.length ¬ 0; AppendSubString[lastDay, daySS]; LogLastDay[]; END; AppendSubString[timeString, timeSS]; StartLogEntry[]; PutStringInLog[timeString]; PutStringInLog[front]; PutStringInLog[s]; PutStringInLog[crString]; FinishLogEntry[]; BROADCAST logChange; END; -- procedure to start a log entry StartLogEntry: INTERNAL PROCEDURE = BEGIN Wait[LOOPHOLE[pageArray, VMDefs.Page]]; -- make sure previous write completed before updating page END; -- procedure to put a string into the log file PutStringInLog: INTERNAL PROCEDURE [ls: LONG STRING] = BEGIN c: CHARACTER; Copy: PROC [offset: CARDINAL] RETURNS [done: CARDINAL] = BEGIN done ¬ MIN[pageByteSize - current.byte, ls.length - offset]; FOR i: CARDINAL IN [0..done) DO pageArray­[current.byte + i] ¬ IF (c ¬ ls[i + offset]) = endMarker THEN SP ELSE c; ENDLOOP; current.byte ¬ current.byte + done; END; firstChunkLength: CARDINAL = Copy[0]; IF current.byte = pageByteSize --cross a page boundary-- AND firstPage = NIL --not at end of second page-- THEN BEGIN current.page ¬ current.page + 1; SELECT TRUE FROM (current.page >= pagesInLogFile) => { current.page ¬ 0; SpillLog[second, logSequence]}; (current.page = halfPagesInLogFile) => SpillLog[first, logSequence]; ENDCASE => NULL; firstPage ¬ LOOPHOLE[pageArray, VMDefs.Page]; --remember first page GetCurrentLogPage[]; --get second page; sets current.byte [] ¬ Copy[firstChunkLength]; END; IF current.byte = pageByteSize THEN current.byte ¬ current.byte - 1 -- leave room for endMarker; END; -- procedure to complete a log entry FinishLogEntry: INTERNAL PROCEDURE = BEGIN pageArray­[current.byte] ¬ endMarker; --will be overwritten by next log entry MarkStart[LOOPHOLE[pageArray, VMDefs.Page]]; --writes second page first if entry crosses page boundary IF firstPage # NIL THEN BEGIN -- wait for second page write to complete then write and release first page Wait[LOOPHOLE[pageArray, VMDefs.Page]]; MarkStart[firstPage]; Release[firstPage]; firstPage ¬ NIL; IF current.page = 0 OR current.page = halfPagesInLogFile THEN LogLastDay[]; -- write day at start of each half of file END; END; -- procedure to write a log entry containing the last day value LogLastDay: INTERNAL PROCEDURE = BEGIN binaryBit: STRING = [1]; binaryBit[0] ¬ 377C; binaryBit.length ¬ 1; lastDay[lastDay.maxlength - 1] ¬ CR; StartLogEntry[]; PutStringInLog[binaryBit]; PutStringInLog[lastDay]; FinishLogEntry[]; SetCreationTime[logFileHandle]; lastDay[lastDay.maxlength - 1] ¬ SP; END; -- procedure to read the current log page -- GetCurrentLogPage: INTERNAL PROCEDURE = BEGIN pageArray ¬ LOOPHOLE[ReadPage[ addr: PageAddress[file: logFileHandle, page: current.page], lookAhead: 0]]; current.byte ¬ 0; IF current.page = 0 THEN BEGIN logSequence ¬ (logSequence + 1) MOD spillLimit; pageArray[0] ¬ LOOPHOLE[logSequence]; current.byte ¬ 1; -- but don't mark it dirty yet -- END; END; -- procedures to implement the LogWatchDefs interface -- LogPosition: PUBLIC TYPE = VMDefs.Position; GetLogPosition: PUBLIC ENTRY PROCEDURE RETURNS [LogPosition] = { RETURN[current]}; PutLogChars: PUBLIC PROCEDURE [ p: LogPosition, put: PROCEDURE [CHARACTER], gap: PROCEDURE, stop: PROCEDURE RETURNS [BOOLEAN]] RETURNS [LogPosition] = BEGIN e: Position; page: LONG POINTER TO PACKED ARRAY PageByteIndex OF CHARACTER; WaitForNewChars: ENTRY PROCEDURE = INLINE { IF p = current THEN WAIT logChange; e ¬ current}; CaughtUp: ENTRY PROCEDURE RETURNS [BOOLEAN] = INLINE {RETURN[p = current]}; DO WaitForNewChars[]; IF p # e THEN BEGIN ENABLE UNWIND => Release[LOOPHOLE[page, Page]]; lastByte: PageByteIndex; page ¬ LOOPHOLE[ReadPage[PageAddress[logFileHandle, p.page], 0]]; lastByte ¬ IF p.page = e.page THEN e.byte ELSE pageByteSize; FOR i: PageByteIndex IN [p.byte..lastByte) DO IF p.page # 0 OR i # 0 THEN put[page[i]]; -- First character in file is log file # ENDLOOP; p ¬ IF p.page = e.page THEN e ELSE [(p.page + 1) MOD pagesInLogFile, 0]; IF CaughtUp[] THEN gap[]; Release[LOOPHOLE[page, Page]]; END; IF stop[] THEN RETURN[p]; ENDLOOP; END; -- PutLogChars -- -- procedure to spill half of log to backing file server SpillRequest: TYPE = {first, second}; spillRequest: SpillRequest; -- which half to spill this time -- spillSequence: [0..spillLimit); -- which file to spill to this time -- spillerWanted: BOOLEAN; -- spilling request has been set up -- spillerChange: CONDITION; -- used in both directions -- spillerSeconds: CARDINAL ¬ 5 * 60; -- delay between retries after failures -- spillerProcess: PROCESS; -- for debugging only -- myName: LONG STRING ¬ NIL; -- credentials for spilling -- myPwd: LONG STRING ¬ NIL; server: LONG STRING ¬ NIL; -- server name for spilling -- spillName: LONG STRING ¬ NIL; -- spill file name -- spillPrefix: CARDINAL; spillEnabled: BOOLEAN ¬ FALSE; KeepString: INTERNAL PROC [from: LONG STRING, extra: CARDINAL ¬ 0] RETURNS [to: LONG STRING] = BEGIN to ¬ Heap.systemZone.NEW[StringBody[from.length + extra]]; String.AppendString[to, from]; END; EnableLogSpilling: PUBLIC ENTRY PROC [name, pwd, host, path: LONG STRING] = BEGIN IF spillEnabled THEN RETURN; myName ¬ KeepString[name]; myPwd ¬ KeepString[pwd]; server ¬ KeepString[host]; spillName ¬ KeepString[path, myName.length + 5 -- "Cabernet-00!1" -- ]; spillPrefix ¬ path.length; spillEnabled ¬ TRUE; -- Assume last spill of previous run failed, and possibly re-try -- IF current.page < halfPagesInLogFile THEN -- previous request was for second half of previous cycle -- BEGIN spillRequest ¬ second; spillSequence ¬ (logSequence + spillLimit - 1) MOD spillLimit; END ELSE -- last spill request was for first half of this cycle -- BEGIN spillRequest ¬ first; spillSequence ¬ logSequence; END; spillerWanted ¬ FALSE; -- Spiller may retry after normal delay -- spillerProcess ¬ FORK LogSpiller[]; END; SpillLog: INTERNAL PROC [half: SpillRequest, cycle: [0..spillLimit)] = BEGIN IF spillEnabled THEN BEGIN -- don't wait for spiller if it isn't ready: it might take too long! spillRequest ¬ half; spillSequence ¬ cycle; spillerWanted ¬ TRUE; NOTIFY spillerChange; END; END; WaitForSpillRequest: ENTRY PROC [completed: BOOLEAN] RETURNS [SpillRequest, [0..spillLimit)] = BEGIN IF completed THEN Process.DisableTimeout[@spillerChange] ELSE Process.SetTimeout[@spillerChange, Process.SecondsToTicks[spillerSeconds]]; DO IF NOT spillerWanted THEN WAIT spillerChange; IF spillerWanted -- new request -- OR ( -- possible retry after timeout -- NOT completed AND ((spillRequest = first AND current.page < halfPagesInLogFile + halfPagesInLogFile / 2) OR (spillRequest = second AND current.page < halfPagesInLogFile / 2))) THEN EXIT; ENDLOOP; spillerWanted ¬ FALSE; RETURN[spillRequest, spillSequence] END; LogSpiller: PROC = BEGIN Behind: ENTRY PROC RETURNS [BOOLEAN] = INLINE {RETURN[spillerWanted]}; completed: BOOLEAN ¬ FALSE; -- assume last run failed -- DO half: SpillRequest; currentCycle: [0..spillLimit); [half, currentCycle] ¬ WaitForSpillRequest[completed]; BEGIN ENABLE VMDefs.UnableToLogin, VMDefs.CantOpen, VMDefs.Error, VMDefs.CantReadBackingStore, VMDefs.CantWriteBackingStore => GOTO crash; DoSpill[half, currentCycle]; completed ¬ TRUE; EXITS crash => completed ¬ FALSE; END; LogSpillEnded[half, spillName, completed, Behind[]]; ENDLOOP; END; LogSpillEnded: PROC [half: SpillRequest, file: LONG STRING, ok, late: BOOLEAN] = BEGIN log: LONG STRING ¬ Heap.systemZone.NEW[StringBody[200]]; String.AppendString[log, "Log "L]; String.AppendString[log, IF ok THEN "spilled: ["L ELSE "spill failed: ["L]; String.AppendString[log, server]; String.AppendString[log, "]"L]; String.AppendString[log, file]; String.AppendString[log, IF half = first THEN ", first"L ELSE ", second"L]; String.AppendString[log, " half"L]; WriteLogEntry[log]; IF late THEN WriteLogEntry["Started next spill request late"L]; Heap.systemZone.FREE[@log]; END; DoSpill: --EXTERNAL-- PROC [half: SpillRequest, currentCycle: [0..spillLimit)] = BEGIN fs: VMDefs.FileSystem = VMDefs.Login[ifs, server, myName, myPwd]; spillName.length ¬ spillPrefix; String.AppendString[spillName, myName]; FOR i: CARDINAL DECREASING IN [spillPrefix..spillName.length) DO IF spillName[i] = '. THEN {spillName.length ¬ i; EXIT} ENDLOOP; String.AppendChar[spillName, '-]; IF currentCycle < 10 THEN String.AppendChar[spillName, '0]; String.AppendDecimal[spillName, currentCycle]; String.AppendString[spillName, "!1"L]; BEGIN ENABLE UNWIND => VMDefs.Logout[fs]; file: VMDefs.FileHandle ¬ VMDefs.OpenFile[ system: fs, name: spillName, options: oldOrNew]; from: VMDefs.Page ¬ NIL; BEGIN ENABLE UNWIND => VMDefs.AbandonFile[file]; base: VMDefs.PageNumber = IF half = first THEN 0 ELSE halfPagesInLogFile; limit: VMDefs.PageNumber = base + halfPagesInLogFile; VMDefs.SetFileLength[file, [page: limit, byte: 0]]; FOR p: VMDefs.PageNumber IN [base..limit) DO to: VMDefs.Page = VMDefs.UsePage[[file, p]]; BEGIN ENABLE UNWIND => VMDefs.Release[to]; from: VMDefs.Page ¬ VMDefs.ReadPage[[logFileHandle, p], 2]; to­ ¬ from­; VMDefs.Release[from]; -- must be released first -- VMDefs.MarkStart[to]; --TEMP-- VMDefs.Wait[to]; -- avoid VM deadlock on connection failure -- --? just how temp is this latter? can it be removed in Klamath? END; -- of enable unwind VMDefs.Release[to]; ENDLOOP; VMDefs.CloseFile[file]; END; END; VMDefs.Logout[fs]; END; -- initialization procedure InitializeLog: ENTRY PROCEDURE = BEGIN AppendString[lastDay, "01-Jan-01 "L]; -- forces day to be logged on startup logFileHandle ¬ OpenFile[ name: "GV.log"L, options: oldOrNew, cacheFraction: 0]; SetFileLength[ file: logFileHandle, position: Position[page: pagesInLogFile, byte: 0]]; -- initialize "logSequence" -- current.page ¬ 0; pageArray ¬ LOOPHOLE[ReadPage[ addr: PageAddress[file: logFileHandle, page: 0], lookAhead: 0]]; logSequence ¬ (LOOPHOLE[pageArray[0], CARDINAL]) MOD spillLimit; current.byte ¬ 1; -- search for endMarker -- DO FOR i: CARDINAL IN [current.byte..pageByteSize) DO IF pageArray­[i] = endMarker THEN {current.byte ¬ i; GOTO found}; ENDLOOP; Release[LOOPHOLE[pageArray, VMDefs.Page]]; current.page ¬ current.page + 1; IF current.page = pagesInLogFile THEN BEGIN -- no endMarker found: start at 0 and increment logSequence -- current.page ¬ 0; GetCurrentLogPage[]; EXIT END; GetCurrentLogPage[]; REPEAT found => NULL; ENDLOOP; END; -- initialization code InitializeLog[]; END.