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 USING [Flatten, 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;
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 STRING ← LOOPHOLE[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: BOOL ← FALSE;
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;
};
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; };
}.