-- FileLookupImpl.mesa
-- M. D. Schroeder, September 7, 1982 11:50 am

DIRECTORY
ConvertUnsafe: TYPE USING [AppendRope],
FileLookup: TYPE,
Mopcodes: TYPE USING [zEXCH],
Process: TYPE USING [Detach, SecondsToTicks],
PupDefs: TYPE USING [GetFreePupBuffer, GetHopsToNetwork, GetPupAddress,
MsToTocks, PupAddress, PupNameTrouble, PupBuffer, PupSocket,
PupSocketDestroy, PupSocketMake, ReturnFreePupBuffer, SetPupContentsBytes],
PupTypes: TYPE USING [fillInSocketID, Pair, PupSocketID, PupType],
Rope: TYPE USING [ Cat, Compare, Fetch, Index, Length, ROPE, Substr ],
Runtime: TYPE USING [BoundsFault],
System: TYPE USING [GetGreenwichMeanTime, GreenwichMeanTime];

FileLookupImpl: MONITOR
IMPORTS ConvertUnsafe, Process, PupDefs, Rope, Runtime, System
EXPORTS FileLookup =

BEGIN OPEN FileLookup;

ROPE: TYPE = Rope.ROPE;
ReverseCardinal: TYPE = MACHINE DEPENDENT RECORD [high, low: CARDINAL];
ServerInfo: TYPE = RECORD [
name: ROPE,
addr: PupDefs.PupAddress,
state: Result, -- can be noResponse, noSuchPort, or ok --
time: System.GreenwichMeanTime, -- GMT of last ok or first not ok --
next: REF ServerInfo];

serverList: REF ServerInfo ← NIL; -- list of known servers --
packetCount: LONG CARDINAL ← 0; -- outgoing; also used as pupID --

LookupFile: PUBLIC SAFE PROC [server, file: ROPE ]
RETURNS [result: Result, version: CARDINAL,
create: System.GreenwichMeanTime, count: LONG CARDINAL] = TRUSTED
BEGIN
sI: REF ServerInfo;
[result, sI] ← FindServer[server];
IF result = ok THEN BEGIN
[result, version, create, count] ← DoFileLookup[sI.addr, IFSSyntax[file]];
SetState[sI, result];
END;
END; --RemoteFileLookup--

FindServer: ENTRY PROC [server: ROPE] RETURNS [result: Result, info: REF ServerInfo] =
-- noResponse => NLS didn't respond, no route to server, --
-- or server didn't respond last time --
-- noSuchPort => server responded with noSuchPort last time; --
-- noSuchName => server not in NLS data base; info may be NIL --
-- ok => info.addr is valid and server may be up; try a DoFileLookup --
BEGIN
addr: PupDefs.PupAddress;
serverStr: STRING ← [32];
result ← ok;
FOR info ← serverList, info.next UNTIL info = NIL DO
IF Rope.Compare[server, info.name, FALSE] = equal
THEN {result ← info.state; RETURN};
ENDLOOP;
-- didn't find the entry, so make one --
ConvertUnsafe.AppendRope[from: server, to: serverStr
! Runtime.BoundsFault => CONTINUE];
PupDefs.GetPupAddress[@addr, serverStr ! PupDefs.PupNameTrouble =>
{SELECT code FROM
noRoute, noResponse => result ← noResponse;
errorFromServer => result ← noSuchName;
ENDCASE => ERROR;
CONTINUE} ];
IF result = noSuchName THEN RETURN;
info ← NEW [ServerInfo ← [name: server, addr: addr, state: result,
time: System.GetGreenwichMeanTime[], next: serverList]];
IF serverList = NIL THEN Process.Detach[FORK ServerCollector[]];
serverList ← info;
END; --FindServer--

