-- FileIOFSImpl.mesa
-- Please maintain change log at end of file.
-- Last Edited by
-- MBrown on October 21, 1983 1:41 pm
-- Rovner on August 15, 1983 1:02 pm
-- Levin on September 22, 1983 3:22 pm
-- Birrell on August 23, 1983 3:14 pm
-- Schroeder on September 22, 1983 2:37 pm

-- To Do:
-- What to do when byte length > allocated length at Open time?
-- Implement "release extra pages on close" option.
-- Clean up logic for extending file.
-- Implement multiple-page stream buffer.
-- Implement coupled read and write streams on same open file.
-- Fix ERROR IO.Error[$Failure, NIL] (file extension)


DIRECTORY
BasicTime USING [GMT],
Basics USING [bytesPerWord, LongNumber, BITAND, LowHalf],
FileIOPrivate USING[ Data, FSDataHandle ],
FS,
FSBackdoor USING [ProduceError],
PrincOps USING [ByteBltBlock],
PrincOpsUtils USING [ByteBlt],
IO,
IOUtils,
Rope,
RuntimeError USING [BoundsFault],
VM USING [
AddressForPageNumber, Allocate, Free, nullInterval, PageNumber, PagesForWords];

FileIOFSImpl: CEDAR PROGRAM
IMPORTS
Basics,
FS,
FSBackdoor,
I: PrincOpsUtils,
IO,
IOUtils,
RuntimeError,
VM
EXPORTS
FS =
BEGIN OPEN Basics;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
ByteCount: TYPE = INT;
ByteNumber: TYPE = ByteCount; -- index rather than count
PageNumber: TYPE = VM.PageNumber;
PageCount: TYPE = VM.PageNumber;

wordsPerFilePage: CARDINAL = FS.WordsForPages[1];
bytesPerFilePage: CARDINAL = FS.BytesForPages[1];

Data: TYPE = FileIOPrivate.Data;
DataHandle: TYPE = REF Data;
FSDataHandle: TYPE = FileIOPrivate.FSDataHandle;
ProcHandle: TYPE = REF IO.StreamProcs;


-- Stream creation

StreamOpen: PUBLIC PROC [
fileName: ROPE,
accessOptions: FS.AccessOptions,
streamOptions: FS.StreamOptions,
keep: CARDINAL, createByteCount: FS.ByteCount,
streamBufferParms: FS.StreamBufferParms,
extendFileProc: FS.ExtendFileProc ]
RETURNS [STREAM] = {
fileHandle: FS.OpenFile = SELECT accessOptions FROM
$read => FS.Open[name: fileName],
$create => FS.Create[name: fileName, keep: keep,
pages: FS.PagesForBytes[createByteCount]],
$append => FS.OpenOrCreate[name: fileName, keep: keep,
pages: FS.PagesForBytes[createByteCount]],
$write => FS.Open[name: fileName, lock: $write]
ENDCASE => ERROR;
RETURN[StreamFromOpenFile[openFile: fileHandle,
accessRights: IF accessOptions = $read THEN $read ELSE $write,
initialPosition: IF accessOptions = $append THEN $end ELSE $start,
streamOptions: streamOptions,
streamBufferParms: streamBufferParms,
extendFileProc: extendFileProc]];
};

