-- File: IFSFileImplB.mesa
-- Last edited by:
-- Levin - 13-Jan-82 10:03:26
DIRECTORY
IFSFile USING [
AccessFailure, Completer, defaultTime, Error, FileTime, OpenOptions, Position, Problem],
IFSFilePrivate USING [
connectionTimeout, DoRead, DoWrite, FileAddressToPosition, FileHandle, fileLockTimeout,
FileObject, fileTimeAddress, FileWatcher, FreeSequinForFS, FSInstance, FSObject,
GetIORequestBlock, GetSequinForFS, IFSTIME, IFSTimes, InsertFile, IORequest,
PositionToFileAddress, PurgeFile, ReleaseFile, ValidateFile, zone],
Inline USING [LongCOPY],
Leaf USING [
Answer, closeOp, deleteOp, FileAddress, IfsError, openOp, paramsOp,
Request, RequestObject],
Mopcodes USING [zEXCH],
Sequin USING [
Broken, Buffer, Get, GetEmptyBuffer, Put, ReleaseBuffer],
Time USING [Current];
IFSFileImplB: MONITOR LOCKS file.LOCK USING file: IFSFilePrivate.FileHandle
IMPORTS IFSFile, IFSFilePrivate, Inline, Sequin, Time
EXPORTS IFSFile, IFSFilePrivate =
BEGIN OPEN IFSFilePrivate;
-- Types --
IOSynchRecord: TYPE = RECORD [
done: BOOLEAN,
outcome: IFSFile.Problem,
file: FileHandle,
finished: CONDITION ← [timeout: 0]];
IOSynch: TYPE = LONG POINTER TO IOSynchRecord;
-- Miscellaneous --
IllegalExtend: ERROR = CODE;
IllegalTruncate: ERROR = CODE;
PacketTooSmall: ERROR = CODE;
-- Procedures, Types, and Signals Exported to IFSFile --
FileObject: PUBLIC TYPE = IFSFilePrivate.FileObject;
FSObject: PUBLIC TYPE = IFSFilePrivate.FSObject;
Open: PUBLIC PROCEDURE [
instance: FSInstance, name: LONG STRING, options: IFSFile.OpenOptions ← oldReadOnly]
RETURNS [file: FileHandle] =
BEGIN
DoOpen: PROCEDURE [file: FileHandle] RETURNS [problem: IFSFile.AccessFailure] =
BEGIN
buffer: Sequin.Buffer;
request: Leaf.Request;
openOpOffset: CARDINAL ← 0;
Flush: PROCEDURE =
BEGIN
Sequin.Put[file.sequin, buffer];
buffer ← Sequin.GetEmptyBuffer[];
request ← LOOPHOLE[buffer.data];
openOpOffset ← 0;
END;
MapError: PROCEDURE [leafError: Leaf.IfsError] RETURNS [IFSFile.AccessFailure] =
{RETURN[
SELECT leafError FROM
accessDenied, illegalDIFAccess,
IN [userName .. connectPassword] => accessDenied,
fileNotFound, dirNotFound => notFound,
fileAlreadyExists => alreadyExists,
IN [nameMalformed .. nameTooLong] => illegalFileName,
fileBusy => accessConflict,
ENDCASE => other]};
DO
fromCache: BOOLEAN;
BEGIN -- for EXITS serverDead
[file.sequin, fromCache] ← GetSequinForFS[instance];
buffer ← Sequin.GetEmptyBuffer[];
request ← LOOPHOLE[buffer.data];
IF ~fromCache THEN
BEGIN
-- send paramsOp only on fresh sequin
request↑ ← [Leaf.paramsOp,
params[packetDataBytes: buffer.maxBytes,
fileLockTimeout: fileLockTimeout/5,
connectionTimeout: connectionTimeout/5]];
buffer.nBytes ← openOpOffset ← Leaf.paramsOp.length;
IF buffer.maxBytes - buffer.nBytes <= Leaf.openOp.length THEN
Flush[! Sequin.Broken => GO TO serverDead]
ELSE request ← LOOPHOLE[@buffer.data.words[SIZE[params Leaf.RequestObject]]];
END;
request↑ ← [Leaf.openOp, open[]];
BEGIN -- for handling PacketTooSmall
ENABLE PacketTooSmall =>
{IF openOpOffset ~= 0 THEN {Flush[! Sequin.Broken => GO TO serverDead]; RETRY}};
fs: FSInstance = file.fs;
WITH openReq: request SELECT open FROM
open =>
BEGIN
IF options ~= oldReadOnly THEN openReq.write ← openReq.extend ← TRUE;
SELECT options FROM
oldOrNew => openReq.create ← TRUE;
new => {openReq.vDefault ← next; openReq.create ← TRUE};
ENDCASE;
END;
ENDCASE;
buffer.nBytes ← buffer.nBytes + Leaf.openOp.length;
AddStringToBuffer[@buffer, fs.primaryName];
AddStringToBuffer[@buffer, fs.primaryPassword];
AddStringToBuffer[@buffer, fs.secondaryName];
AddStringToBuffer[@buffer, fs.secondaryPassword];
AddStringToBuffer[@buffer, name];
END; -- of handling PacketTooSmall
request.op.length ← buffer.nBytes - openOpOffset;
Sequin.Put[file.sequin, buffer ! Sequin.Broken => GO TO serverDead];
buffer ← Sequin.Get[file.sequin ! Sequin.Broken => GO TO serverDead];
BEGIN -- for cantOpen
answer: Leaf.Answer ← LOOPHOLE[buffer.data];
IF ~fromCache THEN
BEGIN
-- process params response
IF answer.op.sense ~= reply THEN {problem ← other; GO TO cantOpen};
WITH ans: answer SELECT answer.op.type FROM
error => {problem ← MapError[ans.error]; GO TO cantOpen};
params => NULL;
ENDCASE => {problem ← other; GO TO cantOpen};
IF answer.op.length ~= buffer.nBytes THEN answer ← answer + answer.op.length/2
ELSE
BEGIN
Sequin.ReleaseBuffer[buffer];
buffer ← Sequin.Get[file.sequin ! Sequin.Broken => GO TO serverDead];
answer ← LOOPHOLE[buffer.data];
END;
END;
IF answer.op.sense ~= reply THEN {problem ← other; GO TO cantOpen};
WITH ans: answer SELECT answer.op.type FROM
error => {problem ← MapError[ans.error]; GO TO cantOpen};
open =>
BEGIN
problem ← ok;
file.leafHandle ← ans.handle;
file.length ← FileAddressToPosition[ans.eofAddress];
END;
ENDCASE => {problem ← other; GO TO cantOpen};
EXITS
cantOpen => FreeSequinForFS[instance, file.sequin, problem ~= other];
END; -- for cantOpen
Sequin.ReleaseBuffer[buffer];
EXIT
EXITS
serverDead =>
BEGIN
FreeSequinForFS[instance, file.sequin, FALSE];
IF fromCache THEN LOOP -- perhaps sequin had timed out
ELSE {problem ← io; EXIT};
END;
END;
ENDLOOP;
END;
file ← InsertFile[instance, name, DoOpen];
file.watcher ← FORK FileWatcher[file];
END;
CantOpen: PUBLIC ERROR [reason: IFSFile.AccessFailure] = CODE;
Close: PUBLIC PROCEDURE [file: FileHandle] =
BEGIN
DoClose: PROCEDURE [file: FileHandle] = {CleanupFile[file, close]};
ValidateFile[file];
ReleaseFile[file, DoClose];
END;
Abandon: PUBLIC PROCEDURE [file: FileHandle] =
BEGIN
DoAbandon: PROCEDURE [file: FileHandle] = {CleanupFile[file, abandon]};
ValidateFile[file];
ReleaseFile[file, DoAbandon];
END;
Destroy: PUBLIC PROCEDURE [file: FileHandle] =
BEGIN
DoDestroy: PROCEDURE [file: FileHandle] = {CleanupFile[file, destroy]};
ValidateFile[file];
PurgeFile[file, DoDestroy];
END;
GetLength: PUBLIC ENTRY PROCEDURE [file: FileHandle] RETURNS [IFSFile.Position] =
BEGIN
RETURN[file.length]
END;
SetLength: PUBLIC PROCEDURE [file: FileHandle, length: IFSFile.Position] =
BEGIN
oldLength: IFSFile.Position;
ValidateFile[file];
oldLength ← GetLength[file];
IF oldLength ~= length THEN DoIO[file, PrepareSetLength[file, length]];
END;
GetTimes: PUBLIC PROCEDURE [file: FileHandle]
RETURNS [read, write, create: IFSFile.FileTime] =
BEGIN
times: IFSTimes;
request: IORequest ← GetIORequestBlock[];
request↑ ← [buffer: @times, address: fileTimeAddress,
op: read, bytesToGo: 2*SIZE[IFSTimes]];
DoIO[file, request];
RETURN[
IFSToMesaTime[times.read], IFSToMesaTime[times.write],
IFSToMesaTime[times.create]]
END;
SetCreation: PUBLIC PROCEDURE [
file: FileHandle, create: IFSFile.FileTime ← IFSFile.defaultTime] =
BEGIN
creation: IFSTIME;
request: IORequest ← GetIORequestBlock[];
IF create = IFSFile.defaultTime THEN create ← Time.Current[];
creation ← MesaToIFSTime[create];
request↑ ← [buffer: @creation, address: fileTimeAddress,
op: write, bytesToGo: 2*SIZE[IFSTIME]];
DoIO[file, request];
END;
-- Procedures Exported to IFSFilePrivate --
CopyString: PUBLIC PROCEDURE [s: LONG STRING] RETURNS [ns: LONG STRING] =
BEGIN
IF s = NIL THEN RETURN[NIL];
ns ← zone.NEW[StringBody[s.length]];
Inline.LongCOPY[from: @s.text, to: @ns.text, nwords: (s.length+1)/2];
ns.length ← s.length;
END;
AddStringToBuffer: PUBLIC PROCEDURE [buffer: POINTER TO Sequin.Buffer, s: LONG STRING] =
BEGIN
startWord: CARDINAL = (buffer.nBytes + 1)/2;
nWords: CARDINAL;
IF s = NIL THEN s ← ""L;
nWords ← (s.length + 1)/2;
IF buffer.maxBytes < 2*(startWord + nWords) THEN ERROR PacketTooSmall;
buffer.data.words[startWord] ← s.length;
Inline.LongCOPY[from: @s.text, to: @buffer.data.words[startWord + 1], nwords: nWords];
buffer.nBytes ← buffer.nBytes + 2*(nWords + 1);
END;
-- Internal Procedures --
PrepareSetLength: ENTRY PROCEDURE [file: FileHandle, length: IFSFile.Position]
RETURNS [request: IORequest] =
BEGIN
request ← GetIORequestBlock[];
request↑ ← [buffer: NIL,
address: PositionToFileAddress[length, [write[newEOF: TRUE]]],
op: write, bytesToGo: 0];
file.length ← length;
END;
CleanupFile: ENTRY PROCEDURE [file: FileHandle, op: {close, destroy, abandon}] =
BEGIN
toCache: BOOLEAN ← FALSE;
file.state ← closing; NOTIFY file.ioSynch;
UNTIL file.state = closed DO WAIT file.ioSynch ENDLOOP;
IF (JOIN file.watcher) AND op ~= abandon THEN
BEGIN OPEN Sequin;
buffer: Buffer ← GetEmptyBuffer[];
request: Leaf.Request ← LOOPHOLE[buffer.data];
IF op = close THEN
BEGIN
request↑ ← [Leaf.closeOp, close[handle: file.leafHandle]];
buffer.nBytes ← Leaf.closeOp.length;
END
ELSE -- op = destroy
BEGIN
request↑ ← [Leaf.deleteOp, delete[handle: file.leafHandle]];
buffer.nBytes ← Leaf.deleteOp.length;
END;
Put[file.sequin, buffer ! Broken => GO TO ignore];
buffer ← Get[file.sequin ! Broken => GO TO ignore];
ReleaseBuffer[buffer];
toCache ← TRUE;
EXITS
ignore => NULL;
END;
FreeSequinForFS[file.fs, file.sequin, toCache];
zone.FREE[@file.name];
END;
DoIO: PROCEDURE [file: FileHandle, request: IORequest] =
BEGIN
ioSynch: IOSynchRecord ← [done: FALSE, outcome: ok, file: file];
WaitForCompletion: ENTRY PROCEDURE [file: FileHandle] = INLINE
{UNTIL ioSynch.done DO WAIT ioSynch.finished; ENDLOOP};
request.proc ← NoteCompletion; request.arg ← LONG[@ioSynch];
SELECT request.op FROM
read => DoRead[file, request];
write => DoWrite[file, request];
ENDCASE;
WaitForCompletion[file];
IF ioSynch.outcome ~= ok THEN ERROR IFSFile.Error[ioSynch.outcome];
END;
NoteCompletion: IFSFile.Completer =
BEGIN
ioSynch: IOSynch = LOOPHOLE[arg];
DoNotify: ENTRY PROCEDURE [file: FileHandle] = INLINE
BEGIN
ioSynch.done ← TRUE; ioSynch.outcome ← outcome;
NOTIFY ioSynch.finished;
END;
DoNotify[ioSynch.file];
END;
IFSToMesaTime: PROCEDURE [IFSTIME] RETURNS [IFSFile.FileTime] = MACHINE CODE
BEGIN Mopcodes.zEXCH; END;
MesaToIFSTime: PROCEDURE [IFSFile.FileTime] RETURNS [IFSTIME] = MACHINE CODE
BEGIN Mopcodes.zEXCH; END;
END.