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