FSRemoteFileImpl.mesa
Copyright © 1984, 1985, 1986 by Xerox Corporation. All rights reserved.
HGM, February 8, 1984 11:55:19 pm PST
Bob Hagmann, August 2, 1985 4:38:26 pm PDT
Doug Wyatt, November 27, 1984 3:53:09 pm PST -- added allDown test to Info
Russ Atkinson (RRA) April 8, 1986 12:59:54 pm PST
Tim Diebert: February 11, 1986 9:39:22 am PST
Hal Murray, June 24, 1986 9:11:54 pm PDT
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;
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 {
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] = {
This helpful procedure allows enumeration for either names or information.
stpCode: STP.ErrorCode ← noSuchFile;
h: Handle ← NIL;
notedOne: BOOL ← FALSE;
innerNote:
STP.NoteFileProcType = {
Process.CheckForAbort[];
RRA: this allows us to abort during an enumeration, which is non-trivial
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[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 =>
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.
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 => {
No such file was the response. This is a positive response that the named file was not present on the named server.
IF
NOT replicated
THEN
LOOP;
If we are NOT assuming replicated files, then we go look for another server.
IF wantedCreatedTime = BasicTime.nullGMT
THEN ReportError[noSuchFile];
If we wanted any date, and we are assuming replicated files, then we should just give up right now, since we will not find a match.
EXIT;
If we wanted a specific date, then we may just have a bad version number hint, so we exit to do the search the expensive way.
};
ok => {
We got a response that the file was present.
IF wantedCreatedTime = BasicTime.nullGMT
THEN
RETURN;
We were requested to find any date, so the one we got was OK.
IF wantedCreatedTime = created
THEN
RETURN;
We were asked for a specific date, and we got it, so its all OK.
IF
NOT replicated
THEN
LOOP;
We were asked for a specific date, and the file was there, but the date was wrong, which means that the version # hint was bogus. If we assume the files are replicated, we should exit, since the version hint is wrong on the replicated servers as well. Otherwise, we loop, hoping that another server will have the correct hint and date as well.
EXIT};
ENDCASE;
ENDLOOP;
The line of code following fails if there is no response to a single packet info check. Tim D.
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. However, STPInfo can handle bogus version # hints.
[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 {
In this case we have a single-character day of month with no leading blank. We accept this number, as well as adjusting the indexing.
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 {
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 \"";
};
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 = 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 {
need a new connection
user, password: ROPE;
h ← STP.Create[];
[user, password] ← UserCredentials.Get[];
STP.Login[h, user, password];
[] ← STP.Open[h, GetServerPupName[server]];
}
ELSE {
already had a connection
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 {
Names with "." are GVNames (Grapevine names), so ask Grapevine to look them up
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 successful, use the connect as the server name for STP.Open
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;
};
}.
Bob Hagmann April 29, 1985 9:12:49 am PDT
changes to: DIRECTORY, GetConnection
Bob Hagmann April 29, 1985 10:03:03 am PDT
changes to: DIRECTORY, GetConnection, GetServerPupName, LookForConnection, FSRemoteFileImpl
Bob Hagmann May 6, 1985 4:13:27 pm PDT
changes to: InnerEnumerate
Bob Hagmann August 2, 1985 3:25:38 pm PDT
changes to: maxSlots, TimeOut, GetServerPupName, FindInGrapevineCache, AddToGrapevineCache, LookForConnection, ForTimeout, GrapevineCacheEntry, maxGVineCache, grapevineCachePut, grapevineCache, GetConnection
Bob Hagmann August 2, 1985 4:37:37 pm PDT
changes to: grapevineCachePut