-- Copyright (C) 1984, 1985 by Xerox Corporation. All rights reserved. -- TimeCheckerNoDisk.mesa, HGM, 25-Jun-85 15:40:31 DIRECTORY Ascii USING [CR], CmFile USING [Handle, TableError], Heap USING [Create, systemZone], Format USING [StringProc], Process USING [Pause, SecondsToTicks, Ticks], ProcessorFace USING [microsecondsPerHundredPulses], Put USING [Text], 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 [ ChannelHandle, Create, Delete, GetPacket, GetPacketBytes, GetSendBuffer, GetSource, PutPacket, ReturnBuffer, SetDestination, SetPacketWords, SetWaitTime, TimeOut], TimeServerClock USING [AdjustClock], TimeServerFormat USING [TSPacket, Version, WireToGMT, WireToLong], TimeServerOps USING [GetClockError]; TimeCheckerNoDisk: MONITOR IMPORTS CmFile, Heap, Process, ProcessorFace, Put, String, System, Time, Token, Unformat, AddressTranslation, Buffer, Indirect, Mailer, PopCorn, PupWireFormat, PupDefs, Socket, TimeServerClock, TimeServerFormat, TimeServerOps = BEGIN OPEN PupDefs; z: UNCOUNTED ZONE = Heap.Create[1]; Mode: TYPE = {pup, ns, 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 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), ns, 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, ns: "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, ns: "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; ns => mode ¬ ns; 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]; ns => ProbeNS[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; ProbeNS: 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[socket: System.nullSocketNumber, 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 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 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; -- 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.