FSRemoteFileImpl.mesa
Copyright Ó 1987 by Xerox Corporation. All rights reserved.
Demers, October 28, 1987 5:02:20 pm PST
DIRECTORY
Atom USING [GetPName],
BasicTime USING [earliestGMT, GMT, Now, Period],
Booting USING [RegisterProcs, RollbackProc],
FS USING [Error, ErrorDesc, FileType, InfoProc],
FSBackdoor USING [ErrorCode, ProduceError, Version],
FSPseudoServers USING [AvoidRemoteCheck, TranslateForRead, TranslateForWrite],
FSRemoteFile USING [ConfirmProc],
FSRemoteFileBackdoor USING [ConfirmProc, ConfirmRetrieveProc, GetServerProc, InfoProc, NameProc, ServerHandle, ServerObject, ServerProcs, ServerProcsObject, SweepProc, ValidateProc],
IO USING [STREAM],
Process USING [Abort, CheckForAbort, Detach, EnableAborts, MsecToTicks, SetTimeout],
Rope USING [Cat, Concat, EqualSubstrs, Find, FindBackward, Flatten, InlineFetch, InlineLength, IsPrefix, Length, ROPE, Substr],
SymTab USING [Create, Delete, EachPairAction, Erase, Fetch, Pairs, Ref, Store],
UserCredentials USING [CredentialsChangeProc, RegisterForChange]
;
FSRemoteFileImpl: CEDAR MONITOR
IMPORTS Atom, BasicTime, Booting, FS, FSBackdoor, FSPseudoServers, Process, Rope, SymTab, UserCredentials
EXPORTS FSRemoteFile, FSRemoteFileBackdoor
~{
Types
ConfirmProc: TYPE ~ FSRemoteFile.ConfirmProc;
ConfirmRetrieveProc: TYPE ~ FSRemoteFileBackdoor.ConfirmRetrieveProc;
FileType: TYPE ~ FS.FileType;
GetServerProc: TYPE ~ FSRemoteFileBackdoor.GetServerProc;
GMT: TYPE ~ BasicTime.GMT;
InfoProc: TYPE ~ FSRemoteFileBackdoor.InfoProc;
NameProc: TYPE ~ FSRemoteFileBackdoor.NameProc;
ROPE: TYPE ~ Rope.ROPE;
ServerHandle: TYPE ~ FSRemoteFileBackdoor.ServerHandle;
Version: TYPE ~ FSBackdoor.Version;
Parameters
secsBetweenSweeps: INT ← 6;
nonexistentServerTTL: CARD ← 60; -- seconds
waitForServerTimeout: INT ← 13; -- seconds
Exported to FSRemoteFile
Delete: PUBLIC PROC [server, file: ROPE, wantedCreatedTime: GMT, proc: FSRemoteFile.ConfirmProc] ~ {
pServer, psTranslation, serverPart, pathPart: ROPE;
h: ServerHandle;
pServer ← UnbracketServer[server];
psTranslation ← FSPseudoServers.TranslateForWrite[pServer];
[serverPart, pathPart] ← ParsePSTranslation[psTranslation];
h ← GetServer[serverPart];
file ← PrependPath[pathPart, InsertDefaultVersion[file, "!l", FALSE]];
h.procs.delete[h, file, wantedCreatedTime, FALSE, proc];
};
EnumerateForInfo: PUBLIC PROC [server, pattern: ROPE, proc: FS.InfoProc] ~ {
pServer: ROPE ← UnbracketServer[server];
pServerB: ROPE ← Rope.Cat["[", pServer, "]"];
replicated: BOOL ← FSPseudoServers.AvoidRemoteCheck[pServer];
psTranslations: LIST OF ROPE;
gotOne: BOOLFALSE;
savedErrorDesc: FS.ErrorDesc;
h: ServerHandle;
serverPart, pathPart: ROPE;
ProcInner: FSRemoteFileBackdoor.InfoProc -- [file, bytes, created, type] RETURNS [continue: BOOL] -- ~ {
fullFName: ROPE;
gotOne ← TRUE;
Process.CheckForAbort[];
fullFName ← Rope.Concat[pServerB, StripPath[pathPart, file]];
RETURN [proc[fullFName~fullFName, attachedTo~NIL, created~created, bytes~bytes, keep~0, fileType~type]];
};
psTranslations ← FSPseudoServers.TranslateForRead[pServer];
pattern ← InsertDefaultVersion[pattern, "!*", FALSE];
FOR each: LIST OF ROPE ← psTranslations, each.rest WHILE (each # NIL) DO
ENABLE FS.Error => {
IF gotOne OR ((error.code = $unknownFile) AND replicated) THEN REJECT;
IF each = psTranslations THEN savedErrorDesc ← error;
LOOP;
};
[serverPart, pathPart] ← ParsePSTranslation[each.first];
h ← GetServer[serverPart];
h.procs.enumerateForInfo[h, PrependPath[pathPart, pattern], FALSE, 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] ~ {
pServer: ROPE ← UnbracketServer[server];
pServerB: ROPE ← Rope.Cat["[", pServer, "]"];
replicated: BOOL ← FSPseudoServers.AvoidRemoteCheck[pServer];
psTranslations: LIST OF ROPE;
gotOne: BOOLFALSE;
savedErrorDesc: FS.ErrorDesc;
h: ServerHandle;
serverPart, pathPart: ROPE;
ProcInner: FSRemoteFileBackdoor.NameProc -- [file] RETURNS [continue: BOOL] -- ~ {
fullFName: ROPE;
gotOne ← TRUE;
Process.CheckForAbort[];
fullFName ← Rope.Concat[pServerB, StripPath[pathPart, file]];
RETURN [proc[fullFName]];
};
psTranslations ← FSPseudoServers.TranslateForRead[pServer];
pattern ← InsertDefaultVersion[pattern, "!*", FALSE];
FOR each: LIST OF ROPE ← psTranslations, each.rest WHILE (each # NIL) DO
ENABLE FS.Error => {
IF gotOne OR ((error.code = $unknownFile) AND replicated) THEN REJECT;
IF each = psTranslations THEN savedErrorDesc ← error;
LOOP;
};
[serverPart, pathPart] ← ParsePSTranslation[each.first];
h ← GetServer[serverPart];
h.procs.enumerateForNames[h, PrependPath[pathPart, pattern], FALSE, 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] ~ {
pServer: ROPE ← UnbracketServer[server];
pServerB: ROPE ← Rope.Cat["[", pServer, "]"];
replicated: BOOL ← FSPseudoServers.AvoidRemoteCheck[pServer];
psTranslations: LIST OF ROPE;
savedErrorDesc: FS.ErrorDesc;
h: ServerHandle;
serverPart, pathPart: ROPE;
psTranslations ← FSPseudoServers.TranslateForRead[pServer];
file ← InsertDefaultVersion[file, "!h", FALSE];
FOR each: LIST OF ROPE ← psTranslations, each.rest WHILE (each # NIL) DO
ENABLE FS.Error => {
IF (error.code = $unknownFile) AND replicated THEN REJECT;
IF each = psTranslations THEN savedErrorDesc ← error;
LOOP;
};
[serverPart, pathPart] ← ParsePSTranslation[each.first];
h ← GetServer[serverPart];
[version, bytes, created, fileType] ← h.procs.getInfo[h, PrependPath[pathPart, file], wantedCreatedTime, FALSE];
RETURN;
ENDLOOP;
IF savedErrorDesc.code = NIL THEN ERROR; -- can't happen
ERROR FS.Error[savedErrorDesc];
};
Rename: PUBLIC PROC [server, fromFile: ROPE, fromCreated: GMT, toFile: ROPE, proc: ConfirmProc] ~ {
pServer, psTranslation, serverPart, pathPart: ROPE;
h: ServerHandle;
pServer ← UnbracketServer[server];
psTranslation ← FSPseudoServers.TranslateForWrite[pServer];
[serverPart, pathPart] ← ParsePSTranslation[psTranslation];
h ← GetServer[serverPart];
fromFile ← PrependPath[pathPart, InsertDefaultVersion[fromFile, "!h", FALSE]];
toFile ← PrependPath[pathPart, InsertDefaultVersion[toFile, "!", TRUE]];
h.procs.rename[h, fromFile, fromCreated, toFile, FALSE, proc];
};
Retrieve: PUBLIC PROC [
server, file: ROPE, wantedCreatedTime: GMT,
proc: PROC[fullGName: ROPE, bytes: INT, created: GMT] RETURNS [IO.STREAM],
checkFileType: BOOL, fileType: FileType
] ~ {
pServer: ROPE ← UnbracketServer[server];
pServerB: ROPE ← Rope.Cat["[", pServer, "]"];
replicated: BOOL ← FSPseudoServers.AvoidRemoteCheck[pServer];
psTranslations: LIST OF ROPE;
savedErrorDesc: FS.ErrorDesc;
h: ServerHandle;
serverPart, pathPart: ROPE;
ProcInner: FSRemoteFileBackdoor.ConfirmRetrieveProc -- [file, bytes, created, type] RETURNS [IO.STREAM] -- ~ {
fullGName: ROPE;
IF checkFileType AND (type # fileType) THEN FSBackdoor.ProduceError[$fileTypeMismatch, "file on server has the wrong file type"];
fullGName ← Rope.Concat[pServerB, StripPath[pathPart, file]];
RETURN[proc[fullGName, bytes, created]];
};
psTranslations ← FSPseudoServers.TranslateForRead[pServer];
file ← InsertDefaultVersion[file, "!h", FALSE];
FOR each: LIST OF ROPE ← psTranslations, each.rest WHILE (each # NIL) DO
ENABLE FS.Error => {
IF (error.code = $unknownFile) AND replicated THEN REJECT;
IF each = psTranslations THEN savedErrorDesc ← error;
LOOP;
};
[serverPart, pathPart] ← ParsePSTranslation[each.first];
h ← GetServer[serverPart];
h.procs.retrieve[h, PrependPath[pathPart, file], wantedCreatedTime, FALSE, ProcInner];
RETURN;
ENDLOOP;
IF savedErrorDesc.code = NIL THEN ERROR; -- can't happen
ERROR FS.Error[savedErrorDesc];
};
Store: PUBLIC PROC [server, file: ROPE, str: IO.STREAM, created: GMT, proc: FSRemoteFile.ConfirmProc] ~ {
pServer, psTranslation, serverPart, pathPart: ROPE;
h: ServerHandle;
pServer ← UnbracketServer[server];
psTranslation ← FSPseudoServers.TranslateForWrite[pServer];
[serverPart, pathPart] ← ParsePSTranslation[psTranslation];
h ← GetServer[serverPart];
file ← PrependPath[pathPart, InsertDefaultVersion[file, "!", TRUE]];
h.procs.store[h, file, FALSE, str, created, proc];
};
Registration (Exported to FSRemoteFileBackdoor)
Registration: TYPE ~ REF RegistrationObject;
RegistrationObject: TYPE ~ RECORD [
next: Registration,
flavor: ATOM,
nameSuffix: ROPE, -- constructed from flavor
getServer: GetServerProc
];
registrations: Registration ← NIL;
Register: PUBLIC ENTRY PROC [flavor: ATOM, getServer: GetServerProc] ~ {
p, prev: Registration;
nameSuffix: ROPE ← Rope.Concat["-", Atom.GetPName[flavor]];
FOR p ← registrations, p.next WHILE p # NIL 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, nameSuffix, getServer]];
};
Server Handle Cache
serverTab: SymTab.Ref ← SymTab.Create[case~FALSE];
lastSweepTime: BasicTime.GMT ← BasicTime.earliestGMT;
readyToSweep: CONDITION;
Daemon: PROC ~ {
seconds: INT;
CallSweep: SymTab.EachPairAction -- [key, val] RETURNS [quit] -- ~ {
h: ServerHandle ~ NARROW[val];
h.procs.sweep[h, CARD[seconds]];
RETURN [FALSE];
};
WaitReadyToSweep: ENTRY PROC ~ INLINE {
DO
thisSweepTime: BasicTime.GMT ~ BasicTime.Now[];
seconds ← BasicTime.Period[from~lastSweepTime, to~thisSweepTime];
IF seconds >= secsBetweenSweeps THEN {
lastSweepTime ← thisSweepTime;
EXIT;
};
WAIT readyToSweep;
ENDLOOP;
};
TRUSTED {
Process.EnableAborts[@readyToSweep];
Process.SetTimeout[@readyToSweep, Process.MsecToTicks[INT[1000]*secsBetweenSweeps]];
};
DO
WaitReadyToSweep[];
[] ← SymTab.Pairs[serverTab, CallSweep];
ENDLOOP;
};
Rollback: ENTRY Booting.RollbackProc ~ {
lastSweepTime ← BasicTime.Now[];
SymTab.Erase[serverTab];
};
CredentialsChange: ENTRY UserCredentials.CredentialsChangeProc ~ {
lastSweepTime ← BasicTime.Now[];
SymTab.Erase[serverTab];
};
Finding a server (exported to FSRemoteFileBackdoor)
GetServerResponse: TYPE ~ REF GetServerResponseObject;
GetServerResponseObject: TYPE ~ RECORD [
nRunning: CARDINAL ← 0,
wakeup: CONDITION,
handle: ServerHandle ← NIL,
downMsg: ROPENIL
];
GetServer: PUBLIC PROC [server: ROPE] RETURNS [h: ServerHandle] ~ {
Raises FS.Error if server is down.
obsolete: BOOL;
downMsg: ROPE;
kids: LIST OF PROCESSNIL;
response: GetServerResponse;
theRegistration: Registration;
started: GMT;
MakeChild: ENTRY PROC [r: Registration] RETURNS [p: PROCESS] ~ --INLINE-- {
response.nRunning ← response.nRunning + 1;
p ← FORK DoGetServer[r, server, response];
};
WaitForResponse: ENTRY PROC ~ --INLINE-- {
set [h, downMsg] ...
WHILE (response.nRunning > 0) AND (response.handle = NIL) AND (response.downMsg = NIL) DO
sinceStarted: INT ~ BasicTime.Period[from~started, to~BasicTime.Now[]];
IF sinceStarted >= waitForServerTimeout THEN EXIT;
TRUSTED { Process.SetTimeout[@response.wakeup, Process.MsecToTicks[1000*(waitForServerTimeout-sinceStarted)+500]] };
WAIT response.wakeup;
ENDLOOP;
h ← response.handle;
downMsg ← response.downMsg;
};
h ← NARROW[SymTab.Fetch[serverTab, server].val];
IF h # NIL THEN {
[obsolete, downMsg] ← h.procs.validate[h];
IF NOT obsolete THEN {
IF downMsg # NIL THEN ErrorDownServer[h, downMsg];
RETURN;
};
};
response ← NEW [GetServerResponseObject];
FOR theRegistration ← registrations, theRegistration.next WHILE theRegistration # NIL DO
suffixLen, pos: INT;
suffixLen ← Rope.Length[theRegistration.nameSuffix];
pos ← Rope.Length[server] - suffixLen;
IF (pos >= 0) AND Rope.EqualSubstrs[server, pos, suffixLen, theRegistration.nameSuffix, 0, suffixLen, FALSE]
THEN { server ← Rope.Substr[server, 0, pos]; EXIT };
ENDLOOP;
IF theRegistration # NIL
THEN {
DoGetServer[theRegistration, server, response];
h ← response.handle;
IF h # NIL THEN h.name ← Rope.Concat[h.name, theRegistration.nameSuffix];
downMsg ← response.downMsg;
}
ELSE {
started ← BasicTime.Now[];
TRUSTED { Process.EnableAborts[@response.wakeup] };
FOR each: Registration ← registrations, each.next WHILE each # NIL DO
kids ← CONS [MakeChild[each], kids];
ENDLOOP;
WaitForResponse[];
FOR kid: LIST OF PROCESS ← kids, kid.rest WHILE kid # NIL DO
TRUSTED { Process.Abort[kid.first] };
TRUSTED { Process.Detach[kid.first] };
ENDLOOP;
};
IF h = NIL THEN {
IF downMsg = NIL THEN downMsg ← "unknown server";
h ← MakeNonexistentServer[server, downMsg];
};
[] ← SymTab.Store[serverTab, h.name, h];
IF downMsg # NIL THEN ErrorDownServer[h, downMsg];
};
DoGetServer: PROC [r: Registration, server: ROPE, response: GetServerResponse] ~ {
h: ServerHandle;
downMsg: ROPE;
NotifyDone: ENTRY PROC ~ --INLINE-- {
response.nRunning ← response.nRunning - 1;
IF (response.handle # NIL) OR (response.downMsg # NIL) THEN RETURN;
SELECT TRUE FROM
(h # NIL) OR (downMsg # NIL) => -- found it -- {
response.handle ← h;
response.downMsg ← downMsg;
NOTIFY response.wakeup;
};
(response.nRunning = 0) => -- nobody will find it -- {
NOTIFY response.wakeup;
};
ENDCASE;
};
[h, downMsg] ← r.getServer[server ! FS.Error => CONTINUE];
NotifyDone[];
};
ErrorDownServer: PROC [h: ServerHandle, downMsg: ROPE] ~ {
FSBackdoor.ProduceError[FSBackdoor.ErrorCode.unknownServer, Rope.Cat["Server \"[", h.name, "]\" unavailable - ", downMsg]];
};
Controlling the flavor of a cached server (Exported to FSRemoteFileBackdoor)
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~h] ← r.getServer[server];
IF h = NIL THEN h ← MakeNonexistentServer[server, "unknown 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,
downMsg: ROPE
];
nonexistentServerProcs: FSRemoteFileBackdoor.ServerProcs ←
NEW[FSRemoteFileBackdoor.ServerProcsObject ← [
sweep~SweepNonexistentServer,
validate~ValidateNonexistentServer,
delete~NIL,
enumerateForInfo~NIL,
enumerateForNames~NIL,
getInfo~NIL,
rename~NIL,
retrieve~NIL,
store~NIL,
do~NIL
]
];
MakeNonexistentServer: PROC [name, downMsg: ROPE] RETURNS [h: ServerHandle] ~ {
d: NonexistentServerData ← NEW[NonexistentServerDataObject ← [downMsg~downMsg]];
h ← NEW[FSRemoteFileBackdoor.ServerObject ← [$nonexistentServer, name, nonexistentServerProcs, d]];
};
SweepNonexistentServer: FSRemoteFileBackdoor.SweepProc -- [h: ServerHandle, seconds: CARD] -- ~ {
d: NonexistentServerData ← NARROW[h.data];
IF d.ttl > seconds THEN d.ttl ← d.ttl - seconds ELSE d.ttl ← 0;
};
ValidateNonexistentServer: FSRemoteFileBackdoor.ValidateProc -- [h: ServerHandle] RETURNS [obsolete: BOOL, down: BOOL] -- ~ {
d: NonexistentServerData ← NARROW[h.data];
RETURN[(d.ttl = 0), d.downMsg];
};
Unimplemented operations
ErrorNotImplemented: PUBLIC PROC [h: ServerHandle, file, msg: ROPE] ~ {
r: ROPENIL;
IF h # NIL THEN r ← Rope.Cat["[", h.name, "]"];
IF file # NIL THEN r ← Rope.Concat[r, file];
IF (msg # NIL) AND (r # NIL) THEN r ← Rope.Concat[r, ": "];
r ← Rope.Concat[r, msg];
FSBackdoor.ProduceError[FSBackdoor.ErrorCode.notImplemented, r];
};
Name manipulation
InsertDefaultVersion: PROC [file, versionPart: ROPE, smash: BOOLFALSE]
RETURNS [ROPE] ~ {
pos: INT ← Rope.FindBackward[file, "!"];
IF pos < 0 THEN RETURN [Rope.Concat[file, versionPart]];
IF smash THEN RETURN [Rope.Concat[Rope.Substr[file, 0, pos], versionPart]];
RETURN [file];
};
ParsePSTranslation: PROC [psTranslation: ROPE] RETURNS [server, path: ROPE] ~ {
len, pos: INT;
len ← Rope.InlineLength[psTranslation];
IF (len = 0) OR (Rope.InlineFetch[psTranslation, 0] # '[)
THEN RETURN [psTranslation, NIL];
pos ← Rope.Find[psTranslation, "]"];
IF pos < 0 THEN RETURN [Rope.Substr[psTranslation, 1], NIL];
server ← Rope.Substr[psTranslation, 1, pos-1];
IF pos < (len-1) THEN path ← Rope.Substr[psTranslation, pos+1];
};
PrependPath: PROC [path, file: ROPE] RETURNS [ROPE] ~ {
pathLen: INT ← Rope.InlineLength[path];
fileLen: INT ← Rope.InlineLength[file];
IF pathLen = 0 THEN RETURN [file];
IF fileLen = 0 THEN RETURN [path];
IF Rope.InlineFetch[path, pathLen-1] # '> THEN path ← Rope.Concat[path, ">"];
SELECT TRUE FROM
Rope.IsPrefix["<>", file] => RETURN [Rope.Concat[path, Rope.Substr[file, 2]]];
Rope.IsPrefix["<", file] => RETURN [Rope.Concat[path, Rope.Substr[file, 1]]];
ENDCASE => RETURN [Rope.Concat[path, file]];
};
StripPath: PROC [path, file: ROPE] RETURNS [ROPE] ~ {
Caller asserts path is a prefix of file.
pathLen: INT ← Rope.InlineLength[path];
IF pathLen = 0 THEN RETURN [file];
IF Rope.InlineFetch[path, pathLen-1] # '> THEN {
path ← Rope.Concat[path, ">"];
pathLen ← pathLen + 1;
};
IF NOT Rope.IsPrefix[path, file, FALSE] THEN ERROR; -- make this less drastic later!
IF Rope.Find[file, ">", pathLen] >= 0
THEN RETURN [Rope.Concat["<", Rope.Substr[file, pathLen]]]
ELSE RETURN [Rope.Concat["<>", Rope.Substr[file, pathLen]]];
};
UnbracketServer: PROC[server: ROPE] RETURNS [ROPE] ~ INLINE {
IF (Rope.InlineLength[server] = 0) OR (Rope.InlineFetch[server, 0] # '[)
THEN RETURN [server];
RETURN [ Rope.Flatten[server, 1, Rope.Length[server]-2]]
};
Initialization
Booting.RegisterProcs[r~Rollback];
UserCredentials.RegisterForChange[proc~CredentialsChange];
TRUSTED { Process.Detach[ FORK Daemon[] ] };
}...