FSRemoteFileImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Russ Atkinson, November 7, 1984 11:53:29 am PST
Schroeder, December 11, 1983 3:01 pm
Levin, September 22, 1983 1:04 pm
HGM, February 8, 1984 11:55:19 pm PST
Bob Hagmann, May 16, 1984 1:04:56 pm PDT
Doug Wyatt, November 27, 1984 3:53:09 pm PST -- added allDown test to Info
DIRECTORY
BasicTime USING [GMT, Now, nullGMT, Pack, Unpack, Unpacked],
FS USING [Error, ErrorGroup, InfoProc, NameProc],
FSBackdoor USING [ErrorCode, ProduceError, Version],
FSName USING [BangStarFile, BangVersionFile, VersionFromRope],
FSPseudoServers,
FSRemoteFile USING [ConfirmProc, Lookup, LookupResult],
FSReport USING [UnknownFile],
IO USING [STREAM],
Process USING [Detach, Seconds, SecondsToTicks],
Rope USING [Cat, Concat, Equal, Fetch, Flatten, Length, Match, ROPE],
RuntimeError USING [UNCAUGHT],
STP USING [Close, ConfirmProcType, Continue, Create, Delete, DesiredProperties, Enumerate, Error, ErrorCode, FileInfo, GetFileInfo, Handle, IsOpen, Login, NoteFileProcType, Open, Rename, Retrieve, SetDesiredProperties, SetDirectory, Store, ValidProperties],
UserCredentials USING [Get];
FSRemoteFileImpl:
CEDAR
MONITOR
IMPORTS BasicTime, FS, FSBackdoor, FSName, FSPseudoServers, FSRemoteFile, FSReport, Process, Rope, RuntimeError, STP, UserCredentials
EXPORTS FSRemoteFile
= {
GMT: TYPE = BasicTime.GMT;
Handle: TYPE = STP.Handle;
ROPE: TYPE = Rope.ROPE;
PseudoServerList: TYPE = FSPseudoServers.PseudoServerList;
STREAM: TYPE = IO.STREAM;
Version: TYPE = FSBackdoor.Version;
Exported to FSRemoteFile
Delete:
PUBLIC
PROC
[server, file: ROPE, wantedCreatedTime: GMT, proc: FSRemoteFile.ConfirmProc] = {
Confirm:
STP.ConfirmProcType = {
[file: ROPE] RETURNS [answer: {do, skip, abort}, localStream: STREAM]
info: STP.FileInfo = STP.GetFileInfo[h];
created: GMT = FTPTimeToGMT[info.create];
SELECT
TRUE
FROM
matchFound => answer ← abort;
NOT timeSearch
OR wantedCreatedTime = created => {
answer ← IF proc [FSName.VersionFromRope[info.version]] THEN do ELSE abort;
matchFound ← TRUE;
};
ENDCASE => answer ← skip;
};
matchFound: BOOLEAN ← FALSE;
timeSearch: BOOLEAN = (wantedCreatedTime # BasicTime.nullGMT);
stpCode: STP.ErrorCode ← noSuchFile;
h: Handle ← NIL;
innerDelete:
PROC
RETURNS [ok:
BOOL ←
TRUE] = {
tServer: ROPE ← FSPseudoServers.TranslateForWrite[UnbracketServer[server]];
h ← NIL;
{
ENABLE UNWIND => ReturnConnection[tServer, h];
h ← GetConnection[tServer, file, FALSE];
STP.Delete[h, file, Confirm
! STP.Error => IF code = noSuchFile THEN CONTINUE ];
IF
NOT matchFound
THEN {
IF timeSearch
THEN {
file ← FSName.BangStarFile[file];
STP.Delete[h, file, Confirm];
};
IF NOT matchFound THEN {stpCode ← noSuchFile; RETURN [FALSE]};
};
};
ReturnConnection[tServer, h];
};
IF innerDelete[ ! STP.Error => {stpCode ← code; CONTINUE}] THEN RETURN;
ReportSTPError[stpCode, server, file, wantedCreatedTime];
};
EnumerateForInfo:
PUBLIC
PROC [server, pattern:
ROPE, proc: FS.InfoProc] = {
NoteInfo: LocalEnumProc = {
[file: ROPE] RETURNS [continue: BOOL]
info: STP.FileInfo = STP.GetFileInfo[h];
created: BasicTime.GMT = FTPTimeToGMT[info.create];
continue ←
IF proc[Rope.Concat[serverB, file],
NIL, created, info.size, 0]
THEN yes ELSE no;
};
serverB: ROPE = BracketServer[server];
InnerEnumerate[server, pattern, NoteInfo, FALSE];
};
EnumerateForNames:
PUBLIC
PROC [server, pattern:
ROPE, proc: FS.NameProc] = {
NoteName: LocalEnumProc = {
[file: ROPE] RETURNS [continue: BOOL]
continue ← IF proc[Rope.Concat[serverB, file]] THEN yes ELSE no;
};
serverB: ROPE = BracketServer[server];
InnerEnumerate[server, pattern, NoteName, TRUE];
};
LocalEnumProc: TYPE = PROC [h: Handle, file: ROPE] RETURNS [continue: STP.Continue];
InnerEnumerate:
PUBLIC
PROC [server, pattern:
ROPE, Note: LocalEnumProc, namesOnly:
BOOL] = {
This helpful procedure allows enumeration for either names or information.
stpCode: STP.ErrorCode ← noSuchFile;
h: Handle ← NIL;
notedOne: BOOL ← FALSE;
innerNote:
STP.NoteFileProcType = {
notedOne ← TRUE;
IF
NOT Rope.Match["<*", file]
THEN file ← Rope.Concat["<>", file];
RRA: certain STP servers, like Nebula, do not prepend "<>" if they do not support directories. Since FS demands this syntax, we should make sure that it is here.
continue ← Note[h, file];
};
innerBlock:
PROC [eachName:
ROPE] = {
h ← NIL;
{
ENABLE UNWIND => ReturnConnection[eachName, h];
h ← GetConnection[eachName, pattern, namesOnly];
STP.Enumerate[h, pattern, innerNote];
};
ReturnConnection[server, h];
};
serverList: LIST OF ROPE = FSPseudoServers.TranslateForRead[server];
replicated: BOOL = FSPseudoServers.AvoidRemoteCheck[server];
FOR each:
LIST
OF
ROPE ← serverList, each.rest
WHILE each #
NIL
DO
innerBlock[each.first
!
STP.Error =>
SELECT
TRUE
FROM
code = noSuchFile =>
If we do not get a match then we yield nothing. If replicated, then there is also no reason to try other servers, since replication should not affect presence. Otherwise, we go on to the next server, provided that we have not yielded something from this server.
SELECT
TRUE
FROM
replicated, notedOne => GO TO NotFound;
ENDCASE => LOOP;
notedOne => {
If we have seen at least one file then we do NOT try other servers, which would royally screw up the enumeration.
stpCode ← code; EXIT};
each = serverList => {
If the auxilliary servers do not work, we report the error from the first server only. So we remember the error code here.
stpCode ← code; LOOP};
ENDCASE =>
Any other errors during auxiliiary severs searches just go on to try the next server in the list (if any).
LOOP;
];
If we continue here, we have enumerated the pattern all right, and have returned the connection.
RETURN;
ENDLOOP;
If we continue here, we have an error of some kind during the enumeration, so we report it.
ReportSTPError[stpCode, server, pattern, BasicTime.nullGMT];
EXITS NotFound => {};
};
Info:
PUBLIC
PROC
[server, file: ROPE, wantedCreatedTime: GMT]
RETURNS [version: Version, bytes: INT, created: GMT] = {
ReportError:
PROC [stpCode:
STP.ErrorCode] = {
ReportSTPError[stpCode, server, file, wantedCreatedTime];
};
result: FSRemoteFile.LookupResult;
serverList: LIST OF ROPE = FSPseudoServers.TranslateForRead[server];
replicated: BOOL = FSPseudoServers.AvoidRemoteCheck[server];
allDown: BOOL ← TRUE;
FOR each:
LIST
OF
ROPE ← serverList, each.rest
WHILE each #
NIL
DO
eachServer: ROPE ← each.first;
last: BOOL ← each.rest = NIL;
[result, version, created, bytes] ← FSRemoteFile.Lookup[eachServer, file];
IF result#noResponse THEN allDown ← FALSE;
SELECT result
FROM
noSuchFile => {
If there is no such file and we wanted anything, then we will never get anything, regardless of the replication. Otherwise we have a more complicated case, so we exit the quick kill.
IF NOT replicated THEN LOOP;
IF wantedCreatedTime = BasicTime.nullGMT
THEN ReportError[noSuchFile] ELSE EXIT;
};
ok => {
We got a response, so if it is correct we return. Otherwise we bail out of the quick find, since all of the replicated servers should be equivalent.
IF wantedCreatedTime = BasicTime.nullGMT THEN RETURN;
IF wantedCreatedTime = created THEN RETURN;
EXIT};
ENDCASE;
ENDLOOP;
IF allDown THEN ReportError[noNameLookupResponse];
This call is for the general case. It is more expensive, of course, since we have to grab a connection for it.
[version, bytes, created] ← STPInfo[server, file, wantedCreatedTime];
};
Rename:
PUBLIC
PROC [server, fromFile:
ROPE, fromCreated:
GMT, toFile:
ROPE, proc: FSRemoteFile.ConfirmProc] = {
stpCode: STP.ErrorCode ← noSuchFile;
version: Version = Info[server, fromFile, fromCreated].version;
innerRename:
PROC
RETURNS [ok:
BOOL ←
TRUE] = {
h: Handle ← NIL;
tServer: ROPE ← FSPseudoServers.TranslateForWrite[server];
{
ENABLE UNWIND => ReturnConnection[tServer, h];
h ← GetConnection[tServer, fromFile, TRUE];
fromFile ← FSName.BangVersionFile[fromFile, version];
STP.Rename[h, fromFile, toFile];
};
ReturnConnection[tServer, h];
};
IF proc[version]
THEN {
IF innerRename[! STP.Error => {stpCode ← code; CONTINUE}] THEN RETURN;
ReportSTPError[stpCode, server, toFile, BasicTime.nullGMT];
};
};
Retrieve:
PUBLIC
PROC [server, file:
ROPE, wantedCreatedTime:
GMT, proc:
PROC[fullGName:
ROPE, bytes:
INT, created:
GMT]
RETURNS [
STREAM]] = {
matchFound: BOOL ← FALSE;
timeSearch: BOOL = (wantedCreatedTime # BasicTime.nullGMT);
h: Handle ← NIL;
stpCode: STP.ErrorCode ← noSuchFile;
serverB: ROPE = BracketServer[server];
Confirm:
STP.ConfirmProcType = {
[file: ROPE] RETURNS [answer: {do, skip, abort}, localStream: STREAM]
info: STP.FileInfo = STP.GetFileInfo[h];
created: GMT = FTPTimeToGMT[info.create];
SELECT
TRUE
FROM
matchFound => answer ← abort;
NOT timeSearch
OR wantedCreatedTime = created => {
localStream ← proc[Rope.Concat[serverB, file], info.size, created];
answer ← IF localStream = NIL THEN abort ELSE do;
matchFound ← TRUE;
};
ENDCASE => answer ← skip;
};
innerRetrieve:
PROC [eachServer:
ROPE]
RETURNS [success:
BOOL ←
TRUE] = {
h ← NIL;
{
ENABLE UNWIND => ReturnConnection[eachServer, h];
h ← GetConnection[eachServer, file, FALSE];
STP.Retrieve[h, file, Confirm
! STP.Error => IF code = noSuchFile THEN CONTINUE ];
IF
NOT matchFound
THEN {
IF timeSearch
THEN {
file ← FSName.BangStarFile[file];
STP.Retrieve[h, file, Confirm];
};
IF NOT matchFound THEN {stpCode ← noSuchFile; success ← FALSE};
};
};
ReturnConnection[eachServer, h];
};
serverList: LIST OF ROPE = FSPseudoServers.TranslateForRead[server];
replicated: BOOL = FSPseudoServers.AvoidRemoteCheck[server];
FOR each:
LIST
OF
ROPE ← serverList, each.rest
WHILE each #
NIL
DO
IF innerRetrieve[each.first
!
STP.Error => {
IF each = serverList THEN stpCode ← code;
IF each.rest = NIL THEN EXIT;
IF stpCode = noSuchFile AND replicated THEN EXIT;
LOOP}
] THEN RETURN ELSE IF replicated THEN EXIT;
ENDLOOP;
ReportSTPError[stpCode, server, file, wantedCreatedTime];
};
Store:
PUBLIC
PROC
[server, file: ROPE, str: STREAM, created: GMT, proc: FSRemoteFile.ConfirmProc] = {
h: Handle ← NIL;
Note:
STP.NoteFileProcType = {
[file: ROPE] RETURNS [continue: BOOL]
doIt: BOOL = proc [ FSName.VersionFromRope[ STP.GetFileInfo[h].version ] ];
continue ← IF doIt THEN yes ELSE no;
};
innerStore:
PROC
RETURNS [ok:
BOOL ←
TRUE] = {
tServer: ROPE ← FSPseudoServers.TranslateForWrite[server];
{
ENABLE UNWIND => ReturnConnection[tServer, h];
h ← GetConnection[tServer, file, TRUE];
STP.Store[h, file, str, Note, unknown, created];
};
ReturnConnection[tServer, h];
};
stpCode: STP.ErrorCode ← noSuchFile;
IF innerStore[ ! STP.Error => {stpCode ← code; CONTINUE}] THEN RETURN;
ReportSTPError[stpCode, server, file, BasicTime.nullGMT];
};
FTPTimeToGMT:
PUBLIC
PROC
[t: Rope.ROPE] RETURNS [time: BasicTime.GMT ← BasicTime.nullGMT] = {
Digit:
PROC [index:
INT]
RETURNS [
CARDINAL] = {
c: CHAR = Rope.Fetch[t, index];
SELECT c
FROM
' => RETURN [0];
IN ['0..'9] => RETURN [c-'0];
ENDCASE => ERROR BadTime;
};
DoubleDigit:
PROC [index:
INT]
RETURNS [
CARDINAL] = {
RETURN [Digit[index]*10 + Digit[index+1]];
};
Char:
PROC [index:
INT]
RETURNS [
CHAR] = {
c: CHAR = Rope.Fetch[t, index];
SELECT c
FROM
'-, '+, ' , IN ['a..'z], IN ['0..'9] => RETURN [c];
IN ['A..'Z] => RETURN [c + ('a-'A)];
ENDCASE => ERROR BadTime;
};
Inner:
PROC = {
uT: BasicTime.Unpacked;
yr: CARDINAL = DoubleDigit[7];
SELECT yr
FROM
< 50 => uT.year ← yr + 2000;
ENDCASE => uT.year ← yr + 1900;
uT.day ← DoubleDigit[0];
uT.hour ← DoubleDigit[10];
uT.minute ← DoubleDigit[13];
uT.second ← DoubleDigit[16];
uT.month ←
SELECT Char[3]
FROM
'j =>
IF Char[4] = 'a
THEN January
ELSE IF Char[5] = 'n THEN June ELSE July,
'f => February,
'm => IF Char[5] = 'r THEN March ELSE May,
'a => IF Char[4] = 'p THEN April ELSE August,
's => September,
'o => October,
'n => November,
'd => December,
ENDCASE => ERROR BadTime;
IF t.Length[] < 19
THEN {
Yetch, FTPServer on AltoGateway does this. Use local zone info.
temp: BasicTime.Unpacked ← BasicTime.Unpack[BasicTime.Now[]];
uT.zone ← temp.zone;
uT.dst ← temp.dst;
}
ELSE {
uT.zone ← 60 * (
SELECT Char[19]
FROM
'g => 0,
'e => 5,
'c => 6,
'm => 7,
'p => 8,
'+ => Digit[20],
'- => - Digit[20],
ENDCASE => ERROR BadTime) ;
uT.dst ← IF (Char[20] = 'd) THEN yes ELSE no;
};
time ← BasicTime.Pack[uT];
};
Inner[ ! RuntimeError.UNCAUGHT => CONTINUE];
};
BadTime: ERROR = CODE;
Internal procedures
STPInfo:
PROC [server, file:
ROPE, wantedCreatedTime:
GMT]
RETURNS [version: Version, bytes:
INT, created:
GMT] = {
h: Handle ← NIL;
Note:
STP.NoteFileProcType = {
[file: ROPE] RETURNS [continue: BOOL]
info: STP.FileInfo = STP.GetFileInfo[h];
created ← FTPTimeToGMT[info.create];
IF
NOT timeSearch
OR wantedCreatedTime = created
THEN {
version ← FSName.VersionFromRope[info.version];
bytes ← info.size;
matchFound ← TRUE;
If version part was missing from client's file name, then Enumerate will produce all versions, but we only want the !H version
continue ← IF timeSearch THEN no ELSE yes;
}
ELSE continue ← yes;
};
matchFound: BOOL ← FALSE;
timeSearch: BOOL = (wantedCreatedTime # BasicTime.nullGMT);
stpCode: STP.ErrorCode ← noSuchFile;
serverList: LIST OF ROPE = FSPseudoServers.TranslateForRead[server];
replicated: BOOL = FSPseudoServers.AvoidRemoteCheck[server];
innerSTPInfo:
PROC [eachServer:
ROPE]
RETURNS [found:
BOOL ←
TRUE] = {
ENABLE UNWIND => ReturnConnection[eachServer, h];
h ← NIL;
h ← GetConnection[eachServer, file, FALSE];
STP.Enumerate[h, file, Note
! STP.Error => IF code = noSuchFile THEN CONTINUE ];
IF
NOT matchFound
THEN {
IF timeSearch
THEN {
file ← FSName.BangStarFile[file];
STP.Enumerate[h, file, Note];
};
IF NOT matchFound THEN {stpCode ← noSuchFile; found ← FALSE};
};
ReturnConnection[eachServer, h];
};
FOR each:
LIST
OF
ROPE ← serverList, each.rest
WHILE each #
NIL
DO
IF innerSTPInfo[each.first
!
STP.Error => {
IF each = serverList THEN stpCode ← code;
IF each.rest = NIL THEN EXIT;
IF stpCode = noSuchFile AND replicated THEN EXIT;
LOOP};
] THEN RETURN ELSE IF replicated THEN EXIT;
ENDLOOP;
ReportSTPError[stpCode, server, file, wantedCreatedTime];
};
Property stuff
MyDesiredProperties: TYPE = PACKED ARRAY STP.ValidProperties OF BoolDefaultFalse;
BoolDefaultFalse: TYPE = BOOL ← FALSE;
namesOnly: MyDesiredProperties = [directory: TRUE, nameBody: TRUE, version: TRUE];
nameSizeCreated: MyDesiredProperties = [
directory: TRUE, nameBody: TRUE, version: TRUE, createDate: TRUE, size: TRUE];
all: STP.DesiredProperties = ALL[TRUE]; -- stops sending of Desired-Property properties
ConditionConnection:
PROC[h: Handle, nameBody:
ROPE, justNames:
BOOL] = {
IF
NOT Rope.Match["<*", nameBody]
THEN {
-- no directory, so turn off directory defaulting and Desired-Property properties
STP.SetDirectory[h, " "];
STP.SetDesiredProperties[h, all];
}
ELSE {
STP.SetDirectory[h, NIL];
STP.SetDesiredProperties[h, IF justNames THEN namesOnly ELSE nameSizeCreated];
};
};
Internal procedures
BracketServer:
PROC[server:
ROPE]
RETURNS [
ROPE] = {
IF Rope.Match["[*", server]
THEN RETURN [server]
ELSE RETURN [ Rope.Cat[ "[", server, "]" ] ];
};
UnbracketServer:
PROC[server:
ROPE]
RETURNS [
ROPE] = {
IF Rope.Match["[*", server]
THEN RETURN [ Rope.Flatten[server, 1, Rope.Length[server]-2]]
ELSE RETURN [ server ];
};
ReportSTPError:
PUBLIC
PROC
[stpCode: STP.ErrorCode, server, file: ROPE, time: GMT] = {
gName: ROPE = Rope.Concat[BracketServer[server], file];
e1: ROPE ← "Server for \"";
e2: ROPE ← "\".";
code: FSBackdoor.ErrorCode;
NewError:
PROC [group:
FS.ErrorGroup, code:
ATOM, explanation:
ROPE] = {
ERROR FS.Error[[group, code, Rope.Cat[e1, gName, "\"", explanation]]];
};
IF stpCode = noSuchFile
THEN {
FSReport.UnknownFile[gName, time]; -- raises FS.Error
};
SELECT stpCode
FROM
noRouteToNetwork, noNameLookupResponse => {
code ← serverInaccessible;
e2 ← "\" is inaccessible." };
connectionClosed => {
code ← wentOffline;
e2 ← "\" connection closed unexpectedly (wentOffline)." };
connectionRejected => {
code ← connectionRejected;
e2 ← "\" rejected the connection attempt." };
connectionTimedOut => {
code ← connectionTimedOut;
e2 ← "\" timed-out the connection." };
accessDenied => {
code ← accessDenied;
e2 ← "\" denied file access permission." };
requestRefused => {
code ← quotaExceeded;
e1 ← "Request refused (possibily no quota for storing) for \"" };
accessError => {
code ← fileBusy;
e1 ← "\""; e2 ← "\" is locked on the server." };
illegalUserName => {
code ← badCredentials;
e1 ← "Credentials rejected when accessing \"" };
illegalFileName => {
code ← illegalName;
e2 ← "\" says that the file name is illegal." };
noSuchHost => {
code ← unknownServer;
e1 ← "Couldn't find the server for \"" };
the rest of the errors are non-standard, and are new for Cedar 5.2
alreadyAConnection =>
NewError[bug, $alreadyAConnection, " already had a connection."];
noConnection =>
NewError[bug, $noConnection, " gave a noConnection error."];
illegalUserPassword =>
NewError[environment, $illegalUserPassword, " had an illegal user password."];
illegalUserAccount =>
NewError[environment, $illegalUserAccount, " had an illegal user account."];
illegalConnectName =>
NewError[environment, $illegalConnectName, " had an illegal connect name."];
illegalConnectPassword =>
NewError[environment, $illegalConnectPassword, " had an illegal connect password."];
credentailsMissing =>
NewError[environment, $credentailsMissing, " had missing credentails."];
protocolError =>
NewError[bug, $protocolError, " gave a protocol error to STP."];
noSuchFile =>
NewError[bug, $noSuchFile, " reported no such file."];
undefinedError =>
NewError[bug, $undefinedError, " gave STP an undefinedError."];
ENDCASE => ERROR;
FSBackdoor.ProduceError[code, Rope.Cat[e1, gName, e2]];
};
STP connection cacheing
maxSlots: CARDINAL = 4; -- only need enough for all used in last TimeOut seconds
TimeOut: Process.Seconds ← 4; -- keep connection around for this long after use
Slot:
TYPE =
RECORD [
server: ROPE,
h: Handle,
used: BOOL
];
emptySlot: Slot = [NIL, NIL, FALSE];
new, reused, flushed: INT ← 0; -- statistics
slot: ARRAY [0..maxSlots) OF Slot ← ALL [emptySlot];
haveSlotTimer: BOOL ← FALSE;
ForTimeout: CONDITION ← [Process.SecondsToTicks[TimeOut]];
GetConnection:
PROC [server, pattern:
ROPE, justNames:
BOOL]
RETURNS [h: Handle] = {
h ← LookForConnection[server];
IF h =
NIL
THEN {
-- need a new connection
user, password: ROPE;
h ← STP.Create[];
[user, password] ← UserCredentials.Get[];
STP.Login[h, user, password];
[] ← STP.Open[h, server];
}
ELSE {
-- already had a connection
IF NOT STP.IsOpen[h] THEN [] ← STP.Open[h, server];
};
ConditionConnection[h, pattern, justNames];
};
LookForConnection:
ENTRY
PROC[server:
ROPE]
RETURNS [h: Handle] = {
ENABLE UNWIND => NULL;
FOR i:
CARDINAL
IN [0..maxSlots)
DO
IF slot[i] # emptySlot
AND Rope.Equal[slot[i].server, server,
FALSE]
THEN {
reused ← reused + 1;
h ← slot[i].h;
slot[i] ← emptySlot;
RETURN;
};
ENDLOOP;
h ← NIL;
new ← new + 1;
};
ReturnConnection:
PROC [server:
ROPE, h: Handle] = {
IF server = NIL OR h = NIL THEN RETURN;
IF
STP.IsOpen[h]
AND
NOT SaveConnection[server, h]
THEN
STP.Close[h ! STP.Error => CONTINUE ];
};
SaveConnection:
ENTRY
PROC [server:
ROPE, h: Handle]
RETURNS [
BOOL] = {
ENABLE UNWIND => NULL;
FOR i:
CARDINAL
IN [0..maxSlots)
DO
IF slot[i] = emptySlot
THEN {
slot[i] ← [server, h, TRUE];
IF
NOT haveSlotTimer
THEN
TRUSTED {
haveSlotTimer ← TRUE;
Process.Detach[FORK SlotTimer[]];
};
RETURN [TRUE];
};
ENDLOOP;
RETURN [FALSE];
};
SlotTimer:
PROC = {
i: CARDINAL ← 0;
h: Handle;
DO
[h, i] ← NextInterestingSlot[i];
IF h = NIL THEN RETURN;
STP.Close[h ! STP.Error => LOOP ];
flushed ← flushed + 1;
ENDLOOP;
};
NextInterestingSlot:
ENTRY
PROC [start:
CARDINAL]
RETURNS [h: Handle, index:
CARDINAL] = {
ENABLE UNWIND => NULL;
DO
IF start = 0 THEN WAIT ForTimeout;
FOR index
IN [start..maxSlots)
DO
IF slot[index] # emptySlot
THEN {
IF slot[index].used
THEN slot[index].used ← FALSE
ELSE {
-- here's one that needs to be closed
h ← slot[index].h;
slot[index] ← emptySlot;
RETURN;
};
};
ENDLOOP;
FOR index
IN [0 .. maxSlots)
DO
IF slot[index] # emptySlot THEN EXIT; -- need to stick around
REPEAT
FINISHED => {
-- nothing more to do
h ← NIL;
index ← maxSlots;
haveSlotTimer ← FALSE;
RETURN;
};
ENDLOOP;
start ← 0;
ENDLOOP;
};
}.