Parameters
myFlavor: ATOM ← $STP;
myServerProcs: FSRemoteFileBackdoor.ServerProcs ← NEW[FSRemoteFileBackdoor.ServerProcsObject ← [STPSweep, STPValidate, STPDelete, STPDo, STPEnumerateForInfo, STPEnumerateForNames, STPGetInfo, STPRename, STPRetrieve, STPStore, STPOpen, STPClose, STPReadWrite]];
initialConnectionTTL: CARD ← 8;
upServerTTL: CARD ← 0; -- irrelevant
downServerTTL: CARD ← 30;
busyServerTTL: CARD ← 10;
credentialsErrorTTL: CARD ← 30;
maxGrapevineCache: CARDINAL = 7;
Registered with FSRemoteFileBackdoor
STPDelete: FSRemoteFileBackdoor.DeleteProc
-- [h: ServerHandle, file: ROPE, wantedCreatedTime: GMT, case: BOOL, proc: ConfirmProc] -- ~ {
matchFound: BOOLEAN ← FALSE;
timeSearch: BOOLEAN = (wantedCreatedTime # BasicTime.nullGMT);
stpCode: STP.ErrorCode ← noSuchFile;
stpH: STP.Handle;
Confirm:
STP.ConfirmProcType
-- [file: ROPE] RETURNS [answer: {do, skip, abort}, localStream: STREAM] -- ~ {
info: STP.FileInfo = STP.GetFileInfo[stpH];
created: GMT = FTPTimeToGMT[info.create];
SELECT
TRUE
FROM
matchFound => answer ← abort;
(
NOT timeSearch)
OR (wantedCreatedTime = created) => {
answer ← IF (proc = NIL) OR (proc [FSName.VersionFromRope[info.version]])
THEN do ELSE abort;
matchFound ← TRUE;
};
ENDCASE => answer ← skip;
};
InnerDelete:
PROC
RETURNS [ok:
BOOL ←
TRUE] ~ {
stpH ← NIL;
{
ENABLE UNWIND => IF stpH # NIL THEN ReturnConnection[h, stpH];
stpH ← GetConnection[h, file, FALSE];
STP.Delete[stpH, file, Confirm
! STP.Error => IF code = noSuchFile THEN CONTINUE ];
IF
NOT matchFound
THEN {
IF timeSearch
THEN {
file ← FSName.BangStarFile[file];
STP.Delete[stpH, file, Confirm];
};
IF
NOT matchFound
THEN {
ReturnConnection[h, stpH];
stpCode ← noSuchFile;
RETURN [FALSE]};
};
};
ReturnConnection[h, stpH];
};
CheckUnimplemented[h, file, case];
IF InnerDelete[ ! STP.Error => {stpCode ← code; CONTINUE}] THEN RETURN;
ReportSTPError[stpCode, h, file, wantedCreatedTime];
};
STPDo: FSRemoteFileBackdoor.DoProc
-- [h: ServerHandle, cmd: ATOM, arg: REF, result: REF] -- ~ {
FSRemoteFileBackdoor.ErrorNotImplemented[h, NIL, "STPDo"];
};
STPEnumerateForInfo: FSRemoteFileBackdoor.EnumerateForInfoProc
-- [h: ServerHandle, pattern: ROPE, case: BOOL, proc: InfoProc] -- ~ {
NoteInfo: LocalEnumProc
-- [stpH: STP.Handle, file: ROPE] RETURNS [continue: BOOL] -- ~ {
info: STP.FileInfo ~ STP.GetFileInfo[stpH];
created: BasicTime.GMT ~ FTPTimeToGMT[info.create];
continue ← (proc = NIL) OR (proc[file, info.size, created, FS.tUnspecified]);
};
CheckUnimplemented[h, pattern, case];
InnerEnumerate[h, pattern, NoteInfo, FALSE];
};
STPEnumerateForNames: FSRemoteFileBackdoor.EnumerateForNamesProc
-- [h: ServerHandle, pattern: ROPE, case: BOOL, proc: NameProc] -- ~ {
NoteName: LocalEnumProc -- [stpH: STP.Handle, file: ROPE] RETURNS [continue: BOOL] -- ~ { RETURN[ (proc = NIL) OR (proc[file]) ] };
CheckUnimplemented[h, pattern, case];
InnerEnumerate[h, pattern, NoteName, TRUE];
};
LocalEnumProc: TYPE = PROC [stpH: STP.Handle, file: ROPE] RETURNS [continue: BOOL];
InnerEnumerate:
PROC [h: ServerHandle, pattern:
ROPE, note: LocalEnumProc, namesOnly:
BOOL] ~ {
This helpful procedure allows enumeration for either names or information.
stpH: STP.Handle;
InnerNote:
STP.NoteFileProcType ~ {
Process.CheckForAbort[];
RRA: this allows us to abort during an enumeration, which is non-trivial
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 ← IF note[stpH, file] THEN yes ELSE no;
};
InnerBlock:
PROC ~ {
stpH ← NIL;
{
ENABLE UNWIND => IF stpH # NIL THEN ReturnConnection[h, stpH];
stpH ← GetConnection[h, pattern, namesOnly];
STP.Enumerate[stpH, pattern, InnerNote];
};
ReturnConnection[h, stpH];
};
InnerBlock[
! STP.Error => ReportSTPError[code, h, pattern, BasicTime.nullGMT] ];
};
STPGetInfo: FSRemoteFileBackdoor.GetInfoProc
-- [h: ServerHandle, file: ROPE, wantedCreatedTime: GMT, case: BOOL] RETURNS [version: Version, bytes: INT, created: GMT, fileType: FS.FileType] -- ~ {
result: STPFSRemoteFile.LookupResult;
CheckUnimplemented[h, file, case];
fileType ← FS.tUnspecified;
[result, version, created, bytes] ← STPFSRemoteFile.Lookup[h, file];
SELECT result
FROM
ok => {
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.
};
noSuchFile => {
A positive response that the named file was not present on the named server.
IF wantedCreatedTime = BasicTime.nullGMT
THEN ReportSTPError[noSuchFile, h, file, wantedCreatedTime];
};
ENDCASE => NULL;
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] ← STPInfoIgnoringVersionHint[h, file, wantedCreatedTime];
};
STPInfoIgnoringVersionHint:
PROC [h: ServerHandle, file:
ROPE, wantedCreatedTime:
GMT]
RETURNS [version: Version, bytes:
INT, created:
GMT] = {
stpH: STP.Handle ← NIL;
Note:
STP.NoteFileProcType
-- [file: ROPE] RETURNS [continue: Continue] -- ~ {
info: STP.FileInfo = STP.GetFileInfo[stpH];
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. This works only because the server enumerates by increasing order of version number ...
continue ← IF timeSearch THEN no ELSE yes;
}
ELSE continue ← yes;
};
matchFound: BOOL ← FALSE;
timeSearch: BOOL = (wantedCreatedTime # BasicTime.nullGMT);
stpCode: STP.ErrorCode ← noSuchFile;
InnerSTPInfo:
PROC
RETURNS [found:
BOOL ←
TRUE] ~ {
ENABLE UNWIND => IF stpH # NIL THEN ReturnConnection[h, stpH];
stpH ← NIL;
stpH ← GetConnection[h, file, FALSE];
STP.Enumerate[stpH, file, Note
! STP.Error => IF code = noSuchFile THEN CONTINUE ];
IF
NOT matchFound
THEN {
IF timeSearch
THEN {
file ← FSName.BangStarFile[file];
STP.Enumerate[stpH, file, Note];
};
IF NOT matchFound THEN {stpCode ← noSuchFile; found ← FALSE};
};
ReturnConnection[h, stpH];
};
IF InnerSTPInfo[ ! STP.Error => { stpCode ← code; CONTINUE }] THEN RETURN;
ReportSTPError[stpCode, h, file, wantedCreatedTime];
};
STPRename: FSRemoteFileBackdoor.RenameProc
-- [h: ServerHandle, fromFile: ROPE, fromCreated: GMT, toFile: ROPE, case: BOOL, proc: ConfirmProc] -- ~ {
stpCode: STP.ErrorCode ← noSuchFile;
version: Version;
InnerRename:
PROC
RETURNS [ok:
BOOL ←
TRUE] ~ {
stpH: STP.Handle ← NIL;
{
ENABLE UNWIND => IF stpH # NIL THEN ReturnConnection[h, stpH];
stpH ← GetConnection[h, fromFile, TRUE];
fromFile ← FSName.BangVersionFile[fromFile, version];
STP.Rename[stpH, fromFile, toFile];
};
ReturnConnection[h, stpH];
};
CheckUnimplemented[h, fromFile, case];
CheckUnimplemented[h, toFile, case];
version ← STPGetInfo[h, fromFile, fromCreated, case].version;
IF (proc =
NIL)
OR (proc[version])
THEN {
IF InnerRename[! STP.Error => {stpCode ← code; CONTINUE}] THEN RETURN;
ReportSTPError[stpCode, h, toFile, BasicTime.nullGMT];
};
};
STPRetrieve: FSRemoteFileBackdoor.RetrieveProc
-- [h: ServerHandle, file: ROPE, wantedCreatedTime: GMT, case: BOOL, proc: ConfirmRetrieveProc] -- ~ {
matchFound: BOOL ← FALSE;
timeSearch: BOOL = (wantedCreatedTime # BasicTime.nullGMT);
stpH: STP.Handle ← NIL;
stpCode: STP.ErrorCode ← noSuchFile;
Confirm:
STP.ConfirmProcType ~ {
[file: ROPE] RETURNS [answer: {do, skip, abort}, localStream: STREAM]
info: STP.FileInfo = STP.GetFileInfo[stpH];
created: GMT = FTPTimeToGMT[info.create];
SELECT
TRUE
FROM
matchFound => answer ← abort;
(
NOT timeSearch)
OR (wantedCreatedTime = created) => {
localStream ← proc[file, info.size, created, FS.tUnspecified];
answer ← IF localStream = NIL THEN abort ELSE do;
matchFound ← TRUE;
};
ENDCASE => answer ← skip;
};
InnerRetrieve:
PROC
RETURNS [success:
BOOL ←
TRUE] ~ {
stpH ← NIL;
{
ENABLE UNWIND => IF stpH # NIL THEN ReturnConnection[h, stpH];
stpH ← GetConnection[h, file, FALSE];
STP.Retrieve[stpH, file, Confirm
! STP.Error => IF code = noSuchFile THEN CONTINUE ];
IF
NOT matchFound
THEN {
IF timeSearch
THEN {
file ← FSName.BangStarFile[file];
STP.Retrieve[stpH, file, Confirm];
};
IF NOT matchFound THEN {stpCode ← noSuchFile; success ← FALSE};
};
};
ReturnConnection[h, stpH];
};
CheckUnimplemented[h, file, case];
IF InnerRetrieve[ ! STP.Error => { stpCode ← code; CONTINUE }] THEN RETURN;
ReportSTPError[stpCode, h, file, wantedCreatedTime];
};
STPStore: FSRemoteFileBackdoor.StoreProc
-- [h: ServerHandle, file: ROPE, case: BOOL, str: STREAM, created: GMT, proc: ConfirmProc] -- ~ {
stpH: STP.Handle ← NIL;
stpCode: STP.ErrorCode;
Note:
STP.NoteFileProcType
-- [file: ROPE] RETURNS [continue: Continue] -- ~ {
IF proc = NIL THEN RETURN [yes];
RETURN[ IF proc[FSName.VersionFromRope[STP.GetFileInfo[stpH].version]] THEN yes ELSE no ];
};
InnerStore:
PROC
RETURNS [ok:
BOOL ←
TRUE] ~ {
stpH ← NIL;
{
ENABLE UNWIND => IF stpH # NIL THEN ReturnConnection[h, stpH];
stpH ← GetConnection[h, file, TRUE];
STP.Store[stpH, file, str, Note, unknown, created];
};
ReturnConnection[h, stpH];
};
CheckUnimplemented[h, file, case];
stpCode ← noSuchFile;
IF InnerStore[ ! STP.Error => {stpCode ← code; CONTINUE}] THEN RETURN;
ReportSTPError[stpCode, h, file, BasicTime.nullGMT];
};
STPOpen: FSRemoteFileBackdoor.OpenProc
-- [h: ServerHandle, cmd: ATOM, file: ROPE, wantedCreatedTime: GMT, case: BOOL, proc: ConfirmRetrieveProc] RETURNS [FileHandle] -- ~ {
FSRemoteFileBackdoor.ErrorNotImplemented[h, NIL, "STPOpen"];
RETURN[NIL];
};
STPClose: FSRemoteFileBackdoor.CloseProc
-- [h: ServerHandle, fileHandle: FileHandle] -- ~ {
FSRemoteFileBackdoor.ErrorNotImplemented[h, NIL, "STPClose"];
};
STPReadWrite: FSRemoteFileBackdoor.RWProc
-- [h: ServerHandle, fileHandle: FileHandle, cmd: ATOM, bytePos: CARD, block: REF TEXT] RETURNS [bytesTransferred: CARD] -- ~ {
FSRemoteFileBackdoor.ErrorNotImplemented[h, NIL, "STPReadWrite"];
};
Server cache
STPGetServer: FSRemoteFileBackdoor.GetServerProc
-- [server: ROPE] RETURNS [h: ServerHandle, downMsg: ROPE ← NIL] -- ~ {
Returns [NIL, NIL] if server doesn't exist.
Returns [NIL, msg] if server exists but is down.
serverPupName: ROPE;
data: STPFSRemoteFile.ServerData;
stpH: STP.Handle;
stpErrorCode: STP.ErrorCode;
opened: BOOL ← FALSE;
IF (serverPupName ← GetServerPupName[server]) = NIL THEN RETURN [NIL, NIL];
{
ENABLE STP.Error => { stpErrorCode ← code; CONTINUE };
user, password: ROPE;
stpH ← STP.Create[];
[user, password] ← UserCredentials.Get[];
STP.Login[stpH, user, password];
[] ← STP.Open[stpH, serverPupName];
opened ← TRUE;
};
IF
NOT opened
THEN
SELECT stpErrorCode
FROM
noSuchHost, noNameLookupResponse => RETURN [NIL, NIL];
ENDCASE => RETURN[NIL, "can't connect"];
data ← NEW[STPFSRemoteFile.ServerDataObject ← [ttl~upServerTTL, downMsg~NIL, connectionTTL~initialConnectionTTL, stpH~stpH, serverPupName~serverPupName]];
SafeStorage.EnableFinalization[data];
h ← NEW[ServerObject ← [flavor~myFlavor, name~server, procs~myServerProcs, data~data]];
RETURN [h, NIL];
};
GetConnection:
PROC [h: ServerHandle, pattern:
ROPE, justNames:
BOOL]
RETURNS [stpH:
STP.Handle] ~ {
data: ServerData ~ NARROW[h.data];
GetConnectionInner:
ENTRY
PROC ~
--INLINE-- {
stpH ← data.stpH; data.stpH ← NIL;
};
GetConnectionInner[];
IF stpH =
NIL
THEN {
user, password: ROPE;
stpH ← STP.Create[];
[user, password] ← UserCredentials.Get[];
STP.Login[stpH, user, password];
};
IF
NOT
STP.IsOpen[stpH]
THEN {
[] ← STP.Open[stpH, data.serverPupName];
};
ConditionConnection[stpH, pattern, justNames];
};
ReturnConnection:
PROC [h: ServerHandle, stpH:
STP.Handle] ~ {
data: ServerData ~ NARROW[h.data];
ReturnConnectionInner:
ENTRY
PROC ~
--INLINE-- {
IF data.stpH = NIL THEN { data.stpH ← stpH; stpH ← NIL };
data.connectionTTL ← initialConnectionTTL;
};
ReturnConnectionInner[];
IF stpH #
NIL
THEN {
STP.Close[stpH ! STP.Error => CONTINUE ];
};
};
STPValidate:
ENTRY FSRemoteFileBackdoor.ValidateProc
-- [h: ServerHandle] RETURNS [obsolete: BOOL, down: BOOL] -- ~ {
data: ServerData ~ NARROW[h.data];
IF (data.ttl = 0) AND data.downMsg # NIL THEN RETURN [TRUE, data.downMsg];
RETURN [FALSE, data.downMsg];
};
SetServerDown:
ENTRY
PROC [h: ServerHandle, seconds:
CARD] ~ {
data: ServerData ~ NARROW[h.data];
data.downMsg ← "got STP error";
data.ttl ← seconds;
};
dfq: SafeStorage.FinalizationQueue ~ SafeStorage.NewFQ[20];
STPSweep: FSRemoteFileBackdoor.SweepProc
-- [h: ServerHandle, seconds: CARD] -- ~ {
data: ServerData ~ NARROW[h.data];
stpH: STP.Handle ← NIL;
STPSweepInner:
ENTRY
PROC ~
--INLINE-- {
IF data.ttl > seconds THEN data.ttl ← data.ttl - seconds ELSE data.ttl ← 0;
IF data.connectionTTL > seconds
THEN data.connectionTTL ← data.connectionTTL - seconds
ELSE {
IF data.connectionTTL > 0 THEN stpH ← data.stpH;
data.stpH ← NIL;
data.connectionTTL ← 0;
};
};
STPSweepInner[];
IF (stpH # NIL) AND (STP.IsOpen[stpH]) THEN STP.Close[stpH ! STP.Error => CONTINUE];
Finalization ...
WHILE
NOT SafeStorage.FQEmpty[dfq]
DO
droppedData: ServerData ~ NARROW[SafeStorage.FQNext[dfq]];
IF ((stpH ← droppedData.stpH) #
NIL)
AND (
STP.IsOpen[stpH])
THEN STP.Close[stpH ! STP.Error => CONTINUE];
ENDLOOP;
};
Grapevine Cache
This is PUBLIC for use by e.g. STPServer. It's a FIFO cache from which nothing ever times out ... so if we ever get back a wrong answer from Grapevine the only way to flush it is to look up a lot (maxGrapevineCache) of different names.
GrapevineCacheEntry:
TYPE =
RECORD[
name: ROPE ← NIL,
connect: GVBasics.Connect
];
GrapevineCacheArray: TYPE = ARRAY [0..maxGrapevineCache) OF GrapevineCacheEntry;
grapevineCache: REF GrapevineCacheArray ← NEW[GrapevineCacheArray];
grapevineCachePut: CARDINAL ← 0; -- next victim
FindInGrapevineCache:
ENTRY
PROC [name:
ROPE]
RETURNS [found:
BOOL ←
FALSE, connect: GVBasics.Connect ←
NIL] = {
ENABLE UNWIND => NULL;
FOR i:
CARDINAL
IN [0..maxGrapevineCache)
DO
IF Rope.Equal[name, grapevineCache[i].name, FALSE] THEN RETURN[TRUE, grapevineCache[i].connect];
ENDLOOP;
};
AddToGrapevineCache:
ENTRY
PROC [name:
ROPE, connect: GVBasics.Connect] = {
ENABLE UNWIND => NULL;
grapevineCache[grapevineCachePut].name ← name;
grapevineCache[grapevineCachePut].connect ← connect;
grapevineCachePut ← (IF grapevineCachePut < (maxGrapevineCache-1) THEN grapevineCachePut+1 ELSE 0);
};
The Detach-FORK below is to protect GVNames.GetConnect from being ABORTed
GetConnectResult: TYPE ~ REF GetConnectResultObject;
GetConnectResultObject:
TYPE ~
RECORD [
done: BOOL ← FALSE,
waitForDone: CONDITION,
info: GVNames.ConnectInfo,
connect: GVBasics.Connect
];
WaitForDone:
ENTRY
PROC [r: GetConnectResult] ~ {
ENABLE UNWIND => NULL;
WHILE NOT r.done DO WAIT r.waitForDone ENDLOOP;
};
NotifyDone:
ENTRY
PROC [r: GetConnectResult] ~ {
ENABLE UNWIND => NULL;
r.done ← TRUE; NOTIFY r.waitForDone;
};
GetServerPupName:
PUBLIC
PROC [server:
ROPE]
RETURNS [pupServer:
ROPE] ~ {
IF Rope.Find[server, ".", 0] < 0 THEN RETURN [server];
Names with "." are GVNames (Grapevine names), so ask Grapevine to look them up
{
foundInCache: BOOL;
connect: GVBasics.Connect;
r: GetConnectResult;
[foundInCache, connect] ← FindInGrapevineCache[server];
IF foundInCache THEN RETURN[connect];
r ← NEW[GetConnectResultObject];
TRUSTED {
Process.EnableAborts[@r.waitForDone];
Process.Detach[FORK DoGetConnect[server, r]]; -- sets r.info, r.connect
};
WaitForDone[r];
If successful, use the connect as the server name for STP.Open ...
IF (r.info = group)
OR (r.info = individual)
THEN {
AddToGrapevineCache[name~server, connect~r.connect];
RETURN[r.connect];
};
};
};
DoGetConnect:
PROC [server:
ROPE, r: GetConnectResult] ~ {
[r.info, r.connect] ← GVNames.GetConnect[server];
NotifyDone[r];
};
}.