StreamFromOpenFile: PUBLIC PROC [
openFile: FS.OpenFile,
accessRights: FS.Lock,
initialPosition: FS.InitialPosition,
streamOptions: FS.StreamOptions,
streamBufferParms: FS.StreamBufferParms,
extendFileProc: FS.ExtendFileProc]
RETURNS [stream: STREAM] = {
pageAllocation: PageCount;
byteLength: ByteCount;
fileName: ROPE = openFile.GetName[].fullFName;
fsData: FSDataHandle;
IF accessRights = $write AND GetFileLock[openFile] # $write
THEN FSBackdoor.ProduceError[wrongLock, fileName];
[pages: pageAllocation, bytes: byteLength] ← openFile.GetInfo[];
fsData ← NEW[Data.fs ← [
accessRights: accessRights,
streamOptions: streamOptions,
fileName: fileName,
fileLength: byteLength,
body: fs[
fileHandle: openFile,
byteLength: byteLength,
byteSize: pageAllocation*bytesPerFilePage]]];
IF fsData.byteLength > fsData.byteSize THEN ERROR;
CreateBufferSpace[fsData];
stream ← IO.CreateStream[ProcHandleFromAccessRights[accessRights], fsData];
IOUtils.StoreData[self: stream, key: $Name, data: fsData.fileName];
IF accessRights = $write AND fsData.byteSize = 0 THEN {
fsData.byteSize ← NewByteSize[fsData.byteSize];
SetFileSize[fsData.fileHandle, fsData.byteSize];
};
IF initialPosition = start THEN {
SetupBuffer[fsData: fsData, fileByte: 0]
}
ELSE { -- initialPosition = end
SetupBuffer[fsData: fsData, fileByte: PageContainingLastByte[fsData.fileLength]];
fsData.index ← fsData.dataBytesInBuffer; -- assert fsData.eofInBuffer
};
IF streamOptions[$tiogaRead] AND byteLength > 0 THEN {
isTioga: BOOL; len: INT;
[yes: isTioga, len: len] ← IsThisThingATiogaFile[stream];
IF isTioga THEN {
IF accessRights = $read THEN {
-- make length look changed by sneaky call to SetLength (not in stream procs).
-- since stream is opened for read only, this call won't change the length in the file.
setLength[stream, len];
fsData.tiogaReader ← TRUE
}
ELSE {
-- you can't incrementally update a Tioga file with IO!
stream.Close[];
FSBackdoor.ProduceError[cantUpdateTiogaFile, fileName];
}
}
};
RETURN[stream];
};--StreamFromOpenFile

PageContainingLastByte: PROC [byteLen: INT] RETURNS [INT] = INLINE {
IF byteLen = 0 THEN RETURN[0] ELSE {
byteLen ← byteLen - 1;
LOOPHOLE[byteLen, LongNumber[num]].lowbits ←
BITAND[LOOPHOLE[byteLen, LongNumber[num]].lowbits, clearLowBits];
RETURN[byteLen] }};

OpenFileFromStream: PUBLIC PROC [self: STREAM] RETURNS [FS.OpenFile] = {
WITH self.streamData SELECT FROM
fsData: FSDataHandle => RETURN [fsData.fileHandle];
ENDCASE => ERROR IO.Error[NotImplementedForThisStream, self];
};

-- Get and Put

CleanupAfterPut: PROC [selfData: DataHandle] = {
-- Restores dataBytesInBuffer and fileLength if they are messed up by a putChar or
--putBlock past the end of file. Called by most stream operations not on this page.
IF selfData.didPut THEN {
selfData.bufferDirty ← TRUE;
IF selfData.index > selfData.dataBytesInBuffer THEN {
selfData.dataBytesInBuffer ← selfData.index;
selfData.fileLength ← selfData.firstFileByteInBuffer + selfData.index };
selfData.didPut ← FALSE }};

getChar: PROC [self: STREAM] RETURNS [CHAR] = {
selfData: DataHandle = NARROW[self.streamData];
c: CHAR;
IF selfData.index >= selfData.dataBytesInBuffer THEN {
IF selfData.eofInBuffer THEN ERROR IO.EndOfStream[self];
-- assert selfData.index = selfData.dataBytesInBuffer = selfData.bufferBytes
AdvanceBuffer[selfData] };
TRUSTED{c ← selfData.buffer[selfData.index]};
selfData.index ← selfData.index + 1;
RETURN[c] };

putChar: PROC [self: STREAM, char: CHAR] = {
selfData: DataHandle = NARROW[self.streamData];
IF selfData.index = selfData.bufferBytes THEN AdvanceBuffer[selfData];
TRUSTED{selfData.buffer[selfData.index] ← char};
selfData.index ← selfData.index + 1;
selfData.didPut ← TRUE };

