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; Delete: PUBLIC PROC [server, file: ROPE, wantedCreatedTime: GMT, proc: FSRemoteFile.ConfirmProc] = { Confirm: STP.ConfirmProcType = { 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 = { 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 = { 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 = { notedOne _ TRUE; IF NOT Rope.Match["<*", file] THEN file _ Rope.Concat["<>", file]; 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 => SELECT TRUE FROM replicated, notedOne => GO TO NotFound; ENDCASE => LOOP; notedOne => { stpCode _ code; EXIT}; each = serverList => { stpCode _ code; LOOP}; ENDCASE => LOOP; ]; RETURN; ENDLOOP; 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] ELSE EXIT; }; ok => { IF wantedCreatedTime = BasicTime.nullGMT THEN RETURN; IF wantedCreatedTime = created THEN RETURN; EXIT}; ENDCASE; ENDLOOP; IF allDown THEN ReportError[noNameLookupResponse]; [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 = { 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 = { 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 { 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 = { 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 { -- 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]; }; }; 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 = 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; }; }. ΔFSRemoteFileImpl.mesa Copyright c 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 Exported to FSRemoteFile [file: ROPE] RETURNS [answer: {do, skip, abort}, localStream: STREAM] [file: ROPE] RETURNS [continue: BOOL] [file: ROPE] RETURNS [continue: BOOL] This helpful procedure allows enumeration for either names or information. 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. 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. If we have seen at least one file then we do NOT try other servers, which would royally screw up the enumeration. If the auxilliary servers do not work, we report the error from the first server only. So we remember the error code here. Any other errors during auxiliiary severs searches just go on to try the next server in the list (if any). If we continue here, we have enumerated the pattern all right, and have returned the connection. If we continue here, we have an error of some kind during the enumeration, so we report it. 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. 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. This call is for the general case. It is more expensive, of course, since we have to grab a connection for it. [file: ROPE] RETURNS [answer: {do, skip, abort}, localStream: STREAM] [file: ROPE] RETURNS [continue: BOOL] Yetch, FTPServer on AltoGateway does this. Use local zone info. Internal procedures [file: ROPE] RETURNS [continue: BOOL] If version part was missing from client's file name, then Enumerate will produce all versions, but we only want the !H version Property stuff Internal procedures the rest of the errors are non-standard, and are new for Cedar 5.2 STP connection cacheing Κό– "cedar" style˜Icode2šœ™šœ Οmœ1™Lšœ˜Lšœ žœ%˜7Lšœ žœ˜Lšžœžœžœ˜Lšœžœ#˜0Lšœžœ5žœ˜ELšœ žœžœž˜Lšžœžœψ˜Lšœžœ˜—šœžœž˜Lšžœ žœ\žœ˜…Lšžœ ˜Lšœ˜L˜Lšžœžœ žœ˜Lšœžœžœ˜Lšžœžœžœ˜Lšœžœ$˜:Lšžœžœžœžœ˜Lšœ žœ˜#L˜—šœ™šΟnœžœž˜Kšœžœžœ%˜Pšœ žœ˜ Kšœžœžœ*žœ™EKšœžœ žœ˜(Kšœ žœ˜)šžœžœž˜Kšœ˜šžœ žœ!˜2Kšœ žœ-žœžœ˜KKšœ žœ˜Kšœ˜—Kšžœ˜—Kšœ˜—Kšœ žœžœ˜Kšœ žœ+˜>Kšœ žœ˜$Kšœ žœ˜š œ žœžœžœžœ˜/Kšœ žœ>˜KKšœžœ˜˜Kšžœžœ!˜.Kšœ!žœ˜(šžœ˜Kš œžœ žœžœžœ˜4—šžœžœ žœ˜šžœ žœ˜Kšœ!˜!Kšžœ˜Kšœ˜—Kš žœžœ žœžœžœ˜>Kšœ˜—K˜—Kšœ˜Kšœ˜—Kš žœžœžœžœžœ˜GKšœ9˜9Kšœ˜—šŸœžœžœžœ˜Lšœ˜Kšœžœžœ žœ™%Kšœžœ žœ˜(Kšœžœ˜3šœ žœ"žœ˜JKšžœžœ˜—Kšœ˜—Kšœ žœ˜&Kšœ*žœ˜1Kšœ˜—šŸœžœžœžœ˜Mšœ˜Kšœžœžœ žœ™%Kšœ žœ"žœžœ˜@Kšœ˜—Kšœ žœ˜&Kšœ*žœ˜0Kšœž˜—Kš Ÿ œžœžœžœžœ žœ ˜Tš Ÿœžœžœžœ"žœ˜]KšœJ™JKšœ žœ˜$Kšœ žœ˜Kšœ žœžœ˜šœ žœ˜#Kšœ žœ˜šžœžœžœ ˜BKšœ’™’—Kšœ˜K˜—šœ žœ žœ˜%Kšœžœ˜˜Kšžœžœ"˜/Kšœ0˜0Kšžœ"˜%K˜—Kšœ˜K˜—Kšœ žœžœžœ,˜DKšœ žœ,˜<š žœžœžœžœžœžœž˜Bšœ˜šœžœ ˜šžœžœž˜šœ˜Kšœ‡™‡šžœžœž˜Kšœžœžœ ˜'Kšžœžœ˜——šœ ˜ Kšœq™qKšœžœ˜—šœ˜Kšœ{™{Kšœžœ˜—šžœ˜ Kšœj™jKšžœ˜———Kšœ˜—Kšœ`™`Kšžœ˜Kšžœ˜—Kšœ[™[Kšœ<˜Kšœ˜—Kšœ ˜ K˜—š žœžœžœžœžœžœž˜Bšžœ˜šœžœ ˜Kšžœžœ˜)Kšžœ žœžœžœ˜Kšžœžœ žœžœ˜1Kšžœ˜—Kš œžœžœžœžœ žœžœ˜+—Kšžœ˜—Kšœ9˜9Kšœ˜——™Kš œžœžœžœžœžœ˜QKšœžœžœžœ˜&Kšœ-žœ žœ žœ˜Ršœ(˜(Kš œ žœ žœ žœžœžœ˜N—KšœžœžœžœΟc/˜WšŸœžœžœ žœ˜Išžœžœ˜!šžœ Q˜XKšžœ˜Kšžœ˜!Kšœ˜—šžœ˜Kšžœžœ˜Kšžœžœ žœ žœ˜NKšœ˜——Kšœ˜——™š Ÿ œžœ žœžœžœ˜4šžœ˜Kšžœžœ ˜Kšžœžœ"˜-—Kšœ˜—š Ÿœžœ žœžœžœ˜6šžœ˜Kšžœžœ2˜=Kšžœžœ ˜—Kšœ˜—šŸœžœž˜Kšœ žœžœžœ˜;Kšœžœ,˜7Kšœžœ˜Kšœžœ ˜Kšœ˜š Ÿœžœ žœžœžœ˜HKšžœžœ>˜FK˜—šžœžœ˜Kšœ$ ˜6Kšœ˜—šžœ ž˜šœ+˜+Kšœ˜Kšœ˜—šœ˜Kšœ˜Kšœ:˜:—šœ˜Kšœ˜Kšœ-˜-—šœ˜Kšœ˜Kšœ&˜&—šœ˜Kšœ˜Kšœ+˜+—šœ˜Kšœ˜KšœA˜A—šœ˜Kšœ˜Kšœ0˜0—šœ˜Kšœ˜Kšœ0˜0—šœ˜Kšœ˜Kšœ0˜0—šœ˜Kšœ˜Kšœ)˜)K™B—šœ˜KšœA˜A—šœ˜Kšœ<˜<—šœ˜KšœN˜N—šœ˜KšœL˜L—šœ˜KšœL˜L—šœ˜KšœT˜T—šœ˜KšœH˜H—šœ˜Kšœ@˜@—šœ ˜ Kšœ6˜6—šœ˜Kšœ?˜?—Kšžœžœ˜—Kšœ7˜7Kšœ˜——™Kšœ žœ 8˜PKšœ 1˜Ošœžœžœ˜Kšœžœ˜ Kšœ ˜ Kšœž˜ Kšœ˜—Kšœžœžœžœ˜$Kšœžœ  ˜,Kšœžœžœžœ ˜4Kšœžœžœ˜Kšœ ž œ%˜:š Ÿ œžœžœ žœžœ˜TKšœ˜šžœž˜ šžœ ˜Kšœžœ˜Kšœžœ ˜Kšœ)˜)Kšžœ˜Kšœžœ˜Kšœ˜—šžœ ˜"Kš žœžœžœ žœžœ˜3Kšœ˜——Kšœ+˜+Kšœ˜—š Ÿœžœžœ žœžœ˜CKšžœžœžœ˜šžœžœžœž˜#šžœžœ$žœžœ˜LKšœ˜Kšœ˜Kšœ˜Kšžœ˜Kšœ˜—Kšžœ˜—Kšœžœ˜Kšœ˜Kšœ˜—šŸœžœ žœ˜4Kš žœ žœžœžœžœžœ˜'š žœžœ žœžœž˜7Kšžœ žœ žœ˜&—Kšœ˜—š Ÿœžœžœ žœ žœžœ˜GKšžœžœžœ˜šžœžœžœž˜#šžœžœ˜Kšœžœ˜šžœžœžœžœ˜#Kšœžœ˜Kšœžœ˜!Kšœ˜—Kšž œ˜Kšœ˜—Kšžœ˜—Kšžœ˜Kšœ˜—šŸ œžœ˜Kšœžœ˜Kšœ ˜ šž˜Kšœ ˜ Kšžœžœžœžœ˜Kšžœ žœ žœ˜"Kšœ˜Kšžœ˜—Kšœ˜—š Ÿœžœžœ žœžœžœ˜[Kšžœžœžœ˜šž˜Kšžœ žœžœ ˜"šžœžœž˜!šžœžœ˜!šžœ˜Kšžœž˜šžœ %˜,Kšœ˜Kšœ˜Kšžœ˜Kšœ˜——Kšœ˜—Kšžœ˜—šžœžœž˜Kšžœžœžœ ˜=šžœžœ ˜*Kšœžœ˜K˜Kšœžœ˜Kšžœ˜Kšœ˜—Kšžœ˜—K˜ Kšžœ˜—Kšœ˜——Kšœ˜—…—Drh2