<> <> <> <> <> <> <> <> 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 USING [AvoidRemoteCheck, PseudoServerList, TranslateForRead, TranslateForWrite], FSRemoteFile USING [ConfirmProc, Lookup, LookupResult], FSReport USING [UnknownFile], GVBasics USING [Connect], GVNames USING [ConnectInfo, GetConnect], IO USING [STREAM], Process USING [CheckForAbort, Detach, Seconds, SecondsToTicks], Rope USING [Cat, Concat, Equal, Fetch, Find, 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, GVNames, 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; <> 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 { ReturnConnection[tServer, h]; 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] = { <> stpCode: STP.ErrorCode _ noSuchFile; h: Handle _ NIL; notedOne: BOOL _ FALSE; innerNote: STP.NoteFileProcType = { Process.CheckForAbort[]; <> notedOne _ TRUE; IF NOT Rope.Match["<*", file] THEN file _ Rope.Concat["<>", file]; <" 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[eachName, 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 => <> SELECT TRUE FROM replicated, notedOne => GO TO NotFound; ENDCASE => LOOP; notedOne => { <> stpCode _ code; EXIT}; each = serverList => { <> stpCode _ code; LOOP}; ENDCASE => <> LOOP; ]; <> RETURN; ENDLOOP; <> IF stpCode # noSuchFile THEN 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 NOT replicated THEN LOOP; <> IF wantedCreatedTime = BasicTime.nullGMT THEN ReportError[noSuchFile]; <> EXIT; <> }; ok => { <> IF wantedCreatedTime = BasicTime.nullGMT THEN RETURN; <> IF wantedCreatedTime = created THEN RETURN; <> IF NOT replicated THEN LOOP; <> EXIT}; ENDCASE; ENDLOOP; <> <> <> [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] = { adjust: NAT _ 0; Digit: PROC [index: INT] RETURNS [CARDINAL] = { c: CHAR = Rope.Fetch[t, index-adjust]; 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; IF Char[1] IN ['0..'9] THEN uT.day _ DoubleDigit[0] ELSE { <> uT.day _ Digit[0]; adjust _ 1}; 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 { <> 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; <> 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; <> 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]; }; <> 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 { <> STP.SetDirectory[h, " "]; STP.SetDesiredProperties[h, all]; } ELSE { STP.SetDirectory[h, NIL]; STP.SetDesiredProperties[h, IF justNames THEN namesOnly ELSE nameSizeCreated]; }; }; <> 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 \""; }; 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]]; }; <> maxSlots: CARDINAL = 9; -- only need enough for all used in last TimeOut seconds TimeOut: Process.Seconds _ 7; -- keep connection around for this long after use SlotArray: TYPE = ARRAY [0..maxSlots) OF Slot; Slot: TYPE = RECORD [ server: ROPE, h: Handle, used: BOOL ]; emptySlot: Slot = [NIL, NIL, FALSE]; new, reused, flushed: INT _ 0; -- statistics slot: REF SlotArray _ NEW[SlotArray _ ALL [emptySlot]]; haveSlotTimer: BOOL _ FALSE; ForTimeout: CONDITION _ [Process.SecondsToTicks[TimeOut]]; GrapevineCacheArray: TYPE = ARRAY [0..maxGVineCache) OF GrapevineCacheEntry; GrapevineCacheEntry: TYPE = RECORD[ name: ROPE _ NIL, connect: GVBasics.Connect ]; maxGVineCache: CARDINAL = 9; -- its quite expensive to find these out grapevineCachePut: CARDINAL _ 0; -- next victim grapevineCache: REF GrapevineCacheArray _ NEW[GrapevineCacheArray]; GetConnection: PROC [server, pattern: ROPE, justNames: BOOL] RETURNS [h: Handle] = { h _ LookForConnection[server]; IF h = NIL THEN { <> user, password: ROPE; h _ STP.Create[]; [user, password] _ UserCredentials.Get[]; STP.Login[h, user, password]; [] _ STP.Open[h, GetServerPupName[server]]; } ELSE { <> IF NOT STP.IsOpen[h] THEN { [] _ STP.Open[h, GetServerPupName[server]]; }; }; ConditionConnection[h, pattern, justNames]; }; GetServerPupName: PUBLIC PROC [server: ROPE] RETURNS [pupServer: ROPE] = { IF server.Find[".", 0, FALSE] > 0 THEN { <> info: GVNames.ConnectInfo; connect: GVBasics.Connect; foundInCache: BOOL; [found: foundInCache, connect: connect] _ FindInGrapevineCache[name: server]; IF foundInCache THEN RETURN[connect]; [info: info, connect: connect ] _ GVNames.GetConnect[server]; <> IF info = group OR info = individual THEN { AddToGrapevineCache[name: server, connect: connect]; RETURN[connect]; }; }; RETURN[server]; }; FindInGrapevineCache: ENTRY PROC [name: ROPE] RETURNS [found: BOOL _ FALSE, connect: GVBasics.Connect] = { FOR i: CARDINAL IN [0..maxGVineCache) DO IF Rope.Equal[name, grapevineCache[i].name, FALSE] THEN RETURN[TRUE, grapevineCache[i].connect]; ENDLOOP; }; AddToGrapevineCache: ENTRY PROC [name: ROPE, connect: GVBasics.Connect] = { grapevineCache[grapevineCachePut].name _ name; grapevineCache[grapevineCachePut].connect _ connect; IF (grapevineCachePut _ grapevineCachePut + 1) >= maxGVineCache THEN grapevineCachePut _ 0; }; 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; }; }. <> <> <> <> <<>> <> <> <> <> <<>> <> <> <<>>