getBlock: PROC [self: STREAM, block: REF TEXT, startIndex: NAT,
count: NAT]
RETURNS [nBytesRead: NAT] = TRUSTED {
selfData: DataHandle = NARROW[self.streamData];
textBlock: PrincOps.ByteBltBlock;
countRemaining: NAT;
stopIndexPlusOne: NAT = MIN [block.maxLength, IOUtils.AddNAT[startIndex, count]];
textBlock ← [
blockPointer: LOOPHOLE[block, LONG POINTER] + TEXT[0].SIZE,
startIndex: startIndex,
stopIndexPlusOne: stopIndexPlusOne];
countRemaining ←
IF startIndex > stopIndexPlusOne THEN 0 ELSE stopIndexPlusOne-startIndex;
nBytesRead ← 0;
WHILE countRemaining # 0 DO
bufferBlock: PrincOps.ByteBltBlock ← [
blockPointer: selfData.buffer,
startIndex: selfData.index,
stopIndexPlusOne: selfData.dataBytesInBuffer];
countTransferred: CARDINAL ← I.ByteBlt[from: bufferBlock, to: textBlock];
selfData.index ← selfData.index + countTransferred;
nBytesRead ← nBytesRead + countTransferred;
IF (countRemaining ← countRemaining - countTransferred) = 0 THEN EXIT;
IF selfData.eofInBuffer THEN EXIT;
textBlock.startIndex ← textBlock.startIndex + countTransferred;
AdvanceBuffer[selfData];
ENDLOOP;
IF nBytesRead # 0 THEN block.length ← startIndex + nBytesRead;
RETURN[nBytesRead] };

putBlock: PROC [self: STREAM, block: REF READONLY TEXT, startIndex: NAT,
count: NAT] = TRUSTED {
selfData: DataHandle = NARROW[self.streamData];
-- Fail if startIndex<0 or stopIndexPlusOne<0.
textBlock: PrincOps.ByteBltBlock;
countRemaining: NAT;
stopIndexPlusOne: NAT ← IOUtils.AddNAT[startIndex, count];
IF stopIndexPlusOne > block.maxLength THEN stopIndexPlusOne ← block.length;
textBlock ← [
blockPointer: LOOPHOLE[block, LONG POINTER] + TEXT[0].SIZE,
startIndex: startIndex,
stopIndexPlusOne: stopIndexPlusOne];
countRemaining ←
IF startIndex > stopIndexPlusOne THEN 0 ELSE stopIndexPlusOne-startIndex;
WHILE countRemaining # 0 DO
bufferBlock: PrincOps.ByteBltBlock ← [
blockPointer: selfData.buffer,
startIndex: selfData.index,
stopIndexPlusOne: selfData.bufferBytes]; -- allow put past current eof.
countTransferred: CARDINAL ← I.ByteBlt[from: textBlock, to: bufferBlock];
selfData.index ← selfData.index + countTransferred;
selfData.didPut ← TRUE;
IF (countRemaining ← countRemaining - countTransferred) = 0 THEN EXIT;
textBlock.startIndex ← textBlock.startIndex + countTransferred;
AdvanceBuffer[selfData];
ENDLOOP
};

maxWordsMoved: INT = (LAST[CARDINAL] / bytesPerWord) - 1;
maxBytesMoved: INT = maxWordsMoved * bytesPerWord;
maxStopIndexPlusOne: INT = maxBytesMoved;
-- all designed to make the max number of bytes transferred an integral number of
--words, which is good

unsafeGetBlock: UNSAFE PROC [self: STREAM, block: IO.UnsafeBlock]
RETURNS [nBytesRead: INT] = UNCHECKED {
selfData: DataHandle = NARROW[self.streamData];
textBlock: PrincOps.ByteBltBlock;
stopIndexPlusOne: INT;
IF block.startIndex < 0 OR block.count < 0 THEN ERROR RuntimeError.BoundsFault;
IF block.count = 0 THEN RETURN [0];
IF block.startIndex > maxBytesMoved THEN {
-- scale block.startIndex into [0 .. bytesPerWord)
wordOffset: INT = block.startIndex / bytesPerWord;
block.base ← block.base + wordOffset;
block.startIndex ← block.startIndex - wordOffset*bytesPerWord;
};
stopIndexPlusOne ← block.startIndex + block.count;
nBytesRead ← 0;
DO
-- Transfer at most maxBytesMoved bytes from the stream to block^.
-- Assert block.startIndex IN [0 .. maxStopIndexPlusOne), < stopIndexPlusOne
countRemaining: CARDINAL;
textBlock ← [
blockPointer: block.base,
startIndex: block.startIndex,
stopIndexPlusOne: MIN[maxStopIndexPlusOne, stopIndexPlusOne]];
countRemaining ← textBlock.stopIndexPlusOne - textBlock.startIndex;
-- Assert countRemaining > 0
-- The following loop transfers from the stream to textBlock^ until textBlock^ is full
--or end of file is reached.
DO
bufferBlock: PrincOps.ByteBltBlock ← [
blockPointer: selfData.buffer,
startIndex: selfData.index,
stopIndexPlusOne: selfData.dataBytesInBuffer];
countTransferred: CARDINAL ← I.ByteBlt[from: bufferBlock, to: textBlock];
selfData.index ← selfData.index + countTransferred;
nBytesRead ← nBytesRead + countTransferred;
IF (countRemaining ← countRemaining - countTransferred) = 0 THEN EXIT;
IF selfData.eofInBuffer THEN GOTO return;
textBlock.startIndex ← textBlock.startIndex + countTransferred;
AdvanceBuffer[selfData];
ENDLOOP;
IF textBlock.stopIndexPlusOne = stopIndexPlusOne THEN GOTO return;
-- Assert textBlock.stopIndexPlusOne = maxStopIndexPlusOne
block.base ← block.base + maxWordsMoved;
block.startIndex ← 0;
stopIndexPlusOne ← stopIndexPlusOne - maxBytesMoved;
ENDLOOP;
EXITS
return => RETURN [nBytesRead]
};

