-- File: IFSFileOpsB.mesa
-- Last edited by Levin: 4-Feb-82 14:52:09
-- edit by Andrew: 11-Feb-81 14:51:07
DIRECTORY
FileDefs USING [
Buffer, bytesPerPage, ComparePositions, Completer, CompleterArg, IncrementPosition,
PageNumber, Position, Problem],
IFSFilePrivate USING [
FileHandle, FileState, FreeIORequestBlock, GetIORequestBlock, IORequest, openSeal],
Leaf USING [
Answer, ByteCount, FileAddress, LeafType, OpSpecificFileAddress, readOp, readAns,
Request, writeOp, WriteRequest],
MiscDefs USING [ByteBlt],
Sequin USING [Broken, Buffer, Get, GetEmptyBuffer, Put, ReleaseBuffer];
IFSFileOpsB: MONITOR LOCKS file.LOCK USING file: IFSFilePrivate.FileHandle
IMPORTS FileDefs, IFSFilePrivate, MiscDefs, Sequin
EXPORTS IFSFilePrivate =
BEGIN OPEN IFSFilePrivate;
-- Types --
FAPieces: TYPE = MACHINE DEPENDENT RECORD [
ugh(0):
SELECT OVERLAID * FROM
fa => [fa(0): Leaf.FileAddress],
faPieces =>
[opSpecific(0:0..4): Leaf.OpSpecificFileAddress, faHigh(0:5..15): [0..3777B],
faMiddle(1:0..6): [0..177B], faLow(1:7..15): [0..777B]],
posPieces =>
[posHigh(0:0..8): [0..777B], posMiddle(0:9..15): [0..177B],
posLow(1:0..15): [0..777B]],
position => [pos(0): FileDefs.Position],
ENDCASE];
-- Miscellaneous --
bytesPerPage: CARDINAL = FileDefs.bytesPerPage;
FileTooBig: ERROR = CODE;
InvalidFile: ERROR = CODE;
LeafGibberish: ERROR = CODE;
TheWellIsDry: ERROR = CODE;
-- Operations (exported to IFSFilePrivate on behalf of FileDefs) --
StartRead: PUBLIC PROCEDURE [file: FileHandle, page: FileDefs.PageNumber,
buffer: FileDefs.Buffer, callback: FileDefs.Completer,
arg: FileDefs.CompleterArg] =
BEGIN
request: IORequest;
bytesToRead: CARDINAL;
ValidateFile[file];
bytesToRead ← IF file.length.page = page THEN file.length.byte ELSE bytesPerPage;
request ← GetIORequestBlock[];
request↑ ← [link: , buffer: buffer,
address: PositionToFileAddress[[page: page, byte: 0]], op: read,
bytesToGo: bytesToRead, proc: callback, arg: arg];
DoRead[file, request];
END;
StartWrite: PUBLIC PROCEDURE [file: FileHandle, page: FileDefs.PageNumber,
buffer: FileDefs.Buffer, callback: FileDefs.Completer,
arg: FileDefs.CompleterArg] =
BEGIN
request: IORequest;
bytesToWrite: CARDINAL;
ValidateFile[file];
bytesToWrite ← IF file.length.page = page THEN file.length.byte ELSE bytesPerPage;
request ← GetIORequestBlock[];
request↑ ← [link: , buffer: buffer,
address: PositionToFileAddress[[page: page, byte: 0]], op: write,
bytesToGo: bytesToWrite, proc: callback, arg: arg];
DoWrite[file, request];
END;
-- Other Procedures exported to IFSFilePrivate --
DoRead: PUBLIC PROCEDURE [file: FileHandle, request: IORequest] =
BEGIN
sequinBuffer: Sequin.Buffer ← Sequin.GetEmptyBuffer[];
sequinRequest: Leaf.Request ← LOOPHOLE[sequinBuffer.data];
AcquireIOLock[file];
EnqueueRequest[file, request];
sequinRequest↑ ←
[op: Leaf.readOp,
opSpecific: read[handle: file.leafHandle, address: request.address,
length: request.bytesToGo]];
sequinBuffer.nBytes ← Leaf.readOp.length;
Sequin.Put[file.sequin, sequinBuffer ! Sequin.Broken => CONTINUE];
ReleaseIOLock[file];
END;
DoWrite: PUBLIC PROCEDURE [file: FileHandle, request: IORequest] =
BEGIN OPEN FileDefs;
bytesSent: Leaf.ByteCount ← 0;
bytesToGo: Leaf.ByteCount = request.bytesToGo;
initialPosition: Position = FileAddressToPosition[request.address];
positionAfterWrite: Position =
IncrementPosition[initialPosition, request.bytesToGo];
AcquireIOLock[file];
IF ComparePositions[file.length, positionAfterWrite] = less THEN
file.length ← positionAfterWrite;
EnqueueRequest[file, request];
DO
positionThisTime: Position = IncrementPosition[initialPosition, bytesSent];
buffer: Sequin.Buffer ← Sequin.GetEmptyBuffer[];
sequinRequest: Leaf.WriteRequest ← LOOPHOLE[buffer.data];
bytesThisTime: Leaf.ByteCount;
buffer.nBytes ← Leaf.writeOp.length;
bytesThisTime ← MIN[buffer.maxBytes - buffer.nBytes, bytesToGo - bytesSent];
sequinRequest↑ ←
[op: Leaf.writeOp, opSpecific: write[handle: file.leafHandle,
address: PositionToFileAddress[positionThisTime, request.address.opSpecific],
length: bytesThisTime, writeBody: ]];
MiscDefs.ByteBlt[to: @sequinRequest.writeBytes, from: request.buffer, toByte: 0,
fromByte: bytesSent, nBytes: bytesThisTime];
sequinRequest.op.length ← buffer.nBytes ← buffer.nBytes + bytesThisTime;
Sequin.Put[file.sequin, buffer ! Sequin.Broken => EXIT];
bytesSent ← bytesSent + bytesThisTime;
IF bytesSent = bytesToGo THEN EXIT;
ENDLOOP;
ReleaseIOLock[file];
END;
FileWatcher: PUBLIC PROCEDURE [file: FileHandle] RETURNS [BOOLEAN] =
BEGIN
current: IORequest ← NIL;
fatalError: BOOLEAN ← FALSE;
offset: CARDINAL;
DO
buffer: Sequin.Buffer;
outcome: FileDefs.Problem;
CheckState: ENTRY PROCEDURE [file: FileHandle] RETURNS [FileState] = -- INLINE --
BEGIN
DO
SELECT file.state FROM
alive => IF fatalError THEN file.state ← flush;
flush => NULL;
closing =>
BEGIN
IF file.ioPending = NIL THEN {file.state ← closed; BROADCAST file.ioSynch};
EXIT
END;
ENDCASE;
IF file.ioPending ~= NIL THEN EXIT;
WAIT file.ioSynch;
ENDLOOP;
RETURN[file.state]
END;
CompleteRequest: PROCEDURE =
BEGIN
request: IORequest = DequeueRequest[file];
request.proc[request.arg, outcome];
FreeIORequestBlock[request];
current ← NIL;
END;
SELECT CheckState[file] FROM
alive =>
BEGIN
answer: Leaf.Answer;
position: FileDefs.Position;
EnsureCurrent: ENTRY PROCEDURE [file: FileHandle] = -- INLINE --
BEGIN
IF current ~= NIL THEN RETURN;
IF file.ioPending = NIL THEN ERROR TheWellIsDry;
current ← file.ioPending.link;
position ← FileAddressToPosition[current.address];
offset ← 0;
END;
ValidateArrival: PROCEDURE [address: Leaf.FileAddress, bytes: Leaf.ByteCount] =
BEGIN
pos: FileDefs.Position = FileAddressToPosition[address];
IF current.op = answer.op.type AND pos = position THEN
BEGIN
outcome ← ok;
current.bytesToGo ← current.bytesToGo - bytes;
position ← FileDefs.IncrementPosition[position, bytes];
END
ELSE fatalError ← TRUE;
END;
EnsureCurrent[file];
outcome ← other;
buffer ← Sequin.Get[file.sequin ! Sequin.Broken =>
{outcome ← io; fatalError ← TRUE; LOOP}];
answer ← LOOPHOLE[buffer.data];
IF answer.op.sense ~= reply THEN GO TO protocolViolation
ELSE
WITH ans: answer SELECT answer.op.type FROM
error =>
BEGIN
IF current.op ~= ans.errorOp.type THEN GO TO protocolViolation;
SELECT ans.error FROM
accessDenied, fileBusy,
IN [userName .. connectPassword] => outcome ← credentials;
allocExceeded, fileSystemFull => outcome ← resources;
brokenLeaf => {outcome ← io; GO TO broken};
IN [unimplementedOp..illegalWrite] => ERROR LeafGibberish;
ENDCASE;
current.bytesToGo ← 0;
END;
read =>
BEGIN
dataBytes: Leaf.ByteCount = ans.op.length - Leaf.readAns.length;
ValidateArrival[ans.address, dataBytes];
MiscDefs.ByteBlt[to: current.buffer, from: @ans.readBytes,
toByte: offset, fromByte: 0, nBytes: dataBytes];
offset ← offset + dataBytes;
END;
write => ValidateArrival[ans.address, ans.length];
ENDCASE => GO TO protocolViolation;
IF current.bytesToGo = 0 THEN CompleteRequest[];
Sequin.ReleaseBuffer[buffer];
EXITS
protocolViolation, broken =>
{fatalError ← TRUE; Sequin.ReleaseBuffer[buffer]};
END;
flush => CompleteRequest[];
closing => {outcome ← other; CompleteRequest[]};
closed => EXIT;
ENDCASE;
ENDLOOP;
RETURN[~fatalError]
END;
ValidateFile: PUBLIC PROCEDURE [file: FileHandle] =
{IF file.seal ~= openSeal THEN ERROR InvalidFile};
FileAddressToPosition: PUBLIC PROCEDURE [fa: Leaf.FileAddress]
RETURNS [FileDefs.Position] =
-- Note: regards 'fa' as a signed quantity (to allow leader page access).
BEGIN
p1, p2: FAPieces;
p1.fa ← fa;
IF ~(p1.faHigh <= 777B OR p1.faHigh IN [3400B..3777B]) THEN ERROR FileTooBig;
p2 ← [posPieces[posHigh: p1.faHigh, posMiddle: p1.faMiddle, posLow: p1.faLow]];
RETURN[p2.pos]
END;
PositionToFileAddress: PUBLIC PROCEDURE [
pos: FileDefs.Position, opSpecific: Leaf.OpSpecificFileAddress ← [read[]]]
RETURNS [Leaf.FileAddress] =
-- Note: regards 'pos' as a signed quantity (to allow leader page access).
BEGIN
p1, p2: FAPieces;
p1.pos ← pos;
p2 ← [faPieces[opSpecific: opSpecific,
faHigh: p1.posHigh, faMiddle: p1.posMiddle, faLow: p1.posLow]];
IF p1.posHigh >= 400B THEN p2.faHigh ← p1.posHigh + 3000B;
RETURN[p2.fa]
END;
-- Internal Procedures --
AcquireIOLock: ENTRY PROCEDURE [file: FileHandle] =
BEGIN
WHILE file.locked DO WAIT file.ioSynch ENDLOOP;
file.locked ← TRUE;
END;
ReleaseIOLock: ENTRY PROCEDURE [file: FileHandle] =
BEGIN
file.locked ← FALSE;
BROADCAST file.ioSynch;
END;
EnqueueRequest: ENTRY PROCEDURE [file: FileHandle, request: IORequest] =
BEGIN
IF file.ioPending = NIL THEN request.link ← request
ELSE {request.link ← file.ioPending.link; file.ioPending.link ← request};
file.ioPending ← request;
BROADCAST file.ioSynch;
END;
DequeueRequest: ENTRY PROCEDURE [file: FileHandle] RETURNS [request: IORequest] =
BEGIN
IF file.ioPending = NIL THEN ERROR TheWellIsDry;
request ← file.ioPending.link;
file.ioPending.link ← request.link;
IF request = file.ioPending THEN file.ioPending ← NIL;
END;
END.