-- 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.