IFSFileImplB.mesa
Levin - 13-Jan-82 10:03:26
Russ Atkinson, November 9, 1983 11:44 pm
DIRECTORY
BasicTime USING [Now, ToPupTime],
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],
Leaf USING [
Answer, closeOp, deleteOp, FileAddress, IfsError, openOp, paramsOp,
Request, RequestObject],
PrincOps USING [zEXCH],
PrincOpsUtils USING [LongCopy],
Rope,
Sequin USING [
Broken, Buffer, Get, GetEmptyBuffer, Put, ReleaseBuffer];
IFSFileImplB: MONITOR
LOCKS file.LOCK USING file: IFSFilePrivate.FileHandle
IMPORTS BasicTime, IFSFile, IFSFilePrivate, PrincOpsUtils, Rope, Sequin
EXPORTS IFSFile, IFSFilePrivate = {
OPEN IFSFilePrivate;
Types --
ROPE: TYPE = Rope.ROPE;
IOSynchRecord: TYPE = RECORD [
done: BOOL,
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 PROC
[instance: FSInstance, name: ROPE, options: IFSFile.OpenOptions ← oldReadOnly]
RETURNS [file: FileHandle] = {
DoOpen: PROC [file: FileHandle] RETURNS [problem: IFSFile.AccessFailure] = {
buffer: Sequin.Buffer;
request: Leaf.Request;
openOpOffset: CARDINAL ← 0;
Flush: PROC = {
Sequin.Put[file.sequin, buffer];
buffer ← Sequin.GetEmptyBuffer[];
request ← LOOPHOLE[buffer.data];
openOpOffset ← 0;
};
MapError: PROC [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: BOOL;
{ -- for EXITS serverDead
[file.sequin, fromCache] ← GetSequinForFS[instance];
buffer ← Sequin.GetEmptyBuffer[];
request ← LOOPHOLE[buffer.data];
IF ~fromCache THEN {
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]]];
};
request^ ← [Leaf.openOp, open[]];
{ENABLE
PacketTooSmall => {
IF openOpOffset ~= 0 THEN {
Flush[! Sequin.Broken => GO TO serverDead];
RETRY};
};
fs: FSInstance = file.fs;
WITH openReq: request SELECT open FROM
open => {
IF options ~= oldReadOnly THEN openReq.write ← openReq.extend ← TRUE;
SELECT options FROM
oldOrNew => openReq.create ← TRUE;
new => {openReq.vDefault ← next; openReq.create ← TRUE};
ENDCASE;
};
ENDCASE;
buffer.nBytes ← buffer.nBytes + Leaf.openOp.length;
buffer ← AddRopeToBuffer[buffer, fs.primaryName];
buffer ← AddRopeToBuffer[buffer, fs.primaryPassword];
buffer ← AddRopeToBuffer[buffer, fs.secondaryName];
buffer ← AddRopeToBuffer[buffer, fs.secondaryPassword];
buffer ← AddRopeToBuffer[buffer, name];
}; -- 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];
{ -- for cantOpen
answer: Leaf.Answer ← LOOPHOLE[buffer.data];
IF ~fromCache THEN
{
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
{
Sequin.ReleaseBuffer[buffer];
buffer ← Sequin.Get[file.sequin ! Sequin.Broken => GO TO serverDead];
answer ← LOOPHOLE[buffer.data];
};
};
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 => {
problem ← ok;
file.leafHandle ← ans.handle;
file.length ← FileAddressToPosition[ans.eofAddress];
};
ENDCASE => {problem ← other; GO TO cantOpen};
EXITS
cantOpen => FreeSequinForFS[instance, file.sequin, problem ~= other];
}; -- for cantOpen
Sequin.ReleaseBuffer[buffer];
EXIT
EXITS
serverDead => {
FreeSequinForFS[instance, file.sequin, FALSE];
IF fromCache THEN LOOP -- perhaps sequin had timed out
ELSE {problem ← io; EXIT};
};
};
ENDLOOP;
};
file ← InsertFile[instance, name, DoOpen];
file.watcher ← FORK FileWatcher[file];
};
CantOpen: PUBLIC ERROR [reason: IFSFile.AccessFailure] = CODE;
Close: PUBLIC PROC [file: FileHandle] = {
DoClose: PROC [file: FileHandle] = {CleanupFile[file, close]};
ValidateFile[file];
ReleaseFile[file, DoClose];
};
Abandon: PUBLIC PROC [file: FileHandle] = {
DoAbandon: PROC [file: FileHandle] = {CleanupFile[file, abandon]};
ValidateFile[file];
ReleaseFile[file, DoAbandon];
};
Destroy: PUBLIC PROC [file: FileHandle] = {
DoDestroy: PROC [file: FileHandle] = {CleanupFile[file, destroy]};
ValidateFile[file];
PurgeFile[file, DoDestroy];
};
GetLength: PUBLIC ENTRY PROC [file: FileHandle] RETURNS [IFSFile.Position] = {
RETURN[file.length]
};
SetLength: PUBLIC PROC [file: FileHandle, length: IFSFile.Position] = {
oldLength: IFSFile.Position;
ValidateFile[file];
oldLength ← GetLength[file];
IF oldLength ~= length THEN DoIO[file, PrepareSetLength[file, length]];
};
GetTimes: PUBLIC PROC [file: FileHandle]
RETURNS [read, write, create: IFSFile.FileTime] = {
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]]
};
SetCreation: PUBLIC PROC [
file: FileHandle, create: IFSFile.FileTime ← IFSFile.defaultTime] = {
creation: IFSTIME;
request: IORequest ← GetIORequestBlock[];
IF create = IFSFile.defaultTime THEN create ← BasicTime.ToPupTime[BasicTime.Now[]];
creation ← MesaToIFSTime[create];
request^ ← [
buffer: @creation, address: fileTimeAddress, op: write, bytesToGo: 2*SIZE[IFSTIME]];
DoIO[file, request];
};
Procedures Exported to IFSFilePrivate --
AddRopeToBuffer: PUBLIC PROC
[buffer: Sequin.Buffer, r: ROPE] RETURNS [Sequin.Buffer] = {
flat: ROPE = Rope.Flatten[r];
s: LONG STRINGLOOPHOLE[flat];
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;
PrincOpsUtils.LongCopy[
from: @s.text, to: @buffer.data.words[startWord + 1], nwords: nWords];
buffer.nBytes ← buffer.nBytes + 2*(nWords + 1);
RETURN [buffer];
};
Internal Procedures --
PrepareSetLength: ENTRY PROC
[file: FileHandle, length: IFSFile.Position] RETURNS [request: IORequest] = {
ENABLE UNWIND => NULL;
request ← GetIORequestBlock[];
request^ ← [buffer: NIL,
address: PositionToFileAddress[length, [write[newEOF: TRUE]]],
op: write, bytesToGo: 0];
file.length ← length;
};
CleanupFile: ENTRY PROC [file: FileHandle, op: {close, destroy, abandon}] = {
ENABLE UNWIND => NULL;
toCache: BOOLFALSE;
file.state ← closing;
BROADCAST file.ioSynch;
UNTIL file.state = closed DO WAIT file.ioSynch ENDLOOP;
IF (JOIN file.watcher) AND op ~= abandon THEN {
buffer: Sequin.Buffer ← Sequin.GetEmptyBuffer[];
request: Leaf.Request ← LOOPHOLE[buffer.data];
IF op = close THEN {
request^ ← [Leaf.closeOp, close[handle: file.leafHandle]];
buffer.nBytes ← Leaf.closeOp.length;
}
ELSE {
op = destroy
request^ ← [Leaf.deleteOp, delete[handle: file.leafHandle]];
buffer.nBytes ← Leaf.deleteOp.length;
};
Sequin.Put[file.sequin, buffer ! Sequin.Broken => GO TO ignore];
buffer ← Sequin.Get[file.sequin ! Sequin.Broken => GO TO ignore];
Sequin.ReleaseBuffer[buffer];
toCache ← TRUE;
EXITS
ignore => NULL;
};
FreeSequinForFS[file.fs, file.sequin, toCache];
};
DoIO: PROC [file: FileHandle, request: IORequest] = {
ioSynch: IOSynchRecord ← [done: FALSE, outcome: ok, file: file];
WaitForCompletion: ENTRY PROC [file: FileHandle] = INLINE
{UNTIL ioSynch.done DO WAIT ioSynch.finished; ENDLOOP};
request.proc ← NoteCompletion;
request.arg ← LOOPHOLE[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];
};
NoteCompletion: IFSFile.Completer = {
ioSynch: IOSynch = LOOPHOLE[arg, IOSynch];
DoNotify: ENTRY PROC [file: FileHandle] = INLINE {
ioSynch.done ← TRUE; ioSynch.outcome ← outcome;
NOTIFY ioSynch.finished;
};
DoNotify[ioSynch.file];
};
IFSToMesaTime: PROC [IFSTIME] RETURNS [IFSFile.FileTime] = MACHINE CODE
{ PrincOps.zEXCH; };
MesaToIFSTime: PROC [IFSFile.FileTime] RETURNS [IFSTIME] = MACHINE CODE
{ PrincOps.zEXCH; };
}.