DoFileLookup: PROC [addr: PupDefs.PupAddress, file: ROPE ]
RETURNS [result: Result, version: CARDINAL,
create: System.GreenwichMeanTime, count: LONG CARDINAL] =
BEGIN
LookupFileType: PupTypes.PupType = LOOPHOLE[200B];
LookupFileReplyType: PupTypes.PupType = LOOPHOLE[201B];
LookupFileErrorType: PupTypes.PupType = LOOPHOLE[202B];
LookupFileSocket: PupTypes.PupSocketID = [0, 61B];
tries: CARDINAL = 4;
msWait: CARDINAL = 2000+500*MIN[8, PupDefs.GetHopsToNetwork[addr.net]];
s: PupDefs.PupSocket;
fileNameChars: CARDINAL = Rope.Length[file];
result ← noResponse; -- assume result --
addr.socket ← LookupFileSocket;
s ← PupDefs.PupSocketMake [local: PupTypes.fillInSocketID, remote: addr,
ticks: PupDefs.MsToTocks[msWait] ];
THROUGH [1..tries] DO
b: PupDefs.PupBuffer = PupDefs.GetFreePupBuffer[];
b.pupType ← LookupFileType;
b.pupID ← PupIDFromLongCardinal[packetCount ← packetCount + 1];
FOR i: CARDINAL IN [0..fileNameChars) DO
b.pupChars[i] ← Rope.Fetch[file, i];
ENDLOOP;
PupDefs.SetPupContentsBytes[b, fileNameChars];
s.put[b];
BEGIN
r: PupDefs.PupBuffer = s.get[];
IF r = NIL THEN LOOP; -- no response yet
SELECT r.pupType FROM
LookupFileReplyType => BEGIN
rPtr: LONG POINTER TO MACHINE DEPENDENT RECORD
[v: CARDINAL, c, l: ReverseCardinal] = LOOPHOLE[@r.pupBody];
result ← ok;
version ← rPtr.v;
create ← GMTFromReverseCardinal[rPtr.c];
count ← LongCardFromReverseCardinal[rPtr.l];
END;
LookupFileErrorType => result ← noSuchName;
error => SELECT r.errorCode FROM
noProcessPupErrorCode => result ← noSuchPort;
cantGetTherePupErrorCode => NULL;
ENDCASE => {PupDefs.ReturnFreePupBuffer[r]; LOOP};
ENDCASE =>
{PupDefs.ReturnFreePupBuffer[r]; LOOP};
PupDefs.ReturnFreePupBuffer[r];
EXIT;
END;
ENDLOOP;
PupDefs.PupSocketDestroy[s];
END; --DoFileLookup--

IFSSyntax: PROC [name: ROPE] RETURNS [iName: ROPE] =
BEGIN
start, end: INT;
length: INT = Rope.Length[name];
start ← 0;
IF (end ← Rope.Index[name, start, "/"]) = length THEN RETURN [name];
iName ← "<";
DO
iName ← Rope.Cat[iName, Rope.Substr[name, start, end-start]];
IF end = length THEN EXIT;
iName ← Rope.Cat[iName, ">"];
start ← end + 1;
end ← Rope.Index[name, start, "/"];
ENDLOOP;
END; --IFSSyntax--

PupIDFromLongCardinal: PROC [s: LONG CARDINAL] RETURNS [PupTypes.Pair] =
MACHINE CODE {Mopcodes.zEXCH};

GMTFromReverseCardinal: PROC [r: ReverseCardinal] RETURNS [System.GreenwichMeanTime] =
MACHINE CODE {Mopcodes.zEXCH};

LongCardFromReverseCardinal: PROC [r: ReverseCardinal] RETURNS [LONG CARDINAL] =
MACHINE CODE {Mopcodes.zEXCH};

SetState: ENTRY PROC [info: REF ServerInfo, state: Result] =
BEGIN
info.time ← System.GetGreenwichMeanTime[];
SELECT state FROM
noResponse, noSuchPort => info.state ← state;
ENDCASE; -- info.state will be ok already --
END; --SetState--

ServerCollector: ENTRY PROC =
BEGIN
forOneMinute: CONDITION ← [Process.SecondsToTicks[60]];
UNTIL serverList = NIL DO
last: REF ServerInfo ← NIL;
info: REF ServerInfo;
WAIT forOneMinute;
FOR info ← serverList, info.next UNTIL info = NIL DO
age: LONG CARDINAL = System.GetGreenwichMeanTime[] - info.time;
IF (info.state # ok AND age > 300) OR (age > 1800)
THEN -- remove this entry from the list --
IF last=NIL THEN serverList ← info.next ELSE last.next ← info.next
ELSE -- entry hasn't aged enough --
last ← info;
ENDLOOP;
ENDLOOP;
END; --ServerCollector--

END...