DIRECTORY
BasicTime USING [earliestGMT, GMT, Now, Period],
FS USING [Error, ErrorDesc, FileType],
FSBackdoor USING [ErrorCode, ProduceError, Version],
FSPseudoServers USING [AvoidRemoteCheck, TranslateForRead, TranslateForWrite],
FSRemoteFileExtras USING [ConfirmProc, ConfirmRetrieveProc, GetServerProc, InfoProc, NameProc, ServerHandle, ServerObject, ServerProcs, ServerProcsObject, SweepProc, ValidateProc],
IO USING [STREAM],
Process USING [Abort, CheckForAbort, Detach, EnableAborts, MsecToTicks, PauseMsec, SetTimeout],
Rope USING [Cat, Flatten, Length, Match, ROPE],
SymTab USING [Create, Delete, EachPairAction, Fetch, Pairs, Ref, Store]
;
FSRemoteFileExtrasImpl:
CEDAR
MONITOR
IMPORTS BasicTime, FS, FSBackdoor, FSPseudoServers, Process, Rope, SymTab
EXPORTS FSRemoteFileExtras
~{
Copied Types
ConfirmProc: TYPE ~ FSRemoteFileExtras.ConfirmProc;
ConfirmRetrieveProc: TYPE ~ FSRemoteFileExtras.ConfirmRetrieveProc;
FileType: TYPE ~ FS.FileType;
GetServerProc: TYPE ~ FSRemoteFileExtras.GetServerProc;
GMT: TYPE ~ BasicTime.GMT;
InfoProc: TYPE ~ FSRemoteFileExtras.InfoProc;
NameProc: TYPE ~ FSRemoteFileExtras.NameProc;
ROPE: TYPE ~ Rope.ROPE;
ServerHandle: TYPE ~ FSRemoteFileExtras.ServerHandle;
Version: TYPE ~ FSBackdoor.Version;
Parameters
msecBetweenSweeps: CARD ← 60000;
nonexistentServerTTL: CARD ← 300;
waitForServerTimeout: CARD ← 20000;
Exported to FSRemoteFile
Delete:
PUBLIC
PROC [server, file:
ROPE, wantedCreatedTime:
GMT, proc: ConfirmProc] ~ {
uServer: ROPE ← UnbracketServer[server];
h: ServerHandle ← GetServer[FSPseudoServers.TranslateForWrite[uServer]];
h.procs.delete[h, file, wantedCreatedTime, proc];
};
EnumerateForInfo:
PUBLIC
PROC [server, pattern:
ROPE, proc: InfoProc] ~ {
uServer: ROPE ← UnbracketServer[server];
servers: LIST OF ROPE ← FSPseudoServers.TranslateForRead[uServer];
replicated: BOOL = FSPseudoServers.AvoidRemoteCheck[server];
gotOne: BOOL ← FALSE;
savedErrorDesc: FS.ErrorDesc;
procInner: InfoProc ~ {
gotOne ← TRUE;
Process.CheckForAbort[];
RETURN [proc[fullFName, attachedTo, created, bytes, keep, fileType]];
};
FOR each:
LIST
OF
ROPE ← servers, each.rest
WHILE (each #
NIL)
DO
ENABLE
FS.Error => {
IF gotOne OR ((error.code = $unknownFile) AND replicated) THEN REJECT;
IF each = servers THEN savedErrorDesc ← error;
LOOP;
};
h: ServerHandle ← GetServer[each.first];
h.procs.enumerateForInfo[h, pattern, procInner];
IF gotOne OR replicated THEN RETURN;
ENDLOOP;
IF savedErrorDesc.code # NIL THEN ERROR FS.Error[savedErrorDesc];
Should I raise FS.Error if NOT gotOne?
};
EnumerateForNames:
PUBLIC
PROC [server, pattern:
ROPE, proc: NameProc] ~ {
uServer: ROPE ← UnbracketServer[server];
servers: LIST OF ROPE ← FSPseudoServers.TranslateForRead[uServer];
replicated: BOOL = FSPseudoServers.AvoidRemoteCheck[server];
gotOne: BOOL ← FALSE;
savedErrorDesc: FS.ErrorDesc;
procInner: NameProc ~ {
gotOne ← TRUE;
Process.CheckForAbort[];
RETURN [proc[fullFName]];
};
FOR each:
LIST
OF
ROPE ← servers, each.rest
WHILE (each #
NIL)
DO
ENABLE
FS.Error => {
IF gotOne OR ((error.code = $unknownFile) AND replicated) THEN REJECT;
IF each = servers THEN savedErrorDesc ← error;
LOOP;
};
h: ServerHandle ← GetServer[each.first];
h.procs.enumerateForNames[h, pattern, procInner];
IF gotOne OR replicated THEN RETURN;
ENDLOOP;
IF savedErrorDesc.code # NIL THEN ERROR FS.Error[savedErrorDesc];
Should I raise FS.Error if NOT gotOne?
};
Info:
PUBLIC
PROC [server, file:
ROPE, wantedCreatedTime:
GMT]
RETURNS [version: Version, bytes: INT, created: GMT, fileType: FileType] ~ {
uServer: ROPE ← UnbracketServer[server];
servers: LIST OF ROPE ← FSPseudoServers.TranslateForRead[uServer];
replicated: BOOL = FSPseudoServers.AvoidRemoteCheck[server];
savedErrorDesc: FS.ErrorDesc;
FOR each:
LIST
OF
ROPE ← servers, each.rest
WHILE (each #
NIL)
DO
ENABLE
FS.Error => {
IF (error.code = $unknownFile) AND replicated THEN REJECT;
IF each = servers THEN savedErrorDesc ← error;
LOOP;
};
h: ServerHandle ← GetServer[each.first];
[version, bytes, created, fileType] ← h.procs.getInfo[h, file, wantedCreatedTime];
RETURN;
ENDLOOP;
ERROR FS.Error[savedErrorDesc];
};
Rename:
PUBLIC
PROC [server, fromFile:
ROPE, fromCreated:
GMT, toFile:
ROPE, proc: ConfirmProc] ~ {
uServer: ROPE ← UnbracketServer[server];
h: ServerHandle ← GetServer[FSPseudoServers.TranslateForWrite[uServer]];
h.procs.rename[h, fromFile, fromCreated, toFile, proc];
};
Retrieve:
PUBLIC
PROC [server, file:
ROPE, wantedCreatedTime:
GMT, proc: ConfirmRetrieveProc, checkFileType:
BOOL, fileType: FileType] ~ {
uServer: ROPE ← UnbracketServer[server];
servers: LIST OF ROPE ← FSPseudoServers.TranslateForRead[uServer];
replicated: BOOL = FSPseudoServers.AvoidRemoteCheck[server];
savedErrorDesc: FS.ErrorDesc;
FOR each:
LIST
OF
ROPE ← servers, each.rest
WHILE (each #
NIL)
DO
ENABLE
FS.Error => {
IF (error.code = $unknownFile) AND replicated THEN REJECT;
IF each = servers THEN savedErrorDesc ← error;
LOOP;
};
h: ServerHandle ← GetServer[each.first];
h.procs.retrieve[h, file, wantedCreatedTime, proc, checkFileType, fileType];
RETURN;
ENDLOOP;
ERROR FS.Error[savedErrorDesc];
};
Store:
PUBLIC
PROC [server, file:
ROPE, str:
IO.
STREAM, created:
GMT, proc: ConfirmProc] ~ {
uServer: ROPE ← UnbracketServer[server];
h: ServerHandle ← GetServer[FSPseudoServers.TranslateForWrite[uServer]];
h.procs.store[h, file, str, created, proc];
};
Registration
Registration: TYPE ~ REF RegistrationObject;
RegistrationObject:
TYPE ~
RECORD [
next: Registration,
flavor: ATOM,
getServer: GetServerProc
];
registrations: Registration ← NIL;
Register:
PUBLIC
ENTRY
PROC [flavor:
ATOM, getServer: GetServerProc] ~ {
p, prev: Registration;
FOR p ← registrations, p.next
DO
IF p.flavor = flavor
THEN {
IF prev = NIL THEN registrations ← p.next ELSE prev.next ← p.next;
EXIT;
};
prev ← p;
ENDLOOP;
IF getServer #
NIL
THEN registrations ← NEW[RegistrationObject ← [registrations, flavor, getServer]];
};
Server Handle Cache
serverTab: SymTab.Ref ← SymTab.Create[];
lastSweepTime: BasicTime.GMT ← BasicTime.earliestGMT;
Daemon:
PROC ~ {
thisSweepTime: BasicTime.GMT;
seconds: INT;
CallSweep: SymTab.EachPairAction
-- [key, val] RETURNS [quit] -- ~ {
h: ServerHandle ~ NARROW[val];
h.procs.sweep[h, seconds];
RETURN [FALSE];
};
DO
Process.PauseMsec[msecBetweenSweeps];
thisSweepTime ← BasicTime.Now[];
seconds ← BasicTime.Period[from~lastSweepTime, to~thisSweepTime];
[] ← SymTab.Pairs[serverTab, CallSweep];
lastSweepTime ← thisSweepTime;
ENDLOOP;
};
Finding a server
GetServerResponse: TYPE ~ REF GetServerResponseObject;
GetServerResponseObject:
TYPE ~
RECORD [
gotServer: CONDITION,
handle: ServerHandle
];
GetServer:
PROC [server:
ROPE]
RETURNS [h: ServerHandle] ~ {
h ← NARROW[SymTab.Fetch[serverTab, server].val];
IF (h =
NIL)
OR (
NOT h.procs.validate[h])
THEN {
kids: LIST OF PROCESS ← NIL;
response: GetServerResponse;
response ← NEW [GetServerResponseObject];
FOR each: Registration ← registrations, each.next
WHILE each #
NIL
DO
kids ← CONS[ (FORK DoGetServer[each, server, response]), kids ];
ENDLOOP;
h ← WaitForResponse[response];
FOR child:
LIST
OF
PROCESS ← kids, child.rest
WHILE child #
NIL
DO
TRUSTED { Process.Abort[child.first] };
TRUSTED { Process.Detach[child.first] };
ENDLOOP;
IF h = NIL THEN h ← MakeNonexistentServer[server];
[] ← SymTab.Store[serverTab, server, h];
};
IF h.flavor = $nonexistentServer THEN ErrorNonexistentServer[h];
};
DoGetServer:
PROC [r: Registration, server:
ROPE, response: GetServerResponse] ~ {
ENABLE FS.Error => CONTINUE;
h: ServerHandle;
NotifyGotHandle:
ENTRY
PROC ~ {
IF response.handle # NIL THEN RETURN;
response.handle ← h;
NOTIFY response.gotServer;
};
IF (h ← r.getServer[server]) # NIL THEN NotifyGotHandle[];
};
WaitForResponse:
ENTRY
PROC [response: GetServerResponse]
RETURNS [ServerHandle] ~ {
IF response.handle =
NIL
THEN
TRUSTED {
Process.EnableAborts[@response.gotServer];
Process.SetTimeout[@response.gotServer, Process.MsecToTicks[waitForServerTimeout]];
WAIT response.gotServer;
};
RETURN [response.handle];
};
Controlling the flavor of a cached server
SetCachedServer:
PUBLIC
PROC [server:
ROPE, flavor:
ATOM] ~ {
r: Registration;
h: ServerHandle;
ClearCachedServer[server];
FOR r ← registrations, r.next WHILE (r # NIL) AND (r.flavor # flavor) DO NULL ENDLOOP;
IF r = NIL THEN RETURN;
h ← r.getServer[server];
IF h = NIL THEN h ← MakeNonexistentServer[server];
[] ← SymTab.Store[serverTab, server, h];
};
ClearCachedServer:
PUBLIC
PROC [server:
ROPE] ~ {
[] ← SymTab.Delete[serverTab, server];
};
Nonexistent Server Implementation
NonexistentServerData: TYPE ~ REF NonexistentServerDataObject;
NonexistentServerDataObject:
TYPE ~
RECORD [
ttl: CARD ← nonexistentServerTTL
];
nonexistentServerProcs: FSRemoteFileExtras.ServerProcs ←
NEW[FSRemoteFileExtras.ServerProcsObject ← [
sweep~SweepNonexistentServer,
validate~ValidateNonexistentServer,
delete~NIL,
enumerateForInfo~NIL,
enumerateForNames~NIL,
getInfo~NIL,
rename~NIL,
retrieve~NIL,
store~NIL
]
];
MakeNonexistentServer:
PROC [name:
ROPE]
RETURNS [h: ServerHandle] ~ {
d: NonexistentServerData ← NEW[NonexistentServerDataObject];
h ← NEW[FSRemoteFileExtras.ServerObject ← [$nonexistentServer, name, nonexistentServerProcs, d]];
};
SweepNonexistentServer: FSRemoteFileExtras.SweepProc
-- [h: ServerHandle, seconds: INT] -- ~ {
d: NonexistentServerData ← NARROW[h.data];
IF d.ttl > CARD[seconds] THEN d.ttl ← d.ttl - seconds;
};
ValidateNonexistentServer: FSRemoteFileExtras.ValidateProc
-- [h: ServerHandle] RETURNS [ok: BOOL] -- ~ {
d: NonexistentServerData ← NARROW[h.data];
RETURN[d.ttl > 0];
};
ErrorNonexistentServer:
PROC [h: ServerHandle, file:
ROPE ←
NIL] ~ {
FSBackdoor.ProduceError[FSBackdoor.ErrorCode.unknownServer, Rope.Cat["Couldn't find the server for \"", BracketServer[h.name], file, "\""]];
};
Name manipulation
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 ];
};
Initialization
TRUSTED { Process.Detach[ FORK Daemon[] ] };
}...