-- File: HostWatcher.mesa, Last Edit: HGM March 28, 1981 3:27 PM -- Please don't forget to update the herald.... DIRECTORY Ascii USING [CR, SP], Inline USING [LowHalf], Process USING [SetTimeout, SecondsToTicks, Yield], Runtime USING [IsBound], Storage USING [Node, String, Free, FreeString, FreeStringNil], String USING [ AppendString, AppendChar, EquivalentString, AppendNumber, AppendDecimal, AppendLongNumber], System USING [GetGreenwichMeanTime, gmtEpoch, GreenwichMeanTime], Time USING [AppendCurrent, Append, Unpack, Current], CmFile USING [OpenSection, NextItem, Close], Event USING [Item, Reason, AddNotifier], FormSW USING [ ClientItemsProcType, ProcType, AllocateItemDescriptor, newLine, CommandItem, BooleanItem, StringItem, FindItem, Display, DisplayItem], MsgSW USING [Post], Put USING [Char, CR, Text, Line, LongDecimal, LongNumber], Tool USING [Create, MakeSWsProc, MakeMsgSW, MakeFormSW, MakeFileSW], ToolWindow USING [TransitionProcType], Window USING [Handle], File USING [Capability], Indirect USING [GetParmFileName], Mailer USING [Level, SendMail], Slosh USING [AddProcs, Why], NameServerDefs USING [ BumpCacheSize, PupDirServerOn, PupNameServerOn, PupDirServerOff, PupNameServerOff], PupRouterDefs USING [ RoutingTableEntry, GetRoutingTableEntry, PupGateInfo, maxHop], PupDefs USING [ PupPackageMake, PupPackageDestroy, AppendHostName, AppendErrorPup, GetFreePupBuffer, ReturnFreePupBuffer, PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake, SecondsToTocks, SetPupContentsBytes, GetPupContentsBytes, EnumeratePupAddresses, PupNameTrouble], PupTypes USING [ eftpReceiveSoc, fillInPupAddress, fillInSocketID, ftpSoc, gatewaySoc, librarianSoc, mailSoc, PupAddress, PupType, PupSocketID, spruceStatusSoc, telnetSoc], HostWatcherOps USING [ Info, InfoObject, Mode, State, PokeGateway, PokeChat, PokeFtp, PokeMail, PokeLibrarian, PokeSpruce, PokeEftp, UpDown]; HostWatcher: MONITOR IMPORTS Inline, Process, Runtime, Storage, String, System, Time, CmFile, Event, FormSW, MsgSW, Put, Tool, Indirect, Mailer, Slosh, NameServerDefs, PupRouterDefs, PupDefs, HostWatcherOps EXPORTS HostWatcherOps = BEGIN OPEN PupDefs, PupTypes; herald: STRING = "Host Watcher of March 28, 1981"; msg, form, log: Window.Handle ← NIL; eventItem: Event.Item ← [eventMask: 177777B, eventProc: Broom]; running, scanning, pleaseStop, debug: BOOLEAN ← FALSE; probing: STRING ← NIL; useCount: CARDINAL ← 0; watcher: PROCESS; first: Info ← NIL; troubles: STRING ← NIL; pause: CONDITION; seconds: CARDINAL = 15*60; wordsPerCacheEntry: CARDINAL = 25; Mode: TYPE = HostWatcherOps.Mode; modeSoc: ARRAY Mode OF PupSocketID = [ [31415, 9265], telnetSoc, ftpSoc, mailSoc, librarianSoc, spruceStatusSoc, eftpReceiveSoc]; State: TYPE = HostWatcherOps.State; stateText: ARRAY State OF STRING ← [ inaccessible: "inaccessible", up: "up", full: "full", down: "down", rejecting: "rejecting", timeout: "not responding", unknown: "unknown"]; Info: TYPE = HostWatcherOps.Info; LastGatewayVanished: ERROR = CODE; HostWatcherOn: PROCEDURE = BEGIN BumpUseCount: ENTRY PROCEDURE RETURNS [BOOLEAN] = INLINE BEGIN RETURN[(useCount ← useCount + 1) = 1]; END; IF BumpUseCount[] THEN BEGIN running ← TRUE; Starter[]; END; UpdatePicture[]; END; Starter: PROCEDURE = BEGIN IF ~FindTargets[] THEN BEGIN running ← FALSE; useCount ← 0; RETURN; END; PupPackageMake[]; NameServerDefs.PupDirServerOn[]; NameServerDefs.PupNameServerOn[]; pleaseStop ← FALSE; watcher ← FORK Watcher[]; END; HostWatcherOff: PROCEDURE = BEGIN UnbumpUseCount: ENTRY PROCEDURE RETURNS [BOOLEAN] = INLINE BEGIN RETURN[useCount # 0 AND (useCount ← useCount - 1) = 0]; END; IF UnbumpUseCount[] THEN BEGIN running ← FALSE; Stopper[]; END; UpdatePicture[]; END; Stopper: PROCEDURE = BEGIN StopperLocked: ENTRY PROCEDURE = INLINE BEGIN NOTIFY pause; END; pleaseStop ← TRUE; StopperLocked[]; 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[" is "L]; 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, " is "L]; String.AppendString[text, stateText[info.state]]; IF info.text.length # 0 THEN BEGIN String.AppendString[text, ": "L]; String.AppendString[text, info.text]; END; String.AppendString[text, "."L]; LogString[text]; END; FindTargets: ENTRY PROCEDURE RETURNS [BOOLEAN] = BEGIN modeStrings: ARRAY Mode OF STRING = [ "Gateway"L, "Chat"L, "FTP"L, "Mail"L, "Librarian"L, "Spruce"L, "EFTP"L]; AddTarget: INTERNAL PROCEDURE [server: STRING, mode: Mode] = BEGIN temp: STRING = [200]; AddPair: INTERNAL PROCEDURE [tag, val: 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]; Put.Line[NIL, temp]; AppendItem[arg, to, cc, full, mode]; END; parmFileName: STRING ← NIL; sectionName: STRING = "HostWatcher"L; token, arg: STRING ← NIL; to, cc, full: STRING ← NIL; IF Runtime.IsBound[Indirect.GetParmFileName] THEN parmFileName ← Indirect.GetParmFileName[]; IF parmFileName = NIL THEN parmFileName ← "HostWatcher.txt"L; IF ~CmFile.OpenSection[parmFileName, sectionName] THEN BEGIN Problem["Can't find [HostWatcher] section."L]; RETURN[FALSE]; END; Announce["Starting "L, herald]; DO [token, arg] ← CmFile.NextItem[]; SELECT TRUE FROM token = NIL => EXIT; String.EquivalentString[token, "Troubles"L] => BEGIN CheckForRegistry[arg]; Storage.FreeString[troubles]; troubles ← arg; Announce["In case of trouble, mail will be returned to: "L, troubles]; END; String.EquivalentString[token, "To"L] => BEGIN CheckForRegistry[arg]; DeleteString[to]; to ← FindString[arg]; END; String.EquivalentString[token, "cc"L] => BEGIN CheckForRegistry[arg]; DeleteString[cc]; cc ← FindString[arg]; END; String.EquivalentString[token, "Full"L] => BEGIN CheckForRegistry[arg]; DeleteString[full]; full ← FindString[arg]; END; String.EquivalentString[token, "Debug"L] => BEGIN debug ← String.EquivalentString[arg, "TRUE"L]; Storage.FreeString[arg]; END; String.EquivalentString[token, "Gateway"L] => AddTarget[arg, gate]; String.EquivalentString[token, "Chat"L] => AddTarget[arg, chat]; String.EquivalentString[token, "FTP"L] => AddTarget[arg, ftp]; String.EquivalentString[token, "Mail"L] => AddTarget[arg, mail]; String.EquivalentString[token, "Librarian"L] => AddTarget[arg, librarian]; String.EquivalentString[token, "Printer"L] => AddTarget[arg, spruce]; String.EquivalentString[token, "EFTP"L] => AddTarget[arg, eftp]; ENDCASE => BEGIN IF token[0] # '; THEN Problem["Unknown keyword: "L, token]; Storage.FreeString[arg]; END; Storage.FreeString[token]; ENDLOOP; CmFile.Close[parmFileName]; IF first = NIL THEN BEGIN Problem["Oops, no targets"L]; RETURN[FALSE]; END; IF troubles = NIL THEN Problem["Please specify somebody in case of TROUBLES"L]; DeleteString[to]; DeleteString[cc]; DeleteString[full]; RETURN[TRUE]; END; CheckForRegistry: PROCEDURE [s: STRING] = BEGIN dot: BOOLEAN ← FALSE; FOR i: CARDINAL IN [0..s.length) DO SELECT s[i] FROM '. => dot ← TRUE; ', => BEGIN IF ~dot THEN BEGIN Problem["Registry expected in arg: "L, s]; RETURN; END; dot ← FALSE; END; ENDCASE => NULL; ENDLOOP; IF ~dot THEN BEGIN Problem["Registry expected in arg: "L, s]; RETURN; END; END; Problem: PROCEDURE [one, two, three: STRING ← NIL] = BEGIN text: STRING = [100]; 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: STRING, mode: Mode] = BEGIN info: Info ← Storage.Node[SIZE[HostWatcherOps.InfoObject]]; info↑ ← [ name: server, to: to, cc: cc, full: full, address: [[0], [0], modeSoc[mode]], mode: mode, text: Storage.String[100], next: NIL]; 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; NameServerDefs.BumpCacheSize[wordsPerCacheEntry]; END; FindString: INTERNAL PROCEDURE [s: STRING] RETURNS [t: STRING] = BEGIN t ← s; FOR info: Info ← first, info.next UNTIL info = NIL DO SELECT TRUE FROM String.EquivalentString[info.to, s] => BEGIN t ← info.to; EXIT; END; String.EquivalentString[info.cc, s] => BEGIN t ← info.cc; EXIT; END; String.EquivalentString[info.full, s] => BEGIN t ← info.full; EXIT; END; ENDCASE; ENDLOOP; IF s # t THEN Storage.FreeString[s]; END; ForgetTargets: ENTRY PROCEDURE = BEGIN info: Info ← first; UNTIL first = NIL DO info ← first; first ← first.next; DeleteItem[info]; ENDLOOP; troubles ← Storage.FreeStringNil[troubles]; END; DeleteItem: INTERNAL PROCEDURE [info: Info] = BEGIN Storage.FreeString[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]; Storage.FreeString[info.text]; Storage.Free[info]; NameServerDefs.BumpCacheSize[-wordsPerCacheEntry]; END; DeleteString: INTERNAL PROCEDURE [s: 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; Storage.FreeString[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: ENTRY PROCEDURE = BEGIN sleep: CARDINAL ← LAST[CARDINAL]; WHILE sleep > seconds DO start ← start + seconds; sleep ← Inline.LowHalf[seconds - (System.GetGreenwichMeanTime[] - start)]; ENDLOOP; Process.SetTimeout[@pause, Process.SecondsToTicks[sleep]]; WAIT pause; END; -- Give NameServer extra time to be sure it has started THROUGH [0..1000) DO Process.Yield[]; 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.Yield[]; ENDLOOP; ENDLOOP; scanning ← FALSE; probing ← NIL; UpdatePicture[]; PostWithTime["End of scan."L]; IF ~pleaseStop THEN WatcherWait[]; ENDLOOP; END; WatcherPoke: ENTRY 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]; Process.SetTimeout[@pause, Process.SecondsToTicks[180]]; WAIT pause; 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]; 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 = up AND oldState = up AND info.lastLineChanged); IF interesting OR info.state = full THEN LogState[info]; IF interesting AND info.to # NIL THEN SendStatus[info.to, info]; IF info.state = full AND info.full # NIL THEN SendStatus[info.full, info]; IF info.state = up THEN BEGIN info.lastUp ← Time.Current[]; info.noPath ← FALSE; END; END; WatcherWait: ENTRY PROCEDURE [start: LONG CARDINAL] = BEGIN sleep: CARDINAL ← LAST[CARDINAL]; sleep ← Inline.LowHalf[seconds - (System.GetGreenwichMeanTime[] - start)]; WHILE sleep > seconds DO start ← start + seconds; sleep ← Inline.LowHalf[seconds - (System.GetGreenwichMeanTime[] - start)]; ENDLOOP; Process.SetTimeout[@pause, Process.SecondsToTicks[sleep]]; WAIT pause; END; FindLastGateway: PROCEDURE [info: Info] = BEGIN rte: PupRouterDefs.RoutingTableEntry; soc: PupSocket; b: PupBuffer ← NIL; 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.network = NIL OR rte.hop > PupRouterDefs.maxHop THEN ERROR LastGatewayVanished; hops ← rte.hop; thisGateway ← previousGateway ← [ [rte.network.netNumber.b], 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 PupSocketDestroy[soc]; IF b # NIL THEN ReturnFreePupBuffer[b]; END; 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 ← GetFreePupBuffer[]; b.pupType ← gatewayRequest; SetPupContentsBytes[b, 0]; b.pupID ← [id, id]; soc.put[b]; UNTIL (b ← soc.get[]) = NIL DO IF b.pupType = gatewayInfo AND b.pupID = [id, id] THEN BEGIN one: LONG POINTER TO PupRouterDefs.PupGateInfo ← LOOPHOLE[@b.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; ReturnFreePupBuffer[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 ← GetFreePupBuffer[]; b.pupType ← gatewayRequest; SetPupContentsBytes[b, 0]; b.pupID ← [id, id]; soc.put[b]; UNTIL (b ← soc.get[]) = NIL DO IF b.pupType = gatewayInfo AND b.pupID = [id, id] THEN BEGIN length: CARDINAL = GetPupContentsBytes[b]; n: CARDINAL ← length/(2*SIZE[PupRouterDefs.PupGateInfo]); one: LONG POINTER TO PupRouterDefs.PupGateInfo ← LOOPHOLE[@b.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; ReturnFreePupBuffer[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 ← GetFreePupBuffer[]; b.pupType ← gatewayRequest; SetPupContentsBytes[b, 0]; b.pupID ← [id, id]; soc.put[b]; UNTIL (b ← soc.get[]) = NIL DO IF b.pupType = gatewayInfo AND b.pupID = [id, id] THEN BEGIN length: CARDINAL = GetPupContentsBytes[b]; n: CARDINAL ← length/(2*SIZE[PupRouterDefs.PupGateInfo]); one: LONG POINTER TO PupRouterDefs.PupGateInfo ← LOOPHOLE[@b.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; ReturnFreePupBuffer[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]; END; CheckLastGateway: PROCEDURE [info: Info] = BEGIN soc: PupSocket; b: PupBuffer; 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 soc ← PupSocketMake[ PupTypes.fillInSocketID, info.lastGateway, SecondsToTocks[2]]; THROUGH [0..10) UNTIL info.lastGatewayOk DO b ← GetFreePupBuffer[]; b.pupType ← gatewayRequest; SetPupContentsBytes[b, 0]; b.pupID ← [0, 0]; soc.put[b]; UNTIL (b ← soc.get[]) = NIL DO IF b.pupType = gatewayInfo THEN info.lastGatewayOk ← TRUE; ReturnFreePupBuffer[b]; ENDLOOP; ENDLOOP; PupSocketDestroy[soc]; END; UpdateUpDown: PROCEDURE [info: Info] = BEGIN upTable: ARRAY State OF BOOLEAN = [ FALSE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE]; downTable: ARRAY State OF BOOLEAN = [ 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 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: STRING, info: Info] = BEGIN subject: STRING = [100]; body: STRING = [350]; state: State; temp: STRING = [25]; n: LONG CARDINAL; Info: PROCEDURE [s: STRING, level: Mailer.Level] = {LogString[s]; }; String.AppendString[subject, info.name]; String.AppendString[subject, " is "L]; String.AppendString[subject, stateText[info.state]]; String.AppendString[body, info.name]; String.AppendString[body, " is "L]; 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.SendMail[ "HostWatcher"L, subject, to, info.cc, body, troubles, NIL, 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: STRING] = BEGIN Put.Text[log, s]; END; WriteLine: PROCEDURE [s: 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: 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: 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: 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: 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: POINTER TO PupAddress, name: 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.network = 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; FlakeyNet: PROCEDURE [him: PupAddress] RETURNS [BOOLEAN] = BEGIN -- SLA, SLA2 or PR IF him.net = 7B OR him.net = 17B OR him.net = 24B THEN RETURN[TRUE]; 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: STRING ← NIL] = BEGIN OPEN String; text: STRING = [200]; Time.AppendCurrent[text]; AppendString[text, " "L]; AppendString[text, one]; IF two # NIL THEN AppendString[text, two]; AppendChar[text, '.]; LogString[text]; END; LogString: PROCEDURE [text: STRING] = BEGIN IF msg # NIL THEN Put.Line[msg, text]; Put.Line[NIL, text]; END; Checker: PROCEDURE [why: Slosh.Why, fileName: STRING, file: File.Capability] = BEGIN parmFileName: STRING ← NIL; IF why # arrived THEN RETURN; IF Runtime.IsBound[Indirect.GetParmFileName] THEN parmFileName ← Indirect.GetParmFileName[]; IF parmFileName = NIL THEN parmFileName ← "HostWatcher.txt"L; IF String.EquivalentString[parmFileName, fileName] AND running THEN BEGIN Stopper[]; Starter[]; END; 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: PROCEDURE [why: Event.Reason] = BEGIN SELECT why FROM makeImage, makeCheck, stopMesa => IF running THEN Stopper[]; startImage, restartCheck, continueCheck => IF running THEN Starter[]; ENDCASE => NULL; END; -- Main Body [] ← Tool.Create[ name: herald, makeSWsProc: MakeSWs, clientTransition: ClientTransition]; Event.AddNotifier[@eventItem]; Slosh.AddProcs[Checker]; HostWatcherOn[]; END.