-- File: HostWatcher.mesa - last edit: -- AOF 3-Feb-88 17:37:42 -- WIrish 5-Feb-88 12:16:51 -- HGM 7-Oct-85 18:42:40 -- Copyright (C) 1984, 1985, 1988 by Xerox Corporation. All rights reserved. DIRECTORY Ascii USING [CR, SP], CmFile USING [Handle, TableError], Event USING [aboutToSwap], EventTypes USING [aboutToBoot, aboutToBootPhysicalVolume], FormSW USING [ ClientItemsProcType, ProcType, AllocateItemDescriptor, newLine, CommandItem, BooleanItem, StringItem, FindItem, Display, DisplayItem], Heap USING [Create, systemZone], Inline USING [LowHalf], MFile USING [AddNotifyProc, Handle, RemoveNotifyProc], MsgSW USING [Post], Process USING [Pause, SecondsToTicks, Ticks], Put USING [Char, CR, Text, Line, LongDecimal], Runtime USING [GetBcdTime], String USING [ AppendString, AppendChar, Equivalent, AppendNumber, AppendDecimal, AppendLongNumber], StringLookUp USING [noMatch, TableDesc], Supervisor USING [ AddDependency, AgentProcedure, CreateSubsystem, RemoveDependency, SubsystemHandle], System USING [GetGreenwichMeanTime, gmtEpoch, GreenwichMeanTime], Time USING [AppendCurrent, Append, Unpack, Current], Token USING [Boolean, Filtered, FreeTokenString, Item, Line], Tool USING [Create, MakeSWsProc, MakeMsgSW, MakeFormSW, MakeFileSW], ToolWindow USING [TransitionProcType], Window USING [Handle], HostWatcherOps USING [ Info, InfoObject, Mode, State, PokeGateway, PokeChat, PokeFtp, PokeMail, PokeLibrarian, PokeSpruce, PokeEftp, PokePopCorn, UpDown], Indirect USING [Close, GetParmFileName, NextValue, OpenSection], Mailer USING [Level, SendGVMail], NameServerDefs USING [ BumpCacheSize, PupDirServerOn, PupNameServerOn, PupDirServerOff, PupNameServerOff], PupDefs USING [ PupPackageMake, PupPackageDestroy, AppendHostName, AppendErrorPup, PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake, SecondsToTocks, SetPupContentsBytes, GetPupContentsBytes, EnumeratePupAddresses, PupNameTrouble, AccessHandle, Body, DestroyPool, GetBuffer, MakePool, ReturnBuffer], PupRouterDefs USING [ RoutingTableEntry, GetRoutingTableEntry, PupGateInfo, maxHop], PupTypes USING [ eftpReceiveSoc, fillInPupAddress, fillInSocketID, ftpSoc, gatewaySoc, librarianSoc, mailSoc, PupAddress, PupType, PupSocketID, spruceStatusSoc, telnetSoc]; HostWatcher: MONITOR IMPORTS CmFile, Event, FormSW, Heap, Inline, MFile, MsgSW, Process, Put, Runtime, String, Supervisor, System, Time, Token, Tool, HostWatcherOps, Indirect, Mailer, NameServerDefs, PupRouterDefs, PupDefs EXPORTS HostWatcherOps = BEGIN OPEN PupDefs, PupTypes; z: UNCOUNTED ZONE = Heap.Create[1]; herald: LONG STRING = [100]; msg, form, log: Window.Handle ← NIL; broom: Supervisor.SubsystemHandle = Supervisor.CreateSubsystem[Broom]; running, scanning, pleaseStop, debug: BOOLEAN ← FALSE; probing: LONG STRING ← NIL; useCount: CARDINAL ← 0; parmFileName: LONG STRING ← Indirect.GetParmFileName[]; watcher: PROCESS; first: Info ← NIL; troubles: LONG STRING ← NIL; seconds: CARDINAL = 15*60; thirtySeconds: Process.Ticks = Process.SecondsToTicks[30]; wordsPerCacheEntry: CARDINAL = 25; Mode: TYPE = HostWatcherOps.Mode; modeSoc: ARRAY Mode OF PupSocketID = [ gate: [31415, 9265], chat: telnetSoc, ftp: ftpSoc, mail: mailSoc, librarian: librarianSoc, spruce: spruceStatusSoc, eftp: eftpReceiveSoc, popCorn: [0, 0]]; State: TYPE = HostWatcherOps.State; isText: ARRAY State OF STRING ← [ inaccessible: " is ", up: " is ", restarted: " was ", full: " is ", down: " is ", rejecting: " is ", timeout: " is ", unknown: " is "]; stateText: ARRAY State OF STRING ← [ inaccessible: "inaccessible", up: "up", restarted: "restarted", full: "full", down: "down", rejecting: "rejecting", timeout: "not responding", unknown: "unknown"]; Info: TYPE = HostWatcherOps.Info; LastGatewayVanished: ERROR = CODE; CreateHerald: PROCEDURE = BEGIN String.AppendString[herald, "Host Watcher of "L]; Time.Append[herald, Time.Unpack[Runtime.GetBcdTime[]]]; END; HostWatcherOn: ENTRY PROCEDURE = BEGIN IF (useCount ← useCount + 1) = 1 THEN BEGIN Supervisor.AddDependency[client: broom, implementor: Event.aboutToSwap]; MFile.AddNotifyProc[Inspect, [parmFileName, null, readOnly], NIL]; Starter[]; END; UpdatePicture[]; END; Starter: INTERNAL PROCEDURE = BEGIN IF ~FindTargets[] THEN BEGIN running ← FALSE; RETURN; END; running ← TRUE; [] ← PupPackageMake[]; NameServerDefs.PupDirServerOn[]; NameServerDefs.PupNameServerOn[]; pleaseStop ← FALSE; watcher ← FORK Watcher[]; END; HostWatcherOff: ENTRY PROCEDURE = BEGIN IF ~running THEN RETURN; -- No parm file IF useCount # 0 AND (useCount ← useCount - 1) = 0 THEN BEGIN Stopper[]; MFile.RemoveNotifyProc[Inspect, [parmFileName, null, readOnly], NIL]; Supervisor.RemoveDependency[client: broom, implementor: Event.aboutToSwap]; END; UpdatePicture[]; END; Stopper: INTERNAL PROCEDURE = BEGIN running ← FALSE; pleaseStop ← TRUE; JOIN watcher[]; NameServerDefs.PupDirServerOff[]; NameServerDefs.PupNameServerOff[]; PupPackageDestroy[]; ForgetTargets[]; Announce["Killed "L, herald]; END; UpdatePicture: PROCEDURE = BEGIN IF form = NIL THEN RETURN; FormSW.FindItem[form, startIX].flags.invisible ← running; FormSW.FindItem[form, stopIX].flags.invisible ← ~running; FormSW.FindItem[form, probeIX].flags.invisible ← ~scanning; FormSW.Display[form]; END; PrintSummary: ENTRY PROCEDURE = BEGIN state: State; n: LONG CARDINAL; WriteCR[]; WriteCurrentDateAndTime[]; WriteLine[" Current Status:"L]; FOR info: Info ← first, info.next UNTIL info = NIL DO WriteString[info.name]; WriteString[isText[info.state]]; WriteString[stateText[info.state]]; IF info.text.length # 0 THEN BEGIN WriteString[": "L]; WriteString[info.text]; END; WriteLine["."L]; IF info.foundLastGateway AND info.lastHops # 0 THEN BEGIN text: STRING = [100]; AppendGatewayInfo[text, info]; WriteLine[text]; END; IF info.lastLineChanged THEN BEGIN text: STRING = [100]; AppendLineChangedInfo[text, info]; WriteLine[text]; END; IF info.mode = gate AND info.lastHopUsesPhoneLine THEN BEGIN WriteLine["The last hop uses a phone line."L]; END; IF info.state # up THEN BEGIN IF info.lastUp # System.gmtEpoch THEN BEGIN text: STRING = [100]; AppendLastUp[text, info]; WriteLine[text]; END; END; FOR state IN State DO IF (n ← info.counters[state]) = 0 THEN LOOP; LD8[n]; WriteString[" ("]; WriteLongDecimal[n*100/info.probes]; WriteString["%) "]; WriteLine[stateText[state]]; ENDLOOP; ENDLOOP; WriteCR[]; END; LogState: PROCEDURE [info: Info] = BEGIN text: STRING = [200]; Time.AppendCurrent[text]; String.AppendString[text, " "L]; String.AppendString[text, info.name]; String.AppendString[text, isText[info.state]]; String.AppendString[text, stateText[info.state]]; IF info.text.length # 0 THEN BEGIN String.AppendString[text, ": "L]; String.AppendString[text, info.text]; END; LogString[text]; END; FindTargets: INTERNAL PROCEDURE RETURNS [BOOLEAN] = BEGIN modeStrings: ARRAY Mode OF STRING = [ gate: "Gateway"L, chat: "Chat"L, ftp: "FTP"L, mail: "Mail"L, librarian: "Librarian"L, spruce: "Spruce"L, eftp: "EFTP"L, popCorn: "PopCorn"L]; AddTarget: INTERNAL PROCEDURE [server: LONG STRING, mode: Mode] = BEGIN temp: STRING = [200]; AddPair: INTERNAL PROCEDURE [tag, val: LONG STRING] = BEGIN IF val = NIL THEN RETURN; IF temp.length # 0 THEN String.AppendString[temp, ", "L]; String.AppendString[temp, tag]; String.AppendString[temp, ": "L]; String.AppendString[temp, val]; END; AddPair[modeStrings[mode], server]; AddPair["To"L, to]; AddPair["cc"L, cc]; AddPair["Full"L, full]; String.AppendChar[temp, Ascii.CR]; Put.Text[NIL, temp]; AppendItem[server, to, cc, full, mode]; [] ← Token.FreeTokenString[server]; END; to, cc, full: LONG STRING ← NIL; cmFile: CmFile.Handle; Option: TYPE = MACHINE DEPENDENT{ troubles(0), to, cc, full, gate, chat, ftp, mail, librarian, spruce, eftp, popCorn, debug, noMatch(StringLookUp.noMatch)}; DefinedOption: TYPE = Option [troubles..debug]; 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 ← [ troubles: "Troubles"L, to: "to"L, cc: "cc"L, full: "Full"L, gate: "Gateway"L, chat: "Chat"L, ftp: "FTP"L, mail: "Mail"L, librarian: "Librarian"L, spruce: "Printer"L, eftp: "EFTP"L, popCorn: "PopCorn"L, debug: "Debug"L]; cmFile ← Indirect.OpenSection["HostWatcher"L]; IF cmFile = NIL THEN BEGIN Message["Can't find [HostWatcher] section in parameter file"L]; RETURN[FALSE]; END; Announce["Starting "L, herald]; DO option: Option; option ← MyNextValue[cmFile, DESCRIPTOR[optionTable] ! CmFile.TableError => BEGIN IF name[0] # '; THEN Message["Unrecognized parameter: ", name]; RETRY; END]; SELECT option FROM noMatch => EXIT; troubles => BEGIN temp: LONG STRING ← Token.Item[cmFile, FALSE]; z.FREE[@troubles]; troubles ← z.NEW[StringBody[temp.length]]; String.AppendString[troubles, temp]; CheckForRegistry[troubles]; [] ← Token.FreeTokenString[temp]; END; to => BEGIN temp: LONG STRING ← Token.Filtered[cmFile, NIL, Token.Line, whiteSpace, FALSE]; DeleteString[to]; to ← z.NEW[StringBody[temp.length]]; String.AppendString[to, temp]; CheckForRegistry[to]; [] ← Token.FreeTokenString[temp]; END; cc => BEGIN temp: LONG STRING ← Token.Filtered[cmFile, NIL, Token.Line, whiteSpace, FALSE]; DeleteString[cc]; cc ← z.NEW[StringBody[temp.length]]; String.AppendString[cc, temp]; CheckForRegistry[cc]; [] ← Token.FreeTokenString[temp]; END; full => BEGIN temp: LONG STRING ← Token.Filtered[cmFile, NIL, Token.Line, whiteSpace, FALSE]; DeleteString[full]; full ← z.NEW[StringBody[temp.length]]; String.AppendString[full, temp]; CheckForRegistry[full]; [] ← Token.FreeTokenString[temp]; END; gate => AddTarget[Token.Item[cmFile, FALSE], gate]; chat => AddTarget[Token.Item[cmFile, FALSE], chat]; ftp => AddTarget[Token.Item[cmFile, FALSE], ftp]; mail => AddTarget[Token.Item[cmFile, FALSE], mail]; librarian => AddTarget[Token.Item[cmFile, FALSE], librarian]; spruce => AddTarget[Token.Item[cmFile, FALSE], spruce]; eftp => AddTarget[Token.Item[cmFile, FALSE], eftp]; popCorn => AddTarget[Token.Item[cmFile, FALSE], popCorn]; debug => debug ← Token.Boolean[cmFile]; ENDCASE => ERROR; ENDLOOP; Indirect.Close[cmFile]; IF first = NIL THEN BEGIN Message["Oops, no targets"L]; RETURN[FALSE]; END; IF troubles = NIL THEN Message["Please specify somebody in case of TROUBLES"L]; DeleteString[to]; DeleteString[cc]; DeleteString[full]; RETURN[TRUE]; 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; Message: PROCEDURE [one, two, three: LONG STRING ← NIL] = BEGIN text: STRING = [250]; Time.AppendCurrent[text]; String.AppendString[text, " HostWatcher: "L]; String.AppendString[text, one]; IF two # NIL THEN String.AppendString[text, two]; IF three # NIL THEN String.AppendString[text, three]; LogString[text]; END; AppendItem: INTERNAL PROCEDURE [server, to, cc, full: LONG STRING, mode: Mode] = BEGIN info: Info ← z.NEW[HostWatcherOps.InfoObject]; info↑ ← [ name: , to: NIL, cc: NIL, full: NIL, address: [[0], [0], modeSoc[mode]], mode: mode, text: z.NEW[StringBody[256]], next: NIL]; info.name ← z.NEW[StringBody[server.length]]; String.AppendString[info.name, server]; IF first = NIL THEN first ← info ELSE BEGIN where: Info; FOR where ← first, where.next UNTIL where.next = NIL DO ENDLOOP; where.next ← info; END; info.to ← FindString[to]; info.cc ← FindString[cc]; info.full ← FindString[full]; NameServerDefs.BumpCacheSize[wordsPerCacheEntry]; END; FindString: INTERNAL PROCEDURE [s: LONG STRING] RETURNS [t: LONG STRING] = BEGIN t ← s; FOR info: Info ← first, info.next UNTIL info = NIL DO SELECT TRUE FROM String.Equivalent[info.to, s] => BEGIN t ← info.to; EXIT; END; String.Equivalent[info.cc, s] => BEGIN t ← info.cc; EXIT; END; String.Equivalent[info.full, s] => BEGIN t ← info.full; EXIT; END; ENDCASE; ENDLOOP; END; ForgetTargets: INTERNAL PROCEDURE = BEGIN info: Info ← first; UNTIL first = NIL DO info ← first; first ← first.next; DeleteItem[info]; ENDLOOP; z.FREE[@troubles]; END; DeleteItem: INTERNAL PROCEDURE [info: Info] = BEGIN z.FREE[@info.name]; IF info.to # info.cc AND info.to # info.full THEN DeleteString[info.to]; IF info.cc # info.full THEN DeleteString[info.cc]; DeleteString[info.full]; z.FREE[@info.text]; z.FREE[@info]; NameServerDefs.BumpCacheSize[-wordsPerCacheEntry]; END; DeleteString: INTERNAL PROCEDURE [s: LONG STRING] = BEGIN FOR info: Info ← first, info.next UNTIL info = NIL DO IF info.to = s OR info.cc = s OR info.full = s THEN RETURN; ENDLOOP; z.FREE[@s]; END; sequenceNumber: CARDINAL ← 0; NextSequenceNumber: PROCEDURE RETURNS [CARDINAL] = INLINE BEGIN RETURN[sequenceNumber ← sequenceNumber + 1]; END; Watcher: PROCEDURE = BEGIN start: LONG CARDINAL ← System.GetGreenwichMeanTime[]; WatcherWait: PROCEDURE = BEGIN sleep: CARDINAL ← LAST[CARDINAL]; WHILE sleep > seconds DO start ← start + seconds; sleep ← Inline.LowHalf[seconds - (System.GetGreenwichMeanTime[] - start)]; ENDLOOP; FOR i: CARDINAL IN [0..sleep/30) UNTIL pleaseStop DO Process.Pause[Process.SecondsToTicks[30]]; ENDLOOP; END; -- Give NameServer extra time to be sure it has started THROUGH [0..1000) DO Process.Pause[1]; ENDLOOP; UNTIL pleaseStop DO scanning ← TRUE; UpdatePicture[]; PostWithTime["Start of scan..."L]; FOR info: Info ← first, info.next UNTIL info = NIL OR pleaseStop DO probing ← info.name; IF form # NIL THEN FormSW.DisplayItem[form, probeIX]; WatcherPoke[info]; THROUGH [0..100) UNTIL pleaseStop DO Process.Pause[1]; ENDLOOP; ENDLOOP; scanning ← FALSE; probing ← NIL; UpdatePicture[]; PostWithTime["End of scan."L]; IF ~pleaseStop THEN WatcherWait[]; ENDLOOP; END; WatcherPoke: PROCEDURE [info: Info] = BEGIN tries: CARDINAL ← 0; oldState: State ← info.state; oldUpDown: HostWatcherOps.UpDown ← info.upDown; interesting: BOOLEAN; BEGIN ENABLE LastGatewayVanished, PupNameTrouble => BEGIN text: STRING = [100]; Time.AppendCurrent[text]; String.AppendString[text, " Troubles finding last Gateway to "L]; String.AppendString[text, info.name]; LogString[text]; FOR i: CARDINAL IN [0..6) UNTIL pleaseStop DO Process.Pause[thirtySeconds]; ENDLOOP; tries ← tries + 1; IF ~pleaseStop AND tries < 2 THEN RETRY; info.state ← unknown; CONTINUE; END; info.state ← unknown; info.text.length ← 0; MyGetPupAddress[ @info.address, info.name ! PupNameTrouble => BEGIN text: STRING = [100]; String.AppendString[info.text, e]; String.AppendString[text, info.name]; String.AppendString[text, ": "L]; String.AppendString[text, e]; IF msg # NIL THEN MsgSW.Post[msg, text]; info.state ← inaccessible; info.noPath ← TRUE; CONTINUE; END]; IF info.state = inaccessible THEN BEGIN CheckLastGateway[info]; IF ~info.lastGatewayOk THEN info.state ← unknown; END ELSE BEGIN FindLastGateway[info]; SELECT info.mode FROM gate => HostWatcherOps.PokeGateway[info]; chat => HostWatcherOps.PokeChat[info]; ftp => HostWatcherOps.PokeFtp[info]; mail => HostWatcherOps.PokeMail[info]; librarian => HostWatcherOps.PokeLibrarian[info]; spruce => HostWatcherOps.PokeSpruce[info]; eftp => HostWatcherOps.PokeEftp[info]; popCorn => HostWatcherOps.PokePopCorn[info]; ENDCASE => ERROR; END; END; -- of ENABLE IF pleaseStop THEN RETURN; info.counters[info.state] ← info.counters[info.state] + 1; info.probes ← info.probes + 1; UpdateUpDown[info]; interesting ← InterestingStateChange[new: info.upDown, old: oldUpDown] OR info.state = restarted OR (info.state = up AND oldState = up AND info.lastLineChanged); IF interesting OR info.state = full THEN LogState[info]; SELECT TRUE FROM interesting AND info.to # NIL => SendStatus[info.to, info]; info.state = full AND info.full # NIL => SendStatus[info.full, info]; ENDCASE => NULL; IF info.state = up THEN BEGIN info.lastUp ← Time.Current[]; info.noPath ← FALSE; END; END; FindLastGateway: PROCEDURE [info: Info] = BEGIN pool: PupDefs.AccessHandle; soc: PupSocket; rte: PupRouterDefs.RoutingTableEntry; b: PupBuffer ← NIL; body: PupDefs.Body; thisGateway, previousGateway: PupAddress; hops, id: CARDINAL; oldPhoneLine: BOOLEAN ← info.lastHopUsesPhoneLine; info.lastHopUsesPhoneLine ← info.lastLineChanged ← FALSE; rte ← PupRouterDefs.GetRoutingTableEntry[info.address.net]; IF rte = NIL OR rte.context = NIL OR rte.hop > PupRouterDefs.maxHop THEN ERROR LastGatewayVanished; hops ← rte.hop; thisGateway ← previousGateway ← [ [rte.context.pupNetNumber], rte.route, PupTypes.gatewaySoc]; IF hops = 0 THEN BEGIN info.previousHops ← info.lastHops; info.lastHops ← hops; info.lastGateway ← thisGateway; info.lastGatewayOk ← TRUE; RETURN; END; BEGIN ENABLE UNWIND => BEGIN IF b # NIL THEN PupDefs.ReturnBuffer[b]; PupSocketDestroy[soc]; PupDefs.DestroyPool[pool]; END; pool ← PupDefs.MakePool[send: 1, receive: 10]; soc ← PupSocketMake[ PupTypes.fillInSocketID, info.lastGateway, SecondsToTocks[2]]; THROUGH [1..hops) DO hit: BOOLEAN ← FALSE; id ← NextSequenceNumber[]; thisGateway ← GetReasonableAddress[thisGateway]; soc.setRemoteAddress[thisGateway]; THROUGH [0..10) DO b ← PupDefs.GetBuffer[pool, send]; body ← b.pup; body.pupType ← gatewayRequest; SetPupContentsBytes[b, 0]; body.pupID ← [id, id]; soc.put[b]; UNTIL (b ← soc.get[]) = NIL DO body ← b.pup; IF body.pupType = gatewayInfo AND body.pupID = [id, id] THEN BEGIN one: LONG POINTER TO PupRouterDefs.PupGateInfo ← LOOPHOLE[@body.pupWords]; length: CARDINAL = GetPupContentsBytes[b]; n: CARDINAL ← length/(2*SIZE[PupRouterDefs.PupGateInfo]); FOR i: CARDINAL IN [0..n) DO IF one.net = info.address.net THEN BEGIN IF one.hop > PupRouterDefs.maxHop THEN ERROR LastGatewayVanished; previousGateway ← thisGateway; thisGateway ← [one.viaNet, one.viaHost, PupTypes.gatewaySoc]; hit ← TRUE; EXIT; END; one ← one + SIZE[PupRouterDefs.PupGateInfo]; ENDLOOP; END; PupDefs.ReturnBuffer[b]; b ← NIL; IF hit THEN EXIT; ENDLOOP; IF hit THEN EXIT; REPEAT FINISHED => ERROR LastGatewayVanished; ENDLOOP; ENDLOOP; IF info.mode = gate THEN BEGIN -- Check for phone line (only interesting if mode=gate) hit: BOOLEAN ← FALSE; me: PupAddress ← soc.getLocalAddress[]; soc.setRemoteAddress[ [info.address.net, info.address.host, PupTypes.gatewaySoc]]; id ← NextSequenceNumber[]; THROUGH [0..10) DO b ← PupDefs.GetBuffer[pool, send]; body ← b.pup; body.pupType ← gatewayRequest; SetPupContentsBytes[b, 0]; body.pupID ← [id, id]; soc.put[b]; UNTIL (b ← soc.get[]) = NIL DO body ← b.pup; IF body.pupType = gatewayInfo AND body.pupID = [id, id] THEN BEGIN length: CARDINAL = GetPupContentsBytes[b]; n: CARDINAL ← length/(2*SIZE[PupRouterDefs.PupGateInfo]); one: LONG POINTER TO PupRouterDefs.PupGateInfo ← LOOPHOLE[@body.pupWords]; FOR i: CARDINAL IN [0..n) DO IF one.net = me.net THEN BEGIN IF one.viaNet = 7B THEN info.lastHopUsesPhoneLine ← TRUE; hit ← TRUE; END; one ← one + SIZE[PupRouterDefs.PupGateInfo]; ENDLOOP; END; PupDefs.ReturnBuffer[b]; b ← NIL; IF hit THEN EXIT; ENDLOOP; IF hit THEN EXIT; REPEAT FINISHED => NULL; -- It won't talk to us! ENDLOOP; END; BEGIN -- Check for back door problem (only interesting if mode=gate) hit: BOOLEAN ← FALSE; me: PupAddress ← soc.getLocalAddress[]; thisGateway ← GetReasonableAddress[thisGateway]; soc.setRemoteAddress[thisGateway]; id ← NextSequenceNumber[]; THROUGH [0..10) DO b ← PupDefs.GetBuffer[pool, send]; body ← b.pup; body.pupType ← gatewayRequest; SetPupContentsBytes[b, 0]; body.pupID ← [id, id]; soc.put[b]; UNTIL (b ← soc.get[]) = NIL DO body ← b.pup; IF body.pupType = gatewayInfo AND body.pupID = [id, id] THEN BEGIN length: CARDINAL = GetPupContentsBytes[b]; n: CARDINAL ← length/(2*SIZE[PupRouterDefs.PupGateInfo]); one: LONG POINTER TO PupRouterDefs.PupGateInfo ← LOOPHOLE[ @body.pupWords]; FOR i: CARDINAL IN [0..n) DO IF one.net = info.address.net THEN BEGIN IF info.address.net = one.viaNet AND info.address.host = one.viaHost THEN -- our best path to his net is via him, -- hence we are talking to him via his back door BEGIN IF info.mode # gate THEN ERROR; thisGateway ← previousGateway; hops ← hops - 1; END; hit ← TRUE; END; one ← one + SIZE[PupRouterDefs.PupGateInfo]; ENDLOOP; END; PupDefs.ReturnBuffer[b]; b ← NIL; ENDLOOP; IF hit THEN BEGIN IF info.mode = gate AND (oldPhoneLine OR info.lastHopUsesPhoneLine) AND info.previousHops # info.lastHops AND info.lastGateway # thisGateway THEN info.lastLineChanged ← TRUE; info.previousHops ← info.lastHops; info.lastHops ← hops; info.lastGateway ← thisGateway; info.lastGatewayOk ← info.foundLastGateway ← TRUE; EXIT; END; REPEAT FINISHED => ERROR LastGatewayVanished; ENDLOOP; END; END; -- of ENABLE PupSocketDestroy[soc]; PupDefs.DestroyPool[pool]; END; CheckLastGateway: PROCEDURE [info: Info] = BEGIN pool: PupDefs.AccessHandle; soc: PupSocket; b: PupBuffer; body: PupDefs.Body; IF info.lastHops = 0 THEN BEGIN -- directly connected, or never got off the ground info.lastGatewayOk ← info.foundLastGateway; RETURN; END; info.lastGatewayOk ← FALSE; IF info.lastGateway = fillInPupAddress THEN RETURN; -- haven't found it yet pool ← PupDefs.MakePool[send: 1, receive: 10]; soc ← PupSocketMake[ PupTypes.fillInSocketID, info.lastGateway, SecondsToTocks[2]]; THROUGH [0..10) UNTIL info.lastGatewayOk DO b ← PupDefs.GetBuffer[pool, send]; body ← b.pup; body.pupType ← gatewayRequest; SetPupContentsBytes[b, 0]; body.pupID ← [0, 0]; soc.put[b]; UNTIL (b ← soc.get[]) = NIL DO IF b.pup.pupType = gatewayInfo THEN info.lastGatewayOk ← TRUE; PupDefs.ReturnBuffer[b]; ENDLOOP; ENDLOOP; PupSocketDestroy[soc]; PupDefs.DestroyPool[pool]; END; UpdateUpDown: PROCEDURE [info: Info] = BEGIN upTable: ARRAY State OF BOOLEAN = [ FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE]; downTable: ARRAY State OF BOOLEAN = [ FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, FALSE]; up: BOOLEAN ← upTable[info.state]; down: BOOLEAN ← downTable[info.state]; IF info.mode = gate AND info.state = inaccessible AND info.lastGatewayOk THEN down ← TRUE; IF info.mode = popCorn AND info.state = full THEN up ← FALSE; IF up THEN info.upDown ← up; IF down THEN info.upDown ← down; END; InterestingStateChange: PROCEDURE [new, old: HostWatcherOps.UpDown] RETURNS [BOOLEAN] = BEGIN IF old = unknown OR new = unknown THEN RETURN[FALSE]; RETURN[new # old]; END; SendStatus: PROCEDURE [to: LONG STRING, info: Info] = BEGIN subject: STRING = [100]; body: STRING = [500]; state: State; temp: STRING = [25]; n: LONG CARDINAL; Info: PROCEDURE [s: LONG STRING, level: Mailer.Level] = BEGIN copy: LONG STRING ← Heap.systemZone.NEW[StringBody[s.length+2]]; String.AppendString[copy, s]; LogString[copy]; Heap.systemZone.FREE[@copy]; END; String.AppendString[subject, info.name]; String.AppendString[subject, isText[info.state]]; String.AppendString[subject, stateText[info.state]]; String.AppendString[body, info.name]; String.AppendString[body, isText[info.state]]; String.AppendString[body, stateText[info.state]]; IF info.text.length # 0 THEN BEGIN String.AppendString[body, ": "L]; String.AppendString[body, info.text]; END; String.AppendChar[body, '.]; String.AppendChar[body, Ascii.CR]; IF info.foundLastGateway AND info.lastHops # 0 THEN BEGIN AppendGatewayInfo[body, info]; String.AppendChar[body, Ascii.CR]; END; IF info.lastLineChanged THEN BEGIN AppendLineChangedInfo[body, info]; String.AppendChar[body, Ascii.CR]; END; IF info.lastUp # System.gmtEpoch THEN BEGIN AppendLastUp[body, info]; String.AppendChar[body, Ascii.CR]; END; FOR state IN State DO IF (n ← info.counters[state]) = 0 THEN LOOP; temp.length ← 0; String.AppendLongNumber[temp, n, 10]; THROUGH [temp.length..8) DO String.AppendChar[body, Ascii.SP]; ENDLOOP; String.AppendLongNumber[body, n, 10]; String.AppendString[body, " ("]; String.AppendLongNumber[body, n*100/info.probes, 10]; String.AppendString[body, "%) "]; String.AppendString[body, stateText[state]]; String.AppendChar[body, '.]; String.AppendChar[body, Ascii.CR]; ENDLOOP; [] ← Mailer.SendGVMail[ subject, to, info.cc, body, troubles, Info]; END; -- IO things (Write* used only by PrintSummary) WriteChar: PROCEDURE [c: CHARACTER] = BEGIN Put.Char[log, c]; END; WriteCR: PROCEDURE = BEGIN Put.CR[log]; END; WriteString: PROCEDURE [s: LONG STRING] = BEGIN Put.Text[log, s]; END; WriteLine: PROCEDURE [s: LONG STRING] = BEGIN Put.Line[log, s]; END; WriteLongDecimal: PROCEDURE [n: LONG CARDINAL] = BEGIN Put.LongDecimal[log, n]; END; WriteDecimal: PROCEDURE [n: CARDINAL] = INLINE BEGIN WriteNumber[n, 10, 0]; END; WriteOctal: PROCEDURE [n: CARDINAL] = INLINE BEGIN WriteNumber[n, 8, 0]; END; WriteNumber: PROCEDURE [n, radix, width: CARDINAL] = INLINE BEGIN temp: STRING = [25]; String.AppendNumber[temp, n, radix]; THROUGH [temp.length..width) DO WriteChar[' ]; ENDLOOP; WriteString[temp]; END; LD8: PROCEDURE [n: LONG CARDINAL] = BEGIN temp: STRING = [25]; String.AppendLongNumber[temp, n, 10]; THROUGH [temp.length..8) DO WriteChar[' ]; ENDLOOP; WriteString[temp]; END; D8: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 10, 8]; END; O3: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END; O6: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END; O9: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 9]; END; WriteCurrentDateAndTime: PROCEDURE = BEGIN time: STRING = [18]; Time.AppendCurrent[time]; WriteString[time]; END; PostWithTime: PROCEDURE [s: LONG STRING] = BEGIN text: STRING = [120]; IF msg = NIL THEN RETURN; Time.AppendCurrent[text]; String.AppendString[text, " "L]; String.AppendString[text, s]; MsgSW.Post[msg, text]; END; ShowErrorPup: PUBLIC PROCEDURE [b: PupBuffer] = BEGIN text: STRING = [200]; IF msg = NIL THEN RETURN; PupDefs.AppendErrorPup[text, b]; MsgSW.Post[msg, text]; END; AppendGatewayInfo: PROCEDURE [text: LONG STRING, info: Info] = BEGIN String.AppendString[text, "The last gateway"L]; String.AppendString[text, IF info.lastGatewayOk THEN " is "L ELSE " was "L]; AppendHostName[text, info.lastGateway]; String.AppendString[text, " which"L]; String.AppendString[text, IF info.lastGatewayOk THEN " is "L ELSE " was "L]; String.AppendDecimal[text, info.lastHops]; String.AppendString[text, " hop"L]; IF info.lastHops > 1 THEN String.AppendChar[text, 's]; String.AppendString[text, " away."L]; END; AppendLineChangedInfo: PROCEDURE [text: LONG STRING, info: Info] = BEGIN String.AppendString[text, "The last line has recently "L]; String.AppendString[ text, SELECT info.lastHops FROM > info.previousHops => "died"L, < info.previousHops => "recovered"L ENDCASE => "changed"L]; String.AppendChar[text, '.]; END; AppendLastUp: PROCEDURE [text: LONG STRING, info: Info] = BEGIN IF info.noPath THEN String.AppendString[text, "The last time we saw it up was "L] ELSE String.AppendString[text, "The last time it was up was "L]; Time.Append[text, Time.Unpack[info.lastUp], TRUE]; String.AppendChar[text, '.]; END; MyGetPupAddress: PROCEDURE [ him: LONG POINTER TO PupAddress, name: LONG STRING] = BEGIN SkipFlakeyNets: PROCEDURE [her: PupAddress] RETURNS [BOOLEAN] = BEGIN rte: PupRouterDefs.RoutingTableEntry; IF FlakeyNet[her] THEN RETURN[FALSE]; rte ← PupRouterDefs.GetRoutingTableEntry[her.net]; IF rte = NIL OR rte.context = NIL OR rte.hop > PupRouterDefs.maxHop THEN RETURN[FALSE]; him.net ← her.net; him.host ← her.host; IF her.socket # [0, 0] THEN him.socket ← her.socket; RETURN[TRUE]; END; IF EnumeratePupAddresses[name, SkipFlakeyNets] THEN RETURN; ERROR PupNameTrouble["No Route to that Host"L, noRoute]; END; GetReasonableAddress: PROCEDURE [him: PupAddress] RETURNS [PupAddress] = BEGIN hisName: STRING = [40]; IF ~FlakeyNet[him] THEN RETURN[him]; AppendHostName[hisName, him]; MyGetPupAddress[@him, hisName]; RETURN[him]; END; -- Beware: This needs be kept in sync with reality FlakeyNet: PROCEDURE [him: PupAddress] RETURNS [BOOLEAN] = BEGIN IF him.net = 7B OR him.net = 17B OR him.net = 145B THEN RETURN[TRUE]; -- SLA, second SLA or PR RETURN[FALSE]; END; Start: FormSW.ProcType = BEGIN HostWatcherOn[]; END; Stop: FormSW.ProcType = BEGIN HostWatcherOff[]; END; Summary: FormSW.ProcType = BEGIN PrintSummary[]; END; MakeSWs: Tool.MakeSWsProc = BEGIN msg ← Tool.MakeMsgSW[window: window, lines: 5]; form ← Tool.MakeFormSW[window: window, formProc: MakeForm]; log ← Tool.MakeFileSW[window: window, name: "HostWatcher.log$"L]; END; Announce: PROCEDURE [one, two: LONG STRING ← NIL] = BEGIN OPEN String; text: STRING = [200]; Time.AppendCurrent[text]; AppendString[text, " "L]; AppendString[text, one]; IF two # NIL THEN AppendString[text, two]; LogString[text]; END; LogString: PROCEDURE [text: LONG STRING] = BEGIN String.AppendChar[text, '.]; String.AppendChar[text, Ascii.CR]; Put.Text[NIL, text]; IF msg # NIL THEN Put.Text[msg, text]; END; startIX: CARDINAL = 0; stopIX: CARDINAL = 1; runningIX: CARDINAL = 2; probeIX: CARDINAL = 4; MakeForm: FormSW.ClientItemsProcType = BEGIN nParams: CARDINAL = 5; items ← FormSW.AllocateItemDescriptor[nParams]; items[0] ← FormSW.CommandItem[ tag: "Start"L, proc: Start, place: FormSW.newLine, invisible: running]; items[1] ← FormSW.CommandItem[ tag: "Stop"L, proc: Stop, place: FormSW.newLine, invisible: ~running]; items[2] ← FormSW.BooleanItem[ tag: "Running"L, switch: @running, readOnly: TRUE]; items[3] ← FormSW.CommandItem[tag: "Summary"L, proc: Summary]; items[4] ← FormSW.StringItem[ tag: "Probing"L, string: @probing, readOnly: TRUE, invisible: ~scanning]; RETURN[items, TRUE]; END; ClientTransition: ToolWindow.TransitionProcType = BEGIN IF new = inactive THEN msg ← form ← log ← NIL; END; Broom: ENTRY Supervisor.AgentProcedure = BEGIN SELECT event FROM EventTypes.aboutToBoot, EventTypes.aboutToBootPhysicalVolume => IF running THEN Stopper[]; ENDCASE => NULL; 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 running THEN Stopper[]; Starter[]; RETURN[FALSE] END; -- Main Body CreateHerald[]; [] ← Tool.Create[ name: herald, makeSWsProc: MakeSWs, clientTransition: ClientTransition]; HostWatcherOn[]; END.