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: BOOLEANFALSE;
timeSearch: BOOLEAN = (wantedCreatedTime # BasicTime.nullGMT);
stpCode: STP.ErrorCode ← noSuchFile;
h: Handle ← NIL;
innerDelete: PROC RETURNS [ok: BOOLTRUE] = {
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: BOOLFALSE;
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: BOOLTRUE;
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: BOOLTRUE] = {
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: BOOLFALSE;
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: BOOLTRUE] = {
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: BOOLTRUE] = {
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: BOOLFALSE;
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: BOOLTRUE] = {
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 = BOOLFALSE;
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: BOOLFALSE;
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;
};
}.