-- Copyright (C) 1981, 1984 by Xerox Corporation. All rights reserved. -- Log.mesa, Transport Mechanism Mail Server: disk log -- -- HGM, 3-Dec-84 22:38:43 -- 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], LogDefs USING [], LogWatchDefs USING [], Process USING [DisableTimeout, SecondsToTicks, SetTimeout], Storage USING [String], 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 Process, Storage, 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: 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: 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 NOT 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: 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: 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: STRING ← NIL; -- credentials for spilling -- myPwd: STRING ← NIL; server: STRING ← NIL; -- server name for spilling -- spillName: STRING ← NIL; -- spill file name -- spillPrefix: CARDINAL; spillEnabled: BOOLEAN ← FALSE; KeepString: INTERNAL PROC [from: STRING, extra: CARDINAL ← 0] RETURNS [to: STRING] = BEGIN to ← Storage.String[from.length + extra]; String.AppendString[to, from]; END; EnableLogSpilling: PUBLIC ENTRY PROC [name, pwd, host, path: 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: STRING, ok, late: BOOLEAN] = BEGIN text: STRING = [128]; String.AppendString[text, "Log "L]; String.AppendString[text, IF ok THEN "spilled: ["L ELSE "spill failed: ["L]; String.AppendString[text, server]; String.AppendString[text, "]"L]; String.AppendString[text, file]; String.AppendString[text, IF half = first THEN ", first"L ELSE ", second"L]; String.AppendString[text, " half"L]; WriteLogEntry[text]; IF late THEN WriteLogEntry["Started next spill request late"L]; 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.