-- Copyright (C) 1984, 1985 by Xerox Corporation. All rights reserved. -- TimeChecker.mesa, HGM, 26-May-85 14:10:06 DIRECTORY Ascii USING [CR], CmFile USING [Handle, TableError], Format USING [StringProc], Heap USING [Create, systemZone], MFile USING [AddNotifyProc, Handle], MStream USING [ReadWrite, Error, GetLength], Process USING [Pause, SecondsToTicks, Ticks], ProcessorFace USING [microsecondsPerHundredPulses], Put USING [Text], Stream USING [Delete, Handle, PutString, SetPosition], String USING [ AppendChar, AppendString, AppendStringAndGrow, AppendDecimal, AppendLongDecimal, CopyToNewString, Replace], StringLookUp USING [noMatch, TableDesc], System USING [ AdjustGreenwichMeanTime, GetClockPulses, GetGreenwichMeanTime, gmtEpoch, GreenwichMeanTime, NetworkAddress, nullSocketNumber, Pulses, PulsesToMicroseconds, SocketNumber], Time USING [AppendCurrent, Current], Token USING [Filtered, FreeTokenString, Item, Line, LongDecimal], Unformat USING [Error, NetworkAddress], AddressTranslation USING [Error, PrintError, StringToNetworkAddress], Buffer USING [AccessHandle, DestroyPool, GetBuffer, MakePool, NSBuffer, ReturnBuffer], Indirect USING [Close, GetParmFileName, NextValue, OpenSection], Mailer USING [Level, SendGVMail], NSConstants USING [timeServerSocket], NSTypes USING [wordsPerExchangeHeader], PopCorn USING [Error, GetClockOffset], PupWireFormat USING [BcplToMesaLongNumber], PupDefs USING [ GetPupAddress, GetPupContentsBytes, PupAddress, PupBuffer, PupNameTrouble, PupSocket, PupSocketDestroy, PupSocketMake, PupPackageDestroy, PupPackageMake, SecondsToTocks, SetPupContentsWords], PupTypes USING [fillInSocketID, miscSrvSoc], PupTimeServerFormat USING [PupTimeFormat], Socket USING [ AssignNetworkAddress, ChannelHandle, Create, Delete, GetPacket, GetPacketBytes, GetSendBuffer, GetSource, PutPacket, ReturnBuffer, SetDestination, SetPacketWords, SetWaitTime, TimeOut], TimeServerClock USING [AdjustClock], TimeServerFormat USING [TSPacket, Version, WireToGMT, WireToLong], TimeServerFormatOld USING [request, response, TimeFormat], TimeServerOps USING [GetClockError], WireFormat USING [WireLongNumber, WireToMesaLongNumber]; TimeChecker: MONITOR IMPORTS CmFile, Heap, MFile, MStream, Process, ProcessorFace, Put, Stream, String, System, Time, Token, Unformat, AddressTranslation, Buffer, Indirect, Mailer, PopCorn, PupWireFormat, PupDefs, Socket, TimeServerClock, TimeServerFormat, TimeServerOps, WireFormat = BEGIN OPEN PupDefs; z: UNCOUNTED ZONE = Heap.Create[1]; Mode: TYPE = {pup, old, new, popCorn}; Handle: TYPE = LONG POINTER TO Object; Object: TYPE = RECORD [ next: Handle, mode: Mode, target: LONG STRING]; first: Handle ← NIL; timesAround: CARDINAL ← 0; parmFileName: LONG STRING ← Indirect.GetParmFileName[]; pleaseStop: BOOLEAN ← FALSE; watcher: PROCESS ← NIL; threshold: LONG CARDINAL ← LAST[LONG CARDINAL]; troubles, to, cc: LONG STRING ← NIL; mail: LONG STRING ← NIL; Init: ENTRY PROCEDURE = BEGIN MFile.AddNotifyProc[Inspect, [parmFileName, null, readOnly], NIL]; Starter[]; END; Starter: INTERNAL PROCEDURE = BEGIN IF ~FindTargets[] THEN RETURN; timesAround ← 0; pleaseStop ← FALSE; PupPackageMake[]; watcher ← FORK Watcher[]; END; Stopper: INTERNAL PROCEDURE = BEGIN pleaseStop ← TRUE; JOIN watcher[]; watcher ← NIL; ForgetTargets[]; PupPackageDestroy[]; END; FindTargets: PROCEDURE RETURNS [BOOLEAN] = BEGIN cmFile: CmFile.Handle; Option: TYPE = MACHINE DEPENDENT{ pup(0), old, new, popCorn, threshold, troubles, to, cc, noMatch(StringLookUp.noMatch)}; DefinedOption: TYPE = Option [pup..cc]; CheckType: PROCEDURE [h: CmFile.Handle, table: StringLookUp.TableDesc] RETURNS [index: CARDINAL] = Indirect.NextValue; MyNextValue: PROCEDURE [ h: CmFile.Handle, table: LONG DESCRIPTOR FOR ARRAY DefinedOption OF LONG STRING] RETURNS [index: Option] = LOOPHOLE[CheckType]; optionTable: ARRAY DefinedOption OF LONG STRING ← [ pup: "Pup"L, old: "Old NS"L, new: "NS"L, popCorn: "PopCorn"L, threshold: "Threshold"L, troubles: "Troubles"L, to: "to"L, cc: "cc"L]; modeText: ARRAY Mode OF STRING ← [pup: "Pup"L, old: "Old NS"L, new: "NS"L, popCorn: "PopCorn"L]; cmFile ← Indirect.OpenSection["TimeChecker"L]; IF cmFile = NIL THEN BEGIN Message["Can't find [TimeChecker] section in parameter file"L]; RETURN[FALSE]; END; DO option: Option; mode: Mode; temp: Handle; target: LONG STRING; option ← MyNextValue[cmFile, DESCRIPTOR[optionTable] ! CmFile.TableError => BEGIN IF name[0] # '; THEN Message["Unrecognized parameter: ", name]; RETRY; END]; SELECT option FROM noMatch => EXIT; pup => mode ← pup; old => mode ← old; new => mode ← new; popCorn => mode ← popCorn; threshold => BEGIN text: STRING = [20]; threshold ← Token.LongDecimal[cmFile]; String.AppendLongDecimal[text, threshold]; Message["The clock fixup threshold is "L, text, " ms"L]; LOOP; END; troubles => BEGIN temp: LONG STRING ← Token.Item[cmFile, FALSE]; String.Replace[@troubles, temp, z]; CheckForRegistry[troubles]; [] ← Token.FreeTokenString[temp]; Message["Grapevine will send trouble reports to "L, troubles]; LOOP; END; to => BEGIN temp: LONG STRING ← Token.Filtered[cmFile, NIL, Token.Line, whiteSpace, FALSE]; String.Replace[@to, temp, z]; CheckForRegistry[to]; [] ← Token.FreeTokenString[temp]; Message["Mail will be sent to "L, to]; LOOP; END; cc => BEGIN temp: LONG STRING ← Token.Filtered[cmFile, NIL, Token.Line, whiteSpace, FALSE]; String.Replace[@cc, temp, z]; CheckForRegistry[cc]; [] ← Token.FreeTokenString[temp]; Message["Copies will be sent to "L, cc]; LOOP; END; ENDCASE => ERROR; temp ← z.NEW[Object]; temp.mode ← mode; target ← Token.Filtered[cmFile, NIL, Token.Line, whiteSpace, FALSE]; temp.target ← z.NEW[StringBody[target.length]]; String.AppendString[temp.target, target]; [] ← Token.FreeTokenString[target]; temp.next ← NIL; IF first = NIL THEN first ← temp ELSE BEGIN last: Handle ← first; UNTIL last.next = NIL DO last ← last.next; ENDLOOP; last.next ← temp; END; Message[modeText[mode], " target is "L, temp.target]; ENDLOOP; Indirect.Close[cmFile]; RETURN[first # NIL]; END; CheckForRegistry: PROCEDURE [s: LONG STRING] = BEGIN dot: BOOLEAN ← FALSE; FOR i: CARDINAL IN [0..s.length) DO SELECT s[i] FROM '. => dot ← TRUE; ', => BEGIN IF ~dot THEN BEGIN Message["Registry expected in arg: "L, s]; RETURN; END; dot ← FALSE; END; ENDCASE => NULL; ENDLOOP; IF ~dot THEN BEGIN Message["Registry expected in arg: "L, s]; RETURN; END; END; ForgetTargets: INTERNAL PROCEDURE = BEGIN finger: Handle ← first; UNTIL first = NIL DO finger ← first; first ← first.next; z.FREE[@finger.target]; z.FREE[@finger]; ENDLOOP; END; Watcher: PROCEDURE = BEGIN oneMinute: Process.Ticks = Process.SecondsToTicks[60]; when: LONG CARDINAL; THROUGH [0..5) UNTIL pleaseStop DO -- let time get set Process.Pause[oneMinute]; ENDLOOP; when ← Time.Current[]; UNTIL pleaseStop DO FOR finger: Handle ← first, finger.next UNTIL finger = NIL DO IF pleaseStop THEN EXIT; SELECT finger.mode FROM pup => ProbePup[finger.target]; old => ProbeOld[finger.target]; new => ProbeNew[finger.target]; popCorn => ProbePopCorn[finger.target]; ENDCASE => ERROR; ENDLOOP; IF mail # NIL THEN SendMail[]; when ← when + 3600; IF Time.Current[] > when THEN BEGIN missed: CARDINAL ← 0; text: STRING = [100]; WHILE Time.Current[] > when DO missed ← missed + 1; when ← when + 3600; timesAround ← timesAround + 1; ENDLOOP; Time.AppendCurrent[text]; String.AppendString[text, " Oops, it looks like we got stuck for "L]; String.AppendDecimal[text, missed]; String.AppendString[text, " hours"L]; LogString[text]; END; THROUGH [0..120) UNTIL pleaseStop OR Time.Current[] > when DO [] ← GetMyGreenwichMeanTime[]; -- Keep clock up to date Process.Pause[oneMinute]; ENDLOOP; timesAround ← timesAround + 1; ENDLOOP; END; ProbePup: PROCEDURE [target: LONG STRING] = BEGIN soc: PupSocket; pool: Buffer.AccessHandle; diff: LONG INTEGER ← LAST[LONG INTEGER]; hits: CARDINAL ← 0; worked: BOOLEAN; who: PupAddress; [worked, who] ← FindPupAddress[target]; IF ~worked THEN RETURN; pool ← Buffer.MakePool[send: 1, receive: 10]; soc ← PupSocketMake[ PupTypes.fillInSocketID, who, SecondsToTocks[5]]; FOR i: CARDINAL IN [0..10) DO b: PupBuffer ← Buffer.GetBuffer[pup, pool, send]; b.pup.pupType ← dateAltoRequest; b.pup.pupID ← [i, i]; SetPupContentsWords[b, 0]; soc.put[b]; DO b ← soc.get[]; IF b = NIL THEN EXIT; SELECT TRUE FROM b.pup.pupType # dateAltoIs OR PupDefs.GetPupContentsBytes[b] # 2*SIZE[PupTimeServerFormat.PupTimeFormat] OR b.pup.pupID # [i, i] OR b.pup.source # who => NULL; ENDCASE => BEGIN info: LONG POINTER TO PupTimeServerFormat.PupTimeFormat; me, him: LONG INTEGER; -- NB: not LONG CARDINAL info ← LOOPHOLE[@b.pup.pupWords]; me ← Time.Current[]; him ← PupWireFormat.BcplToMesaLongNumber[info.time]; diff ← MIN[diff, (him - me)]; hits ← hits + 1; END; Buffer.ReturnBuffer[b]; ENDLOOP; ENDLOOP; PupSocketDestroy[soc]; Buffer.DestroyPool[pool]; PrintResponse[hits # 0, target, diff*1000]; END; FindPupAddress: PROCEDURE [target: LONG STRING] RETURNS [worked: BOOLEAN, who: PupAddress] = BEGIN worked ← TRUE; who.socket ← PupTypes.miscSrvSoc; GetPupAddress[@who, target ! PupDefs.PupNameTrouble => BEGIN text: STRING = [150]; worked ← FALSE; Time.AppendCurrent[text]; String.AppendString[ text, " TimeChecker: Troubles finding Pup address for "L]; String.AppendString[text, target]; String.AppendString[text, ", "L]; String.AppendString[text, e]; LogString[text]; CONTINUE; END ]; END; ProbeOld: PROCEDURE [target: LONG STRING] = BEGIN soc: Socket.ChannelHandle; diff: LONG INTEGER ← LAST[LONG INTEGER]; error: LONG CARDINAL ← LAST[LONG CARDINAL]; hits: CARDINAL ← 0; worked: BOOLEAN; who: System.NetworkAddress; [worked, who] ← FindNSAddress[target]; IF ~worked THEN RETURN; soc ← Socket.Create[local: Socket.AssignNetworkAddress[], receive: 1]; Socket.SetWaitTime[soc, 5000]; FOR i: CARDINAL IN [0..10) DO b: Buffer.NSBuffer ← Socket.GetSendBuffer[soc]; Socket.SetDestination[b, who]; b.ns.packetType ← echo; b.ns.nsWords[0] ← b.ns.nsWords[1] ← i; b.ns.nsWords[2] ← TimeServerFormatOld.request; Socket.SetPacketWords[b, 3]; Socket.PutPacket[soc, b]; DO b ← Socket.GetPacket[soc ! Socket.TimeOut => EXIT]; SELECT TRUE FROM Socket.GetPacketBytes[b] # 2*(3 + SIZE[TimeServerFormatOld.TimeFormat]) OR (b.ns.nsWords[2] # TimeServerFormatOld.response) OR b.ns.nsWords[0] # i OR b.ns.nsWords[1] # i OR Socket.GetSource[b].host # who.host => NULL; ENDCASE => BEGIN wtf: LONG POINTER TO TimeServerFormatOld.TimeFormat = LOOPHOLE[@b.ns.nsWords[3]]; me, him: LONG INTEGER; -- NB: not LONG CARDINAL me ← Time.Current[]; him ← LOOPHOLE[WireFormat.WireToMesaLongNumber[wtf.time]]; diff ← MIN[diff, (him - me)]; hits ← hits + 1; END; Socket.ReturnBuffer[b]; ENDLOOP; ENDLOOP; Socket.Delete[soc]; PrintResponse[hits # 0, target, diff*1000]; END; ProbeNew: PROCEDURE [target: LONG STRING] = BEGIN soc: Socket.ChannelHandle; diff: LONG INTEGER ← LAST[LONG INTEGER]; error: LONG CARDINAL ← LAST[LONG CARDINAL]; hits: CARDINAL ← 0; worked: BOOLEAN; who: System.NetworkAddress; version: WORD = TimeServerFormat.Version; flight: LONG CARDINAL; [worked, who] ← FindNSAddress[target]; IF ~worked THEN RETURN; soc ← Socket.Create[local: Socket.AssignNetworkAddress[], receive: 1]; Socket.SetWaitTime[soc, 5000]; FOR i: CARDINAL IN [0..10) DO start: System.Pulses; request: LONG POINTER TO timeRequest TimeServerFormat.TSPacket; wordsInRequest: CARDINAL = SIZE[timeRequest TimeServerFormat.TSPacket]; b: Buffer.NSBuffer ← Socket.GetSendBuffer[soc]; Socket.SetDestination[b, who]; b.ns.packetType ← packetExchange; b.ns.exchangeID ← [i, i]; b.ns.exchangeType ← timeService; request ← LOOPHOLE[@b.ns.exchangeBody]; request↑ ← [version, timeRequest[]]; Socket.SetPacketWords[b, NSTypes.wordsPerExchangeHeader + wordsInRequest]; start ← System.GetClockPulses[]; Socket.PutPacket[soc, b]; DO response: LONG POINTER TO timeResponse TimeServerFormat.TSPacket; wordsInResponse: CARDINAL = SIZE[timeResponse TimeServerFormat.TSPacket]; b ← Socket.GetPacket[soc ! Socket.TimeOut => EXIT]; response ← LOOPHOLE[@b.ns.exchangeBody]; SELECT TRUE FROM b.ns.packetType # packetExchange OR b.ns.exchangeID # [i, i] OR Socket.GetPacketBytes[b] # 2*(NSTypes.wordsPerExchangeHeader + wordsInResponse) OR (b.ns.exchangeType # timeService) OR response.version # version OR response.type # timeResponse OR Socket.GetSource[b].host # who.host => NULL; ENDCASE => BEGIN -- the response we were looking for stop: System.Pulses = System.GetClockPulses[]; hisError: LONG CARDINAL = TimeServerFormat.WireToLong[response.absoluteError]; totalError: LONG CARDINAL = hisError + flight + 1000; me, him: LONG INTEGER; -- NB: not LONG CARDINAL me ← Time.Current[]; him ← TimeServerFormat.WireToGMT[response.time]; IF response.errorAccurate THEN BEGIN IF totalError < error THEN BEGIN error ← totalError; diff ← (him - me); flight ← System.PulsesToMicroseconds[[stop - start]]/1000; END; END ELSE BEGIN diff ← MIN[diff, (him - me)]; error ← LAST[LONG CARDINAL]; flight ← System.PulsesToMicroseconds[[stop - start]]/1000; END; hits ← hits + 1; END; Socket.ReturnBuffer[b]; ENDLOOP; ENDLOOP; Socket.Delete[soc]; PrintResponse[hits # 0, target, diff*1000, error, flight]; END; FindNSAddress: PROCEDURE [target: LONG STRING] RETURNS [worked: BOOLEAN, who: System.NetworkAddress] = BEGIN Problem: PROCEDURE [e: LONG STRING] = BEGIN text: STRING = [200]; Time.AppendCurrent[text]; String.AppendString[ text, " TimeChecker: Troubles finding NS address for "L]; String.AppendString[text, target]; String.AppendString[text, ", "L]; String.AppendString[text, e]; LogString[text]; END; worked ← TRUE; who ← GetAddress[target, NSConstants.timeServerSocket ! Trouble => BEGIN Problem[reason]; worked ← FALSE; CONTINUE; END ]; END; ProbePopCorn: PROCEDURE [target: LONG STRING] = BEGIN diff, delta: LONG INTEGER; hisError, ourError: LONG CARDINAL; flight: LONG CARDINAL; worked, known, mixup: BOOLEAN ← FALSE; who: PupAddress; interesting: BOOLEAN = ((timesAround MOD 24) = 0); [worked, who] ← FindPupAddress[target]; IF ~worked THEN RETURN; [diff, flight] ← PopCorn.GetClockOffset[who, IF interesting THEN 5 ELSE 2 ! PopCorn.Error => BEGIN temp: STRING = [200]; Time.AppendCurrent[temp]; String.AppendString[temp, " TimeChecker: Troubles from "L]; String.AppendString[temp, target]; String.AppendString[temp, ", "L]; String.AppendString[temp, text]; LogString[temp]; IF interesting THEN AppendToLogFile[temp]; worked ← FALSE; CONTINUE; END;]; IF ~worked THEN RETURN; IF diff > 0 THEN -- convert to seconds delta ← (diff + 500) / 1000 ELSE delta ← (diff - 500) / 1000; hisError ← 1000 + flight + 1000; -- quantum and fudge hisError ← hisError + 80; -- DLion clock jitters **************** PrintResponse[TRUE, target, diff, hisError, flight]; IF timeLastSet = System.gmtEpoch THEN BEGIN now: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[]; SetMyGreenwichMeanTime[System.AdjustGreenwichMeanTime[now, delta]]; END ELSE BEGIN now: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[]; myNow: System.GreenwichMeanTime ← GetMyGreenwichMeanTime[]; myDelta: LONG INTEGER ← myNow - now; PrintDelta[myDelta - delta]; END; [known, ourError] ← TimeServerOps.GetClockError[]; mixup ← known AND (ABS[diff] > (hisError + ourError)); IF mixup OR threshold # LAST[LONG CARDINAL] THEN BEGIN IF mixup OR ~known OR (hisError + ourError > threshold) THEN BEGIN TimeServerClock.AdjustClock[delta, hisError, TRUE]; IF mixup THEN Message["Clock Mixup. Clock synced to "L, target] ELSE Message["Threshold tripped. Clock synced to "L, target]; END; END; END; PrintResponse: PROCEDURE [ hit: BOOLEAN, target: LONG STRING, diff: LONG INTEGER, hisError: LONG CARDINAL ← LAST[LONG CARDINAL], flight: LONG CARDINAL ← 0] = BEGIN text: STRING = [300]; mixup: BOOLEAN ← FALSE; known: BOOLEAN; ourError: LONG CARDINAL; [known, ourError] ← TimeServerOps.GetClockError[]; IF known AND hisError # LAST[LONG CARDINAL] THEN BEGIN mixup ← ABS[diff] > (hisError + ourError + flight); END; Time.AppendCurrent[text]; IF hit THEN BEGIN SELECT TRUE FROM (timesAround = 0) => BEGIN String.AppendString[text, " At the start of this run, "L]; END; (timesAround = 1) => BEGIN String.AppendString[text, " After one hour, "L]; END; (timesAround = 24) => BEGIN String.AppendString[text, " After one day, "L]; END; (timesAround = 168) => BEGIN String.AppendString[text, " After one week, "L]; END; ((timesAround MOD 24) = 0) => BEGIN String.AppendString[text, " After "L]; String.AppendDecimal[text, timesAround/24]; String.AppendString[text, " days, "L]; END; ENDCASE => BEGIN String.AppendString[text, " After "L]; String.AppendDecimal[text, timesAround]; String.AppendString[text, " hours, "L]; END; String.AppendString[text, target]; String.AppendString[text, "'s clock is "L]; String.AppendLongDecimal[text, diff]; String.AppendString[text, " ms"L]; String.AppendString[text, " faster than ours"L]; IF hisError # LAST[LONG CARDINAL] THEN BEGIN String.AppendChar[text, '.]; IF mixup THEN String.AppendString[text, " ***************"L]; String.AppendChar[text, Ascii.CR]; String.AppendString[text, "His error: "L]; String.AppendLongDecimal[text, hisError]; String.AppendString[text, " ms"L]; IF known THEN BEGIN String.AppendString[text, ", Our error: "L]; String.AppendLongDecimal[text, ourError]; String.AppendString[text, " ms"L]; END END; IF flight # 0 THEN BEGIN IF hisError = LAST[LONG CARDINAL] THEN BEGIN String.AppendChar[text, '.]; String.AppendChar[text, Ascii.CR]; END ELSE String.AppendString[text, ", "L]; String.AppendString[text, "Flight time: "L]; String.AppendLongDecimal[text, flight]; String.AppendString[text, " ms"L]; END; END ELSE BEGIN String.AppendString[text, " TimeChecker: No response from "L]; String.AppendString[text, target]; END; LogString[text]; IF ((timesAround MOD 24) = 0) OR mixup THEN AppendToLogFile[text]; END; PrintDelta: PROCEDURE [delta: LONG INTEGER] = BEGIN text: STRING = [200]; Time.AppendCurrent[text]; SELECT TRUE FROM (timesAround = 0) => BEGIN String.AppendString[text, " At the start of this run, "L]; END; (timesAround = 1) => BEGIN String.AppendString[text, " After one hour, "L]; END; (timesAround = 24) => BEGIN String.AppendString[text, " After one day, "L]; END; (timesAround = 168) => BEGIN String.AppendString[text, " After one week, "L]; END; ((timesAround MOD 24) = 0) => BEGIN String.AppendString[text, " After "L]; String.AppendDecimal[text, timesAround/24]; String.AppendString[text, " days, "L]; END; ENDCASE => BEGIN String.AppendString[text, " After "L]; String.AppendDecimal[text, timesAround]; String.AppendString[text, " hours, "L]; END; String.AppendString[text, "my simulated clock is "L]; String.AppendLongDecimal[text, delta]; String.AppendString[text, " seconds"L]; String.AppendString[text, " faster than it should be"L]; LogString[text]; IF ((timesAround MOD 24) = 0) THEN AppendToLogFile[text]; END; Message: PROCEDURE [one, two, three: LONG STRING ← NIL] = BEGIN text: STRING = [100]; Time.AppendCurrent[text]; String.AppendString[text, " TimeChecker: "L]; String.AppendString[text, one]; IF two # NIL THEN String.AppendString[text, two]; IF three # NIL THEN String.AppendString[text, three]; LogString[text]; END; LogString: PROCEDURE [text: LONG STRING] = BEGIN String.AppendChar[text, '.]; String.AppendChar[text, Ascii.CR]; Put.Text[NIL, text]; END; AppendToLogFile: PROCEDURE [s: LONG STRING] = BEGIN sh: Stream.Handle ← NIL; sh ← MStream.ReadWrite["TimeServer.log"L, [], text ! MStream.Error => CONTINUE ]; IF sh = NIL THEN RETURN; Stream.SetPosition[sh, MStream.GetLength[sh]]; Stream.PutString[sh, s]; Stream.Delete[sh]; AppendToMail[s]; END; AppendToMail: PROCEDURE [s: LONG STRING] = BEGIN String.AppendStringAndGrow[@mail, s, z]; END; SendMail: PROCEDURE = BEGIN subject: STRING = "Report from TimeChecker"L; Info: PROCEDURE [s: LONG STRING, level: Mailer.Level] = BEGIN copy: LONG STRING ← String.CopyToNewString[s, Heap.systemZone, 2]; LogString[copy]; Heap.systemZone.FREE[@copy]; END; IF to # NIL THEN [] ← Mailer.SendGVMail[subject, to, cc, mail, troubles, Info]; z.FREE[@mail]; END; Inspect: ENTRY PROCEDURE [ name: LONG STRING, file: MFile.Handle, clientInstanceData: LONG POINTER] RETURNS [BOOLEAN] = BEGIN IF parmFileName = NIL THEN RETURN[FALSE]; Message["Recycling because a new version of "L, parmFileName, " arrived"L]; IF watcher # NIL THEN Stopper[]; Starter[]; RETURN[FALSE] END; -- Copied (more or less) from GMTUsingIntervalTimer timeLastSet: System.GreenwichMeanTime ← System.gmtEpoch; gmtSimulated: System.GreenwichMeanTime ← System.gmtEpoch; pulsesGmtSimulated: LONG CARDINAL; -- interval timer value corresponding to gmtSimulated pulsesPer100Seconds: LONG CARDINAL ← 10*(1D9/ProcessorFace.microsecondsPerHundredPulses); SetMyGreenwichMeanTime: PROCEDURE [gmt: System.GreenwichMeanTime] = BEGIN pulsesGmtSimulated ← System.GetClockPulses[]; gmtSimulated ← gmt; timeLastSet ← gmt; END; GetMyGreenwichMeanTime: PROCEDURE RETURNS [System.GreenwichMeanTime] = BEGIN newPulses, pulsesJumped, new100Seconds, leftoverSeconds: LONG CARDINAL; IF gmtSimulated = System.gmtEpoch THEN RETURN[gmtSimulated]; -- clock not set newPulses ← System.GetClockPulses[] - pulsesGmtSimulated; new100Seconds ← newPulses / pulsesPer100Seconds; pulsesJumped ← new100Seconds * pulsesPer100Seconds; pulsesGmtSimulated ← pulsesGmtSimulated + pulsesJumped; gmtSimulated ← System.AdjustGreenwichMeanTime[gmtSimulated, new100Seconds * 100]; leftoverSeconds ← ((newPulses - pulsesJumped) * 100) / pulsesPer100Seconds; RETURN[System.AdjustGreenwichMeanTime[gmtSimulated, leftoverSeconds]]; END; Trouble: ERROR [reason: LONG STRING] = CODE; GetAddress: PROCEDURE [host: LONG STRING, socket: System.SocketNumber] RETURNS [addr: System.NetworkAddress] = BEGIN localFailed: BOOLEAN ← FALSE; IF host = NIL THEN ERROR Trouble["NIL => Address Fault"L]; addr ← Unformat.NetworkAddress[host, octal ! Unformat.Error => BEGIN localFailed ← TRUE; CONTINUE; END ]; IF localFailed THEN BEGIN addr ← AddressTranslation.StringToNetworkAddress[host ! AddressTranslation.Error => BEGIN temp: STRING = [200]; proc: Format.StringProc = {String.AppendString[temp, s]}; AddressTranslation.PrintError[errorRecord, proc]; ERROR Trouble[temp]; END].addr; addr.socket ← socket; -- CH returns trash in socket END; IF addr.socket = System.nullSocketNumber THEN addr.socket ← socket; END; Init[]; END.