-- 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; -- 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. Ę3˜JšœÕ8Īrœĩ œœĨ˜¤č—…—t&t_