unsafePutBlock: PROC [self: STREAM, block: IO.UnsafeBlock] = TRUSTED {
selfData: DataHandle = NARROW[self.streamData];
textBlock: PrincOps.ByteBltBlock;
stopIndexPlusOne: INT;
IF block.startIndex < 0 OR block.count < 0 THEN ERROR RuntimeError.BoundsFault;
IF block.startIndex > maxBytesMoved THEN {
-- scale block.startIndex into [0 .. bytesPerWord)
wordOffset: INT = block.startIndex / bytesPerWord;
block.base ← block.base + wordOffset;
block.startIndex ← block.startIndex - wordOffset*bytesPerWord;
};
stopIndexPlusOne ← block.startIndex + block.count;
DO
-- Transfer at most maxBytesMoved bytes from block^ to the stream.
-- Assert block.startIndex IN [0 .. maxStopIndexPlusOne), < stopIndexPlusOne
countRemaining: CARDINAL;
textBlock ← [
blockPointer: block.base,
startIndex: block.startIndex,
stopIndexPlusOne: MIN[maxStopIndexPlusOne, stopIndexPlusOne]];
countRemaining ← textBlock.stopIndexPlusOne - textBlock.startIndex;
-- Assert countRemaining > 0
-- The following loop transfers textBlock^ to the stream.
DO
bufferBlock: PrincOps.ByteBltBlock ← [
blockPointer: selfData.buffer,
startIndex: selfData.index,
stopIndexPlusOne: selfData.bufferBytes]; -- allow put past current eof.
countTransferred: CARDINAL ← I.ByteBlt[from: textBlock, to: bufferBlock];
selfData.index ← selfData.index + countTransferred;
selfData.didPut ← TRUE;
IF (countRemaining ← countRemaining - countTransferred) = 0 THEN EXIT;
textBlock.startIndex ← textBlock.startIndex + countTransferred;
AdvanceBuffer[selfData];
ENDLOOP;
IF textBlock.stopIndexPlusOne = stopIndexPlusOne THEN EXIT;
-- Assert textBlock.stopIndexPlusOne = maxStopIndexPlusOne
block.base ← block.base + maxWordsMoved;
block.startIndex ← 0;
stopIndexPlusOne ← stopIndexPlusOne - maxBytesMoved;
ENDLOOP
};

AdvanceBuffer: PROC [selfData: DataHandle] = {
-- On entry, index = dataBytesInBuffer = bufferBytes. Exit with same position in
--file, but index < dataBytesInBuffer or EOF.
-- Handles implicit file extension.
-- Called from getChar, putChar, getBlock, putBlock.
fsData: FSDataHandle = NARROW[selfData];
firstByteOfNextPage: INT = fsData.firstFileByteInBuffer + fsData.bufferBytes;
changeSize: BOOL ← FALSE;
IF firstByteOfNextPage = maxLength THEN ERROR IO.Error[$Failure, NIL];
CleanupAfterPut[fsData];
IF firstByteOfNextPage >= fsData.byteSize THEN {
fsData.byteSize ← NewByteSize[fsData.byteSize];
SetFileSize[fsData.fileHandle, fsData.byteSize] };
SetupBuffer[fsData: fsData, fileByte: firstByteOfNextPage];
fsData.index ← LowHalf[firstByteOfNextPage-fsData.firstFileByteInBuffer];
};

NewByteSize: PROC [byteCount: ByteCount] RETURNS [ByteCount] = {
RETURN [byteCount+5120];
};

endOf: PROC [self: STREAM] RETURNS[BOOL] = {
-- Requires no CleanupAfterPut.
selfData: DataHandle = NARROW[self.streamData];
RETURN[selfData.eofInBuffer AND selfData.index >= selfData.dataBytesInBuffer] };

charsAvail: PROC [self: STREAM, wait: BOOL] RETURNS [INT] = {
RETURN[INT.LAST] };

getIndex: PROC [self: STREAM] RETURNS [INT] = {
-- Requires no CleanupAfterPut.
selfData: DataHandle = NARROW[self.streamData];
RETURN[selfData.firstFileByteInBuffer + selfData.index] };

setIndex: PROC [self: STREAM, index: INT] = {
firstByte: INT ← index;
byte: CARDINAL; --will contain index - firstByte
fsData: FSDataHandle = NARROW[self.streamData];
firstBufferByte: INT = fsData.firstFileByteInBuffer;
newIndex: CARDINAL;
IF index < 0 THEN ERROR IO.Error[BadIndex, self];
LOOPHOLE[firstByte, LongNumber[num]].lowbits ←
BITAND[LOOPHOLE[firstByte, LongNumber[num]].lowbits, clearLowBits];
-- firstByte contains index of first byte of file page containing byte "index"
byte ← BITAND[LOOPHOLE[index, LongNumber[num]].lowbits, clearHighBits];
-- byte contains index - firstByte
IF fsData.didPut THEN { -- CleanupAfterPut[fsData]
fsData.bufferDirty ← TRUE;
IF fsData.index > fsData.dataBytesInBuffer THEN {
fsData.dataBytesInBuffer ← fsData.index;
fsData.fileLength ← fsData.firstFileByteInBuffer + fsData.index };
fsData.didPut ← FALSE };
-- ensure that page containing byte "index" is in the buffer
IF firstByte NOT IN [firstBufferByte .. firstBufferByte+fsData.bufferBytes) THEN {
IF index > fsData.fileLength THEN ERROR IO.EndOfStream[self];
IF fsData.byteSize <= firstBufferByte+fsData.dataBytesInBuffer AND
fsData.accessRights # $read THEN {
-- Analogous to AdvanceBuffer (bytes in buffer are not covered by file, but this
--time we may be jumping away from the EOF.)
fsData.byteSize ← NewByteSize[fsData.byteSize];
SetFileSize[fsData.fileHandle, fsData.byteSize] };
SetupBuffer[fsData: fsData, fileByte: firstByte];
};
newIndex ← LowHalf[firstByte-fsData.firstFileByteInBuffer] + byte;
IF newIndex > fsData.dataBytesInBuffer THEN ERROR IO.EndOfStream[self];
fsData.index ← newIndex };

reset: PROC [self: STREAM] = {
setIndex[self, getLength[self]] };

flush: PROC [self: STREAM] = {
fsData: FSDataHandle = NARROW[self.streamData];
ForceOut[fsData, IF fsData.streamOptions[$commitAndReopenTransOnFlush]
THEN $continue ELSE $none, FALSE];
};

close: PROC [self: STREAM, abort: BOOL] = {
fsData: FSDataHandle = NARROW[self.streamData];
ForceOut[fsData, IF fsData.streamOptions[$finishTransOnClose]
THEN $complete ELSE $none, abort];
IF fsData.streamOptions[$closeFSOpenFileOnClose] THEN fsData.fileHandle.Close[];
fsData.fileHandle ← [NIL];
DeleteBufferSpace[fsData];
fsData.streamIsClosed ← TRUE;
self.streamProcs ← IOUtils.closedStreamProcs };

-- Procs that are called via the property list mechanism.

getLength: PROC [self: STREAM] RETURNS [length: INT] = {
selfData: DataHandle = NARROW[self.streamData];
IF selfData.streamIsClosed THEN ERROR IO.Error[StreamClosed, self];
-- special CleanupAfterPut[selfData]; just clean up file length,
--let some later call do the rest
IF selfData.didPut AND selfData.index > selfData.dataBytesInBuffer THEN {
selfData.fileLength ← selfData.firstFileByteInBuffer + selfData.index };
RETURN[selfData.fileLength] };

clearLowBits: CARDINAL = CARDINAL.LAST-(bytesPerFilePage-1);
clearHighBits: CARDINAL = (bytesPerFilePage-1);
maxLength: INT = INT.LAST - bytesPerFilePage;

setLength: PROC [self: STREAM, length: INT] = {
-- Note: do not reduce the size of a shortened file until stream closed.
RoundUpToPages: PROC [bytes: INT] RETURNS [INT] = INLINE {
bytes ← bytes + (bytesPerFilePage-1);
LOOPHOLE[bytes, LongNumber[num]].lowbits ←
BITAND[LOOPHOLE[bytes, LongNumber[num]].lowbits, clearLowBits];
RETURN[bytes] };
fsData: FSDataHandle = NARROW[self.streamData];
newFileBytes, firstBufferByte: INT;
IF fsData.streamIsClosed THEN ERROR IO.Error[StreamClosed, self];
IF length NOT IN [0 .. maxLength] THEN ERROR IO.Error[BadIndex, self];
newFileBytes ← RoundUpToPages[length];
IF fsData.didPut THEN { -- CleanupAfterPut[fsData]
fsData.bufferDirty ← TRUE;
fsData.didPut ← FALSE };
firstBufferByte ← fsData.firstFileByteInBuffer;
fsData.fileLength ← length;
SELECT TRUE FROM
length > firstBufferByte + fsData.bufferBytes => {
-- new last byte of file is past end of current buffer.
IF length > fsData.byteSize THEN { --extend file to newFileBytes now
fsData.byteSize ← newFileBytes;
SetFileSize[fsData.fileHandle, fsData.byteSize] };
fsData.dataBytesInBuffer ← fsData.bufferBytes;
fsData.eofInBuffer ← FALSE };
length > firstBufferByte OR (length = 0 AND firstBufferByte = 0) => {
-- new last byte of file is in current buffer, or new file is empty and
--first data page is first page of buffer. Defer actually changing file length;
--this is analogous to extending file with puts.
fsData.eofInBuffer ← TRUE;
fsData.dataBytesInBuffer ← length - firstBufferByte;
fsData.index ← MIN[fsData.index, fsData.dataBytesInBuffer] };
ENDCASE => {
-- last byte of file precedes current buffer. (a special case of this, length
--= 0 and firstBufferByte = 0, was already handled above without a
--SetupBuffer call).
fsData.bufferDirty ← FALSE; --avoid redundant write if buffer dirty.
SetupBuffer[fsData: fsData, fileByte: newFileBytes-bytesPerFilePage];
fsData.index ← fsData.dataBytesInBuffer };
};

eraseChar: PROC [self: STREAM, char: CHAR] = {
index: INT = getIndex[self];
IF index = 0 THEN ERROR IO.Error[IllegalBackup, self];
setIndex[self, index-1];
IF getChar[self] # char THEN {putChar[self, '\\]; putChar[self, char]}
ELSE setIndex[self, index-1] };

backup: PROC [self: STREAM, char: CHAR] = {
selfData: DataHandle = NARROW[self.streamData];
index: INT;
IF selfData.streamIsClosed THEN ERROR IO.Error[StreamClosed, self];
index ← getIndex[self];
IF index = 0 THEN ERROR IO.Error[IllegalBackup, self];
setIndex[self, index-1];
IF getChar[self] # char THEN ERROR IO.Error[IllegalBackup, self];
setIndex[self, index-1] };

-- Buffer management

growIncrement: CARDINAL = 4; --if we ever need to grow file, we grow it by this many pages.
growIncrementBytes: CARDINAL = growIncrement*bytesPerFilePage;

CreateBufferSpace: PROC [fsData: FSDataHandle] = {
fsData.bufferInterval ← VM.Allocate[count: VM.PagesForWords[wordsPerFilePage]];
TRUSTED{fsData.buffer ← VM.AddressForPageNumber[fsData.bufferInterval.page]};
fsData.bufferBytes ← bytesPerFilePage };

DeleteBufferSpace: PROC [fsData: FSDataHandle] = {
TRUSTED{VM.Free[fsData.bufferInterval]};
fsData.bufferInterval ← VM.nullInterval };

SetupBuffer: PROC [fsData: FSDataHandle, fileByte: INT] = {
-- didPut = FALSE on entry (someone else called CleanupAfterPut).
-- Arranges buffer so that fileByte (must be page-aligned) is the first byte in it.
-- If buffer is dirty, writes it to file (file page has already been allocated).
-- Maintains invariants of eofInBuffer, dataBytesInBuffer, bufferBytes, and
--firstFileByteInBuffer in the face of all this. DOES NOT update index.
-- Called from StreamFromAlpineOpenFile, AdvanceBuffer, setLength, setIndex.
bytesToRead: CARDINAL;
IF fsData.bufferDirty THEN {
TRUSTED{WriteFilePage[f: fsData.fileHandle,
to: fsData.firstFileByteInBuffer, from: fsData.buffer]};
fsData.bufferDirty ← FALSE };
IF (bytesToRead ← LowHalf[MIN[fsData.fileLength - fileByte, bytesPerFilePage]]) > 0 THEN
TRUSTED{ReadFilePage[f: fsData.fileHandle, from: fileByte, to: fsData.buffer]};
fsData.firstFileByteInBuffer ← fileByte;
fsData.eofInBuffer ← fsData.fileLength <= (fileByte + bytesPerFilePage);
fsData.dataBytesInBuffer ← bytesToRead };

ForceOut: PROC [fsData: FSDataHandle,
commitAction: {none, complete, continue}, abort: BOOL] = {
-- Called from Flush, Close.
-- This is the only proc that sets byte length, and only proc that finishes trans.
CleanupAfterPut[fsData];
IF fsData.bufferDirty THEN {
TRUSTED{WriteFilePage[f: fsData.fileHandle,
to: fsData.firstFileByteInBuffer, from: fsData.buffer]};
fsData.bufferDirty ← FALSE };
IF fsData.fileLength # fsData.byteLength AND NOT fsData.tiogaReader THEN {
fsData.byteLength ← fsData.fileLength;
fsData.fileHandle.SetByteCountAndCreatedTime[fsData.byteLength] };
IF commitAction # $none THEN Finish[fsData.fileHandle];
};

-- Talking to FS

ReadFilePage: PROC [f: FS.OpenFile, from: ByteNumber, to: LONG POINTER] = {
p: PageNumber = from/bytesPerFilePage;
TRUSTED{f.Read[from: p, nPages: 1, to: to]};
};

WriteFilePage: PROC [f: FS.OpenFile, to: ByteNumber, from: LONG POINTER] = {
p: PageNumber = to/bytesPerFilePage;
f.Write[from: from, nPages: 1, to: p];
};

SetFileSize: PROC [f: FS.OpenFile, byteSize: ByteCount] = {
f.SetPageCount[pages: (byteSize+bytesPerFilePage-1)/bytesPerFilePage];
};

GetFileSize: PROC [f: FS.OpenFile] RETURNS [ByteCount] = {
RETURN [f.GetInfo[].pages*bytesPerFilePage];
};

GetFileByteLength: PROC [f: FS.OpenFile] RETURNS [ByteCount] = {
RETURN [f.GetInfo[].bytes];
};

GetFileLock: PROC [f: FS.OpenFile] RETURNS [FS.Lock] = {
RETURN [f.GetInfo[].lock]
};

Finish: PROC [f: FS.OpenFile] = {
};

-- Tioga

IsThisThingATiogaFile: PROC [h: STREAM] RETURNS [yes: BOOL, len: INT] = {
pos, length: INT;
{ -- block so EXITS code can use pos, len, and length.
controlHeaderId: ARRAY [0..fileIdSize) OF CHAR = [235C,312C];
controlTrailerId: ARRAY [0..fileIdSize) OF CHAR = [205C,227C];
commentHeaderId: ARRAY [0..fileIdSize) OF CHAR = [0C,0C];
fileIdSize: NAT = 2;
numTrailerLengths: NAT = 3; -- <file-props-length> <data-length> <file-length>
endSize: NAT = fileIdSize+numTrailerLengths*4; -- trailer plus three lengths
ReadLen: PROC [h: STREAM] RETURNS [INT] = {
  start: PACKED ARRAY [0..3] OF CHARACTER;
  start[0] ← h.GetChar[]; start[1] ← h.GetChar[];
  start[2] ← h.GetChar[]; start[3] ← h.GetChar[];
  RETURN [LOOPHOLE[start]] };
 commentStart, commentLen, propsLen, controlLen, controlEnd: INT;
 pos ← h.GetIndex[]; -- save position to restore later
 length ← h.GetLength[]; -- length including any trailer stuff
 controlEnd ← length-endSize; -- where the trailer info starts
 IF controlEnd <= 0 THEN GOTO fail; -- too small
 h.SetIndex[controlEnd]; -- set up to read the trailer
 FOR i:NAT IN [0..fileIdSize) DO -- read the controlTrailerId
  IF h.GetChar[] # controlTrailerId[i] THEN GOTO fail;
  ENDLOOP;
 IF (propsLen ← ReadLen[h]) NOT IN [0..controlEnd) THEN GOTO fail;
 IF (commentStart ← ReadLen[h]) NOT IN [0..controlEnd) THEN GOTO fail;
 IF ReadLen[h] # length THEN GOTO fail;
 IF commentStart > 0 THEN { -- may have padded text with a null
  h.SetIndex[commentStart-1];
  len ← IF h.GetChar[]=0C THEN commentStart-1 ELSE commentStart }
 ELSE h.SetIndex[len ← commentStart];
 FOR i:NAT IN [0..fileIdSize) DO -- read the commentHeaderId
  IF h.GetChar[] # commentHeaderId[i] THEN GOTO fail;
  ENDLOOP;
 commentLen ← ReadLen[h]; -- the length of the comment section
 IF commentStart+commentLen NOT IN [0..controlEnd) THEN GOTO fail;
 h.SetIndex[commentStart+commentLen]; -- go to start of control info
 FOR i:NAT IN [0..fileIdSize) DO -- check the controlHeaderId
  IF h.GetChar[] # controlHeaderId[i] THEN GOTO fail;
  ENDLOOP;
 controlLen ← ReadLen[h]; -- the length of the control section
 IF commentStart+commentLen+controlLen # length THEN GOTO fail;
 GOTO succeed;
 EXITS
  fail => { h.SetIndex[pos]; RETURN [FALSE, length] };
  succeed => { h.SetIndex[pos]; RETURN [TRUE, len] };
 }};

-- Procedure records (never modified)

nucleusFileIOReadProcs: ProcHandle = IO.CreateStreamProcs[
variety: $input, class: $File,

getChar: getChar,
endOf: endOf,
charsAvail: charsAvail,
getBlock: getBlock,
unsafeGetBlock: unsafeGetBlock,

putChar: NIL, -- not implemented
putBlock: NIL, -- call putChar
unsafePutBlock: NIL, -- call putChar
flush: flush,

reset: reset,
close: close,
getIndex: getIndex,
setIndex: setIndex,

backup: backup,
getLength: getLength
];

nucleusFileIOAllProcs: ProcHandle = IO.CreateStreamProcs[
variety: $inputOutput, class: $File,
getChar: getChar,
endOf: endOf,
charsAvail: charsAvail,
getBlock: getBlock,
unsafeGetBlock: unsafeGetBlock,

putChar: putChar,
putBlock: putBlock,
unsafePutBlock: unsafePutBlock,
flush: flush,

reset: reset,
close: close,
getIndex: getIndex,
setIndex: setIndex,

backup: backup,
getLength: getLength,
setLength: setLength
];

ProcHandleFromAccessRights: ARRAY FS.Lock OF ProcHandle = [
read: nucleusFileIOReadProcs,
write: nucleusFileIOAllProcs];

END.


CHANGE LOG

Created by MBrown on June 22, 1983 10:08 am
-- By editing FileIOAlpineImpl.

Changed by MBrown on August 19, 1983 2:44 pm
-- Close FS file when closing stream (this should really be an option).

Changed by Birrell on August 23, 1983 3:14 pm
-- In SetFileSize: byteSize/bytesPerFilePage -> (byteSize+bytesPerFilePage-1)/bytesPerFilePage.

Changed by MBrown on August 25, 1983 1:18 pm
-- In SetIndex: fsData.byteSize < firstBufferByte+fsData.dataBytesInBuffer -> fsData.byteSize <= firstBufferByte+fsData.dataBytesInBuffer. Implemented GetFileLock (was stubbed waiting for FS). In StreamFromOpenFile, if stream open for write and file has no pages, extend it.

Changed by MBrown on September 17, 1983 8:41 pm
-- Conversion to new IO interface.

Changed by Schroeder and Levin on September 22, 1983 3:22 pm
-- Eliminate FileIO interface, export to FS instead.

Changed by MBrown on October 11, 1983 2:04 pm
-- In setIndex, don't extend file if opened readonly (reported by Atkinson). Define maxStopIndexPlusOne: INT = maxBytesMoved, not maxBytesMoved+1 (reported by Nix).

Changed by MBrown on October 21, 1983 1:42 pm
-- Implemented closeFSOpenFileOnClose option. truncatePagesOnClose option still unimplemented.