-- Copyright (C) 1981, 1984, 1985 by Xerox Corporation. All rights reserved.
-- File: IFSFileOpsA.mesa
-- HGM, 15-Sep-85 11:49:39
-- Last edited by Gobbel: 27-Jul-81 19:57:07
-- Last edited by Levin: 6-Mar-81 16:39:17
DIRECTORY
FileDefs USING [
bytesPerPage, ComparePositions, Completer, defaultTime, FileTime, Position],
Heap USING [systemZone],
IFSFilePrivate USING [
DoRead, DoWrite, FileAddressToPosition, FileHandle, fileTimeAddress,
FileWatcher, FSInstance, GetIORequestBlock, ifsFiles, IFSTIME, IFSTimes,
InsertFile, IORequest, PositionToFileAddress, PurgeFile, ReleaseFile,
ValidateFile],
Inline USING [LongCOPY],
Leaf USING [
Answer, closeOp, deleteOp, FileAddress, IfsError, openOp, paramsOp, ptLeaf,
Request, RequestObject],
Mopcodes USING [zEXCH],
Sequin USING [
Broken, Buffer, Create, Destroy, Get, GetEmptyBuffer, Put, ReleaseBuffer],
String USING [AppendString],
Time USING [Current],
VMDefs USING [AccessFailure, Error, OpenOptions, Problem];
IFSFileOpsA: MONITOR LOCKS file.LOCK USING file: IFSFilePrivate.FileHandle
IMPORTS
FileDefs, Heap, IFSFilePrivate, Inline, Sequin, String, Time, VMDefs
EXPORTS IFSFilePrivate =
BEGIN OPEN IFSFilePrivate;
-- Types --
IOSynchRecord: TYPE = RECORD [
done: BOOLEAN,
outcome: VMDefs.Problem,
file: FileHandle,
finished: CONDITION ← [timeout: 0]];
IOSynch: TYPE = LONG POINTER TO IOSynchRecord;
-- Miscellaneous --
IllegalExtend: ERROR = CODE;
IllegalTruncate: ERROR = CODE;
PacketTooSmall: ERROR = CODE;
-- Operations (exported to IFSFilePrivate on behalf of FileDefs) --
Open: PUBLIC PROCEDURE [
instance: FSInstance, name: LONG STRING, options: VMDefs.OpenOptions ← oldReadOnly]
RETURNS [file: FileHandle] =
BEGIN
DoOpen: PROCEDURE [file: FileHandle] RETURNS [problem: VMDefs.AccessFailure] =
BEGIN
buffer: Sequin.Buffer ← Sequin.GetEmptyBuffer[];
request: Leaf.Request ← LOOPHOLE[buffer.data];
openOpOffset: CARDINAL;
Flush: PROCEDURE =
BEGIN
Sequin.Put[file.sequin, buffer];
buffer ← Sequin.GetEmptyBuffer[];
request ← LOOPHOLE[buffer.data];
openOpOffset ← 0;
END;
MapError: PROCEDURE [leafError: Leaf.IfsError]
RETURNS [VMDefs.AccessFailure] = {
RETURN[
SELECT leafError FROM
accessDenied, fileBusy, illegalDIFAccess, IN
[userName..connectPassword] => accessDenied,
fileNotFound, dirNotFound => notFound,
fileAlreadyExists => alreadyExists,
IN [nameMalformed..nameTooLong] => illegalFileName,
ENDCASE => other]};
file.sequin ← Sequin.Create[
dest: instance.serverAddr, pupType: Leaf.ptLeaf];
request↑ ← [
Leaf.paramsOp, params[
packetDataBytes: buffer.maxBytes, fileLockTimeout: 2 * 60 / 5,
connectionTimeout: 10 * 60 / 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[]];
BEGIN
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;
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
answer: Leaf.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};
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;
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];
ifsFiles ← ifsFiles + 1;
END;
ENDCASE => {problem ← other; GO TO cantOpen};
EXITS cantOpen => Sequin.Destroy[file.sequin];
END;
Sequin.ReleaseBuffer[buffer];
EXITS serverDead => {
problem ← io;
Sequin.Destroy[file.sequin]; };
END;
file ← InsertFile[instance, name, DoOpen];
file.watcher ← FORK FileWatcher[file];
END;
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 [FileDefs.Position] = BEGIN RETURN[file.length] END;
SetLength: PUBLIC PROCEDURE [file: FileHandle, length: FileDefs.Position] =
BEGIN
oldLength: FileDefs.Position;
ValidateFile[file];
oldLength ← GetLength[file];
SELECT FileDefs.ComparePositions[oldLength, length] FROM
less => DoIO[file, PrepareSetLength[file, length]];
equal => NULL;
greater => Truncate[file, length];
ENDCASE;
END;
Extend: PUBLIC PROCEDURE [
file: FileHandle, length: FileDefs.Position, buffer: LONG POINTER] =
BEGIN
PrepareExtend: ENTRY PROCEDURE [file: FileHandle]
RETURNS [request: IORequest] = INLINE
BEGIN
request ← GetIORequestBlock[];
ValidateFile[file];
request↑ ← [
buffer: buffer,
address: PositionToFileAddress[
[file.length.page, 0], [write[newEOF: TRUE]]], op: write, bytesToGo:];
IF FileDefs.ComparePositions[length, file.length] # greater THEN
GO TO bogus;
IF length.page = file.length.page THEN {request.bytesToGo ← length.byte}
ELSE
BEGIN
IF ~(length.page = file.length.page + 1 AND length.byte = 0) THEN
GO TO bogus;
request.bytesToGo ← FileDefs.bytesPerPage;
END;
file.length ← length;
EXITS bogus => ERROR IllegalExtend;
END;
DoIO[file, PrepareExtend[file]];
END;
Truncate: PUBLIC PROCEDURE [file: FileHandle, length: FileDefs.Position] =
BEGIN
ValidateFile[file];
SELECT FileDefs.ComparePositions[file.length, length] FROM
less => ERROR IllegalTruncate;
equal => NULL;
greater => DoIO[file, PrepareSetLength[file, length]];
ENDCASE;
END;
GetTimes: PUBLIC PROCEDURE [file: FileHandle]
RETURNS [read, write, create: FileDefs.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;
SetCreationTime: PUBLIC PROCEDURE [
file: FileHandle, create: FileDefs.FileTime ← FileDefs.defaultTime] =
BEGIN
creation: IFSTIME;
request: IORequest ← GetIORequestBlock[];
IF create = FileDefs.defaultTime THEN create ← Time.Current[];
creation ← MesaToIFSTime[create];
request↑ ← [
buffer: @creation, address: fileTimeAddress, op: write,
bytesToGo: 2 * SIZE[IFSTIME]];
DoIO[file, request];
END;
-- Other Procedures exported to IFSFilePrivate --
CopyString: PUBLIC PROCEDURE [s: LONG STRING] RETURNS [ns: LONG STRING] =
BEGIN
IF s = NIL THEN RETURN[NIL];
ns ← Heap.systemZone.NEW[StringBody [s.length]];
String.AppendString[ns, s];
END;
AddStringToBuffer: PUBLIC PROCEDURE [
buffer: LONG 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: FileDefs.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
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];
EXITS ignore => NULL;
END;
Sequin.Destroy[file.sequin];
ifsFiles ← ifsFiles - 1;
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 ← @ioSynch;
SELECT request.op FROM
read => DoRead[file, request];
write => DoWrite[file, request];
ENDCASE;
WaitForCompletion[file];
IF ioSynch.outcome # ok THEN ERROR VMDefs.Error[ioSynch.outcome];
END;
NoteCompletion: FileDefs.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 [FileDefs.FileTime] = MACHINE CODE
BEGIN Mopcodes.zEXCH; END;
MesaToIFSTime: PROCEDURE [FileDefs.FileTime] RETURNS [IFSTIME] = MACHINE CODE
BEGIN Mopcodes.zEXCH; END;
END.