-- File: PupTimeServerCold.mesa, Last Edit: BLyon January 16, 1981 5:08 PM -- Please don't forget to update the herald... DIRECTORY Ascii USING [TAB, SP, CR], InlineDefs USING [BcplToMesaLongNumber], Process USING [Detach, Yield], Runtime USING [IsBound], Storage USING [FreeString], String USING [ AppendChar, AppendString, AppendDecimal, AppendLongDecimal, EquivalentString, StringToDecimal, InvalidNumber], System USING [GetGreenwichMeanTime], Time USING [AppendCurrent], CmFile USING [OpenSection, NextItem, Close], Event USING [Item, Reason, AddNotifier], FormSW USING [ ClientItemsProcType, ProcType, AllocateItemDescriptor, newLine, Display, FindItem, CommandItem, BooleanItem, StringItem, NumberItem], Put USING [Line], Tool USING [Create, MakeSWsProc, MakeMsgSW, MakeFormSW], ToolWindow USING [TransitionProcType], Window USING [Handle], Clock USING [ SetCorrection, TimeParameters, GetTimeParms, SetTimeParms, SetTime, SetTimeResetter], Indirect USING [GetParmFileName], MiscServerDefs USING [ PupMiscServerOn, PupMiscServerOff, IgnoreThisPacket, SetTimeServer], TimeServerDefs USING [ PupTimeServer, PupTimeFormat, parmsOk, resetAddress, correction], StatsDefs USING [ StatCounterIndex, StatUpdate, StatsGetCounters, StatsStringToIndex], DriverDefs USING [Network], PupDefs USING [ PupBuffer, PupSocketID, UniqueLocalPupSocketID, PupSocket, PupSocketMake, PupSocketDestroy, SecondsToTocks, GetFreePupBuffer, ReturnFreePupBuffer, AppendHostName, PupRouterBroadcastThis, SetPupContentsWords], PupTypes USING [PupAddress, fillInPupAddress, miscSrvSoc], BufferDefs; PupTimeServerCold: PROGRAM IMPORTS InlineDefs, Process, Runtime, Storage, String, System, Time, CmFile, Event, FormSW, Put, Tool, Clock, Indirect, MiscServerDefs, TimeServerDefs, StatsDefs, PupDefs EXPORTS BufferDefs, TimeServerDefs SHARES BufferDefs = BEGIN OPEN StatsDefs, PupDefs, TimeServerDefs; -- EXPORTed TYPEs Network: PUBLIC TYPE = DriverDefs.Network; msg, form: PUBLIC Window.Handle ← NIL; eventItem: Event.Item ← [eventMask: 177777B, eventProc: Broom]; useCount: CARDINAL ← 0; resetting, pleaseStop, running: BOOLEAN ← FALSE; resetText: STRING ← [30]; stats: POINTER TO ARRAY StatCounterIndex OF LONG CARDINAL ← StatsGetCounters[]; statText, statTenex, statAlto: PUBLIC StatCounterIndex; Init: PROCEDURE = BEGIN [] ← Tool.Create[ name: "Time Server of November 16, 1980"L, makeSWsProc: MakeSWs, clientTransition: ClientTransition, initialState: inactive]; Event.AddNotifier[@eventItem]; END; PupTimeServerOn: PUBLIC PROCEDURE = BEGIN IF (useCount ← useCount + 1) = 1 THEN BEGIN running ← TRUE; Starter[]; END; UpdatePicture[]; END; Starter: PROCEDURE = BEGIN MiscServerDefs.PupMiscServerOn[]; parmsOk ← FindParameters[]; MiscServerDefs.SetTimeServer[TimeServerDefs.PupTimeServer]; pleaseStop ← FALSE; Process.Detach[FORK ResetFromAnywhere[]]; Clock.SetTimeResetter[ResetFromAnywhere]; END; PupTimeServerOff: PUBLIC PROCEDURE = BEGIN IF useCount # 0 AND (useCount ← useCount - 1) = 0 THEN BEGIN running ← FALSE; Stopper[]; END; UpdatePicture[]; END; Stopper: PROCEDURE = BEGIN MiscServerDefs.PupMiscServerOff[]; MiscServerDefs.SetTimeServer[MiscServerDefs.IgnoreThisPacket]; pleaseStop ← TRUE; WHILE resetting DO Process.Yield[]; ENDLOOP; END; UpdatePicture: PROCEDURE = BEGIN IF form = NIL THEN RETURN; FormSW.FindItem[form, startIX].flags.invisible ← running; FormSW.FindItem[form, stopIX].flags.invisible ← ~running; FormSW.Display[form]; END; ResetFromAnywhere: PROCEDURE = BEGIN ResetTime[PupTypes.fillInPupAddress]; END; ResetTime: PUBLIC PROCEDURE [where: PupTypes.PupAddress] = BEGIN broadcast: BOOLEAN ← where = PupTypes.fillInPupAddress; b: PupBuffer; me: PupSocketID ← UniqueLocalPupSocketID[]; socket: PupSocket ← PupSocketMake[me, where, SecondsToTocks[2]]; i: CARDINAL ← 0; delta: LONG INTEGER; time: LONG CARDINAL ← System.GetGreenwichMeanTime[] - 99; since: LONG CARDINAL; -- smash resetAddress even if we are already resetting to avoid getting stuck trying to reset from nowhere resetAddress ← where; IF broadcast THEN where.socket ← PupTypes.miscSrvSoc; resetText.length ← 0; IF broadcast THEN String.AppendString[resetText, "Anywhere"L] ELSE AppendHostName[resetText, resetAddress]; UpdatePicture[]; IF resetting THEN RETURN; resetting ← TRUE; UpdatePicture[]; DO IF pleaseStop THEN BEGIN resetting ← FALSE; RETURN; END; since ← System.GetGreenwichMeanTime[] - time; IF since > 60*10 OR (i < 10 AND since > 5) THEN BEGIN -- Poke every few seconds for a while, then back way off. i ← i + 1; b ← GetFreePupBuffer[]; b.pupID ← [0, i]; b.source.socket ← me; b.dest ← where; b.pupType ← dateAltoRequest; SetPupContentsWords[b, 0]; socket.setRemoteAddress[resetAddress]; IF broadcast THEN PupRouterBroadcastThis[b] ELSE socket.put[b]; time ← System.GetGreenwichMeanTime[]; END; b ← socket.get[]; IF b # NIL THEN BEGIN IF b.pupType = dateAltoIs THEN BEGIN network: Network = b.network; info: LONG POINTER TO TimeServerDefs.PupTimeFormat ← LOOPHOLE[@b.pupBody]; before, after: LONG CARDINAL; oldSeconds, oldHours: LONG INTEGER; IF broadcast AND network.netNumber.b = b.source.net AND network.hostNumber = b.source.host THEN BEGIN -- From ME ReturnFreePupBuffer[b]; LOOP; END; before ← System.GetGreenwichMeanTime[]; StatUpdate[]; -- hackery to keep stats from getting confused oldSeconds ← stats[statSeconds]; oldHours ← stats[statHours]; Clock.SetTime[LOOPHOLE[InlineDefs.BcplToMesaLongNumber[info.time]]]; StatUpdate[]; -- we could loose a tick or so, but that's tough stats[statSeconds] ← oldSeconds; stats[statHours] ← oldHours; after ← System.GetGreenwichMeanTime[]; delta ← LOOPHOLE[after, LONG INTEGER] - LOOPHOLE[before, LONG INTEGER]; EXIT; END; ReturnFreePupBuffer[b]; END; ENDLOOP; resetAddress ← b.source; ReturnFreePupBuffer[b]; IF TRUE THEN BEGIN text: STRING = [100]; Time.AppendCurrent[text]; String.AppendString[text, " Time Reset from "L]; AppendHostName[text, resetAddress]; String.AppendString[text, ". Correction was "L]; String.AppendLongDecimal[text, delta]; String.AppendChar[text, '.]; LogString[text]; END; PupSocketDestroy[socket]; resetting ← FALSE; resetText.length ← 0; AppendHostName[resetText, resetAddress]; UpdatePicture[]; END; SetupTimeServerThings: PUBLIC PROCEDURE = BEGIN statText ← StatsStringToIndex["Text Date Requests"]; statTenex ← StatsStringToIndex["Tenex Date Requests"]; statAlto ← StatsStringToIndex["Alto Date Requests"]; END; FindParameters: PROCEDURE RETURNS [ok: BOOLEAN] = BEGIN parmFileName: STRING ← NIL; dstSpecified, zoneSpecified, correctionSpecified: BOOLEAN ← FALSE; sectionName: STRING = "TimeServer"L; token, arg: STRING ← NIL; finger: CARDINAL; parms: Clock.TimeParameters ← Clock.GetTimeParms[]; text: STRING = [100]; GetNextToken: PROCEDURE = BEGIN c: CHARACTER; -- Borrow token token.length ← 0; FOR finger IN [finger..arg.length) DO c ← arg[finger]; SELECT c FROM Ascii.TAB, Ascii.SP => IF (token.length # 0) THEN RETURN; -- flush leading blanks Ascii.CR, ',, ': => BEGIN finger ← finger + 1; RETURN; END; ENDCASE => String.AppendChar[token, c]; ENDLOOP; END; GetDecimal: PROCEDURE RETURNS [n: INTEGER] = BEGIN GetNextToken[]; -- StringToNumber dies on leading +, so flush it here IF token.length > 1 AND token[0] = '+ THEN BEGIN FOR i: CARDINAL IN [0..token.length - 1) DO token[i] ← token[i + 1]; ENDLOOP; token.length ← token.length - 1; END; RETURN[ String.StringToDecimal[ token ! String.InvalidNumber => BEGIN Problem["Decimal number expected: "L, token]; ok ← FALSE; n ← 0; CONTINUE; END]]; END; IF Runtime.IsBound[Indirect.GetParmFileName] THEN parmFileName ← Indirect.GetParmFileName[]; IF parmFileName = NIL THEN parmFileName ← "TimeServer.txt"; ok ← TRUE; IF ~CmFile.OpenSection[parmFileName, sectionName] THEN BEGIN Problem["Can't find [TimeServer] section in "L, parmFileName]; RETURN[FALSE]; END; DO finger ← 0; text.length ← 0; [token, arg] ← CmFile.NextItem[]; SELECT TRUE FROM token = NIL => EXIT; (token.length > 0 AND token[0] = ';) => NULL; String.EquivalentString[token, "CORRECTION"L] => -- <seconds per day> BEGIN correction ← GetDecimal[]; String.AppendDecimal[text, correction]; Problem["The clock correction is "L, text, " seconds per day"L]; Clock.SetCorrection[correction]; correctionSpecified ← TRUE; END; String.EquivalentString[token, "DST"L] => -- <begin day> <end day> BEGIN first: CARDINAL ← GetDecimal[]; last: CARDINAL ← GetDecimal[]; IF first # parms.beginDst OR last # parms.endDst THEN BEGIN Problem["The DST info on this disk is wrong"L]; parms.beginDst ← first; parms.endDst ← last; END; String.AppendString[text, "DST: begin="L]; String.AppendDecimal[text, first]; String.AppendString[text, ", last="L]; String.AppendDecimal[text, last]; Problem[text]; dstSpecified ← TRUE; END; String.EquivalentString[token, "ZONE"L] => -- <sign> <hours>:<minutes> BEGIN hours: INTEGER ← GetDecimal[]; minutes: CARDINAL ← GetDecimal[]; IF hours # parms.zone OR minutes # parms.minutes THEN BEGIN Problem["The time zone info on this disk is wrong"L]; parms.zone ← hours; parms.minutes ← minutes; END; String.AppendString[text, "ZONE: hours="L]; String.AppendDecimal[text, hours]; String.AppendString[text, ", minutes="L]; String.AppendDecimal[text, minutes]; Problem[text]; zoneSpecified ← TRUE; END; ENDCASE => Problem["Unknown parameter: "L, token]; Storage.FreeString[token]; Storage.FreeString[arg]; ENDLOOP; Clock.SetTimeParms[parms]; CmFile.Close[parmFileName]; RETURN[ok AND dstSpecified AND zoneSpecified AND correctionSpecified]; END; Problem: PROCEDURE [one, two, three: STRING ← NIL] = BEGIN text: STRING = [100]; Time.AppendCurrent[text]; String.AppendString[text, " TimeServer: "L]; String.AppendString[text, one]; IF two # NIL THEN String.AppendString[text, two]; IF three # NIL THEN String.AppendString[text, three]; String.AppendChar[text, '.]; LogString[text]; END; Start: FormSW.ProcType = BEGIN PupTimeServerOn[]; END; Stop: FormSW.ProcType = BEGIN PupTimeServerOff[]; END; ResetCommand: FormSW.ProcType = BEGIN Process.Detach[FORK ResetTime[PupTypes.fillInPupAddress]]; END; UseCurrentTime: FormSW.ProcType = BEGIN Clock.SetTime[System.GetGreenwichMeanTime[]]; LogString["Using current time."L]; END; LogString: PROCEDURE [text: STRING] = BEGIN IF msg # NIL THEN Put.Line[msg, text]; Put.Line[NIL, text]; END; MakeSWs: Tool.MakeSWsProc = BEGIN msg ← Tool.MakeMsgSW[window: window, lines: 5]; form ← Tool.MakeFormSW[window: window, formProc: MakeForm]; END; startIX: CARDINAL = 0; stopIX: CARDINAL = 1; MakeForm: FormSW.ClientItemsProcType = BEGIN nParams: CARDINAL = 7; items ← FormSW.AllocateItemDescriptor[nParams]; items[0] ← FormSW.CommandItem[ tag: "Start"L, proc: Start, place: FormSW.newLine]; items[1] ← FormSW.CommandItem[ tag: "Stop"L, proc: Stop, invisible: TRUE, place: FormSW.newLine]; items[2] ← FormSW.CommandItem[ tag: "UseCurrent"L, proc: UseCurrentTime, place: FormSW.newLine]; items[3] ← FormSW.NumberItem[ tag: "Correction"L, value: @correction, boxWidth: 50, readOnly: TRUE]; items[4] ← FormSW.CommandItem[tag: "Reset"L, proc: ResetCommand]; items[5] ← FormSW.BooleanItem[ tag: "Resetting"L, switch: @resetting, readOnly: TRUE]; items[6] ← FormSW.StringItem[ tag: "ResetFrom"L, string: @resetText, readOnly: TRUE]; RETURN[items, TRUE]; END; ClientTransition: ToolWindow.TransitionProcType = BEGIN IF new = inactive THEN msg ← form ← NIL; END; Broom: PROCEDURE [why: Event.Reason] = BEGIN IF useCount = 0 THEN RETURN; SELECT why FROM makeImage, makeCheck => IF running THEN Stopper[]; startImage, restartCheck, continueCheck => IF running THEN Starter[]; ENDCASE => NULL; END; -- Initialization Init[]; SetupTimeServerThings[]; PupTimeServerOn[]; -- This may be undesirable END.