-- FileStreamImpl.mesa -- Please maintain change log at end of file. -- Last Edited by -- MBrown on September 17, 1983 8:39 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 November 28, 1983 12:36 pm -- Hagmann on December 6, 1983 4:51 pm DIRECTORY BasicTime USING [GMT], Basics USING [bytesPerWord, LongNumber, BITAND, LowHalf], FS, FileStream, FileStreamPrivate USING[ Data, DoFinalization, FSDataHandle, BufferNodeHandle, BufferNode, FileDataHandle, FileData, NodeStatus, ProcHandle, StartRequest ], FSLock USING [RemoveREF ], PrincOps USING [ByteBltBlock], PrincOpsUtils USING [ByteBlt], Process USING [Detach, GetPriority, Priority, priorityForeground, SetPriority], IO, IOUtils, Rope, RuntimeError USING [BoundsFault], SafeStorage USING [EstablishFinalization, FinalizationQueue, FQNext, NewFQ], VM USING [ Free, PageNumber] ; FileStreamImpl: CEDAR MONITOR LOCKS fileData.lockRecord USING fileData: FileDataHandle IMPORTS Basics, FileStreamPrivate , FS, FSLock, I: PrincOpsUtils, IO, IOUtils, Process, RuntimeError, SafeStorage, VM EXPORTS FileStreamPrivate, FileStream = 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; bytesPerFilePage: CARDINAL = FS.BytesForPages[1]; minFileExtend: INT = 10*bytesPerFilePage; Data: TYPE = FileStreamPrivate.Data; FSDataHandle: TYPE = FileStreamPrivate.FSDataHandle; BufferNode: TYPE = FileStreamPrivate.BufferNode; BufferNodeHandle: TYPE = FileStreamPrivate.BufferNodeHandle; FileDataHandle: TYPE = FileStreamPrivate.FileDataHandle; FileData: TYPE = FileStreamPrivate.FileData; ProcHandle: TYPE = FileStreamPrivate.ProcHandle; -- This code does not protect itself from parallel use of a stream by concurrent -- processes. It assumes that the processes will synchronize at a higher level. -- Parallel use of different streams for the same open file is expected, but the -- read/write stream must be opened by StreamOpen or StreamFromOpenFile, -- and read stream by StreamFromOpenStream. -- Get and Put CleanupAfterPut: ENTRY PROC [fileData: FileDataHandle, selfData: FSDataHandle] = INLINE { -- Restores dataBytesInBuffer and fileLength if they are messed up by a PutChar or -- PutBlock past the end of file. -- Same logic in SetLengthUnderMonitor. -- only call this routine with a write stream currentNode: BufferNodeHandle = selfData.currentNode; IF currentNode.didPut THEN { currentNode.bufferDirty _ TRUE; IF selfData.index > currentNode.dataBytesInBuffer THEN { currentNode.dataBytesInBuffer _ selfData.index; fileData.fileLength _ currentNode.firstFileByteInBuffer + selfData.index }; currentNode.didPut _ FALSE }}; EstablishFileLength: ENTRY PROC[fileData: FileDataHandle ] RETURNS [fileLength: INT] = INLINE { -- Paw through write stream info to find the file length. The new length is -- the true length modulo some uncertainity whether a put was done in parallel -- during the execution of this routine. The file length returned is -- as least as big as the file was when the monitor was acquired. This -- is fine because the notion of EOF or file length for a reader of a file that is -- in the process of being written is somewhat vague. A higher level protocol -- should keep this straight in the client (why are you reading bytes that -- might not be there yet?). Fix up dataBytesInBuffer if needed. -- (This is mostly intended to allow the read stream to look at the file size seen -- by the write stream.) ENABLE UNWIND => NULL; writeData: FSDataHandle _ fileData.writeStreamData; IF writeData = NIL OR writeData.streamIsClosed THEN { writeData _ NIL ; RETURN[fileData.fileLength] ; } ELSE { writeNode: BufferNodeHandle = writeData.currentNode ; writeNode.dataBytesInBuffer _ MAX[ writeData.index, writeNode.dataBytesInBuffer] ; fileLength _ MAX[ fileData.fileLength, writeNode.firstFileByteInBuffer + writeNode.dataBytesInBuffer ] ; fileData.fileLength _ fileLength; }; writeData _ NIL ; }; convertFStoIOError: PROC [self: STREAM, error: FS.ErrorDesc] = INLINE { selfData: FSDataHandle _ NARROW[self.streamData]; selfData.FSErrorDesc _ error ; IO.Error[$Failure, self]; }; GetChar: PUBLIC PROC [self: STREAM] RETURNS [CHAR] = { ENABLE FS.Error => { convertFStoIOError [self, error]; }; selfData: FSDataHandle _ NARROW[self.streamData]; node: BufferNodeHandle _ selfData.currentNode ; c: CHAR; fileLength: INT ; IF selfData.index >= node.dataBytesInBuffer THEN { -- Suspect that end-of-buffer or end-of-file has been reached. -- This may be false! However, the test is cheap and usually false. fileLength _ EstablishFileLength[fileData: selfData.fileData ] ; -- File length may be wrong if writer is using the same buffer -- as the reader, so get a good file length. This is not cheap: we -- have to get a monitor lock and maybe look inside the write stream. -- Note that we use the local variable fileLength and not -- selfData.fileData.fileLength. IF fileLength <= selfData.index+node.firstFileByteInBuffer THEN ERROR IO.EndOfStream[self]; -- We are not at EOF. If we are at EOB, then get the next buffer. -- Not EOF and not EOB can occur if the writer has put some -- char's into the buffer and this was not reflected in dataBytesInBuffer -- until we did the EstablishFileLength call. IF selfData.index = node.bufferBytes THEN node _ AdvanceBuffer[selfData] }; TRUSTED{c _ node.buffer[selfData.index]}; selfData.index _ selfData.index + 1; selfData _ NIL ; RETURN[c] ; }; PutChar: PUBLIC PROC [self: STREAM, char: CHAR] = { ENABLE FS.Error => { convertFStoIOError [self, error]; }; selfData: FSDataHandle _ NARROW[self.streamData]; node: BufferNodeHandle _ selfData.currentNode ; IF selfData.index = node.bufferBytes THEN node _ AdvanceBuffer[selfData]; TRUSTED{node.buffer[selfData.index] _ char}; selfData.index _ selfData.index + 1; node.didPut _ TRUE ; selfData _ NIL ; }; -- Change use to IOUtils.AddNAT when the 0+0 gives NAT.LAST bug is fixed AddNAT: PROC [a, b: NAT] RETURNS [NAT] = INLINE { sum: CARDINAL = LOOPHOLE [a, CARDINAL] + LOOPHOLE [b, CARDINAL]; RETURN [IF sum > 0 THEN LOOPHOLE [sum, NAT] ELSE IF sum = 0 THEN 0 ELSE NAT.LAST]; }; GetBlock: PUBLIC PROC [self: STREAM, block: REF TEXT, startIndex: NAT, count: NAT] RETURNS [nBytesRead: NAT] = TRUSTED { ENABLE FS.Error => { convertFStoIOError [self, error]; }; selfData: FSDataHandle _ NARROW[self.streamData]; textBlock: PrincOps.ByteBltBlock; countRemaining: NAT; stopIndexPlusOne: NAT = MIN [block.maxLength, 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.currentNode.buffer, startIndex: selfData.index, stopIndexPlusOne: selfData.currentNode.dataBytesInBuffer]; countTransferred: CARDINAL _ 0; IF bufferBlock.startIndex < bufferBlock.stopIndexPlusOne THEN countTransferred _ I.ByteBlt[from: bufferBlock, to: textBlock]; selfData.index _ selfData.index + countTransferred; nBytesRead _ nBytesRead + countTransferred; IF (countRemaining _ countRemaining - countTransferred) = 0 THEN EXIT; -- Bytes may be added concurrently with this get. EstablishFileLength gives -- a true file length (which may be different from what it was when we -- started this iteration) to see if there is more data to blt. IF EstablishFileLength[fileData: selfData.fileData ] <= selfData.index + selfData.currentNode.firstFileByteInBuffer THEN EXIT; textBlock.startIndex _ textBlock.startIndex + countTransferred; -- The below IF is needed for the same reason we called -- EstablishFileLength above. IF selfData.index = selfData.currentNode.bufferBytes THEN [] _ AdvanceBuffer[selfData]; ENDLOOP; IF nBytesRead # 0 THEN block.length _ startIndex + nBytesRead; selfData _ NIL ; RETURN[nBytesRead] }; PutBlock: PUBLIC PROC [self: STREAM, block: REF READONLY TEXT, startIndex: NAT, count: NAT] = TRUSTED { ENABLE FS.Error => { convertFStoIOError [self, error]; }; selfData: FSDataHandle _ NARROW[self.streamData]; -- Fail if startIndex<0 or stopIndexPlusOne<0. textBlock: PrincOps.ByteBltBlock; countRemaining: NAT; stopIndexPlusOne: NAT _ 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.currentNode.buffer, startIndex: selfData.index, stopIndexPlusOne: selfData.currentNode.bufferBytes]; -- allow put past current eof. countTransferred: CARDINAL _ I.ByteBlt[from: textBlock, to: bufferBlock]; selfData.index _ selfData.index + countTransferred; selfData.currentNode.didPut _ TRUE; IF (countRemaining _ countRemaining - countTransferred) = 0 THEN EXIT; textBlock.startIndex _ textBlock.startIndex + countTransferred; [] _ AdvanceBuffer[selfData]; ENDLOOP; selfData _ NIL ; }; maxWordsMoved: INT = (LAST[CARDINAL] / bytesPerWord) - 1; maxBytesMoved: INT = maxWordsMoved * bytesPerWord; maxStopIndexPlusOne: INT = maxBytesMoved + 1; -- all designed to make the max number of bytes transferred an integral number of --words, which is good UnsafeGetBlock: PUBLIC UNSAFE PROC [self: STREAM, block: IO.UnsafeBlock] RETURNS [nBytesRead: INT] = UNCHECKED { ENABLE FS.Error => { convertFStoIOError [self, error]; }; selfData: FSDataHandle _ 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 { selfData _ NIL ; 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.currentNode.buffer, startIndex: selfData.index, stopIndexPlusOne: selfData.currentNode.dataBytesInBuffer]; countTransferred: CARDINAL _ 0; IF bufferBlock.startIndex < bufferBlock.stopIndexPlusOne THEN countTransferred _ I.ByteBlt[from: bufferBlock, to: textBlock]; selfData.index _ selfData.index + countTransferred; nBytesRead _ nBytesRead + countTransferred; IF (countRemaining _ countRemaining - countTransferred) = 0 THEN EXIT; IF EstablishFileLength[fileData: selfData.fileData ] <= selfData.index + selfData.currentNode.firstFileByteInBuffer THEN { selfData _ NIL ; GOTO return; }; textBlock.startIndex _ textBlock.startIndex + countTransferred; IF selfData.index = selfData.currentNode.bufferBytes THEN [] _ AdvanceBuffer[selfData]; ENDLOOP; IF textBlock.stopIndexPlusOne = stopIndexPlusOne THEN { selfData _ NIL ; GOTO return; }; -- Assert textBlock.stopIndexPlusOne = maxStopIndexPlusOne block.base _ block.base + maxWordsMoved; block.startIndex _ 0; stopIndexPlusOne _ stopIndexPlusOne - maxBytesMoved; ENDLOOP; EXITS return => RETURN [nBytesRead] }; UnsafePutBlock: PUBLIC PROC [self: STREAM, block: IO.UnsafeBlock] = TRUSTED { ENABLE FS.Error => { convertFStoIOError [self, error]; }; selfData: FSDataHandle _ 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.currentNode.buffer, startIndex: selfData.index, stopIndexPlusOne: selfData.currentNode.bufferBytes]; -- allow put past current eof. countTransferred: CARDINAL _ I.ByteBlt[from: textBlock, to: bufferBlock]; selfData.index _ selfData.index + countTransferred; selfData.currentNode.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 ; selfData _ NIL ; }; AdvanceBuffer: PROC [fsData: FSDataHandle] RETURNS [node: BufferNodeHandle]= { -- 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, UnsafeGetBlock, UnsafePutBlock. fileData: FileDataHandle = fsData.fileData; firstByteOfNextPage: INT = fsData.currentNode.firstFileByteInBuffer + fsData.currentNode.bufferBytes; changeSize: BOOL _ FALSE; IF firstByteOfNextPage = maxLength THEN ERROR IO.Error[$Failure, NIL]; IF fsData.isWriteStream THEN CleanupAfterPut[fileData: fileData, selfData: fsData]; IF firstByteOfNextPage >= fileData.byteSize THEN { newSize: INT _ 0; IF fileData.extendFileProc # NIL THEN newSize _ fileData.extendFileProc[firstByteOfNextPage] ; fileData.byteSize _ IF newSize # 0 THEN MAX[newSize, firstByteOfNextPage] ELSE fileData.byteSize + MAX[minFileExtend, ((fileData.byteSize/10)/bytesPerFilePage)*bytesPerFilePage]; SetFileSize[fileData.fileHandle, fileData.byteSize] }; node _ SetupBuffer[fileData: fileData, fsData: fsData, fileByte: firstByteOfNextPage]; fsData.index _ LowHalf[firstByteOfNextPage-fsData.currentNode.firstFileByteInBuffer]; fsData _ NIL ; }; EndOf: PUBLIC PROC [self: STREAM] RETURNS[BOOL] = { selfData: FSDataHandle _ NARROW[self.streamData]; node: BufferNodeHandle _ selfData.currentNode ; -- do cheap test to see if not at EOF IF selfData.index >= node.dataBytesInBuffer THEN { -- Cheap test inconclusive. Find real file length. fileLength: INT = EstablishFileLength[fileData: selfData.fileData]; IF fileLength <= selfData.index+node.firstFileByteInBuffer THEN RETURN[TRUE]; }; selfData _ NIL ; RETURN[FALSE]; }; CharsAvail: PUBLIC PROC [self: STREAM, wait: BOOL] RETURNS [INT] = { RETURN[INT.LAST] }; GetIndex: PUBLIC PROC [self: STREAM] RETURNS [index: INT] = { selfData: FSDataHandle _ NARROW[self.streamData]; index _ selfData.currentNode.firstFileByteInBuffer + selfData.index ; selfData _ NIL ; }; SetIndex: PUBLIC PROC [self: STREAM, index: INT] = { ENABLE FS.Error => { convertFStoIOError [self, error]; }; fsData: FSDataHandle _ NARROW[self.streamData]; currentNode: BufferNodeHandle _ fsData.currentNode ; firstBufferByte: INT _ currentNode.firstFileByteInBuffer; fileData: FileDataHandle = fsData.fileData ; fileLength: INT ; IF index < 0 THEN ERROR IO.Error[BadIndex, self]; -- Make sure dataBytesInBuffer and fileLength are correct by calling -- CleanupAfterPut or EstablishFileLength IF fsData.isWriteStream THEN { CleanupAfterPut[fileData: fileData, selfData: fsData]; fileLength _ fileData.fileLength; } ELSE fileLength _ EstablishFileLength[fileData: fileData ]; IF index > fileLength THEN ERROR IO.EndOfStream[self]; -- ensure that page containing byte "index" is in the buffer IF index NOT IN [firstBufferByte .. firstBufferByte+currentNode.bufferBytes) THEN { firstBufferByte _ index - (index MOD currentNode.bufferBytes); currentNode _ SetupBuffer[fileData: fileData, fsData: fsData, fileByte: firstBufferByte]; }; fsData.index _ index - firstBufferByte; fsData _ NIL ; }; Reset: PUBLIC PROC [self: STREAM] = { SetIndex[self, GetLength[self]] }; Flush: PUBLIC PROC [self: STREAM] = { ENABLE FS.Error => { convertFStoIOError [self, error]; }; fsData: FSDataHandle _ NARROW[self.streamData]; IF fsData.isWriteStream THEN ForceOut[ fsData: fsData ]; fsData _ NIL ; }; Close: PUBLIC PROC [self: STREAM, abort: BOOL] = { ENABLE FS.Error => { convertFStoIOError [self, error]; }; fsData: FSDataHandle _ NARROW[self.streamData]; ForceOut[ fsData: fsData ]; CloseFileDataForStream[fileData: fsData.fileData, fsData: fsData]; fsData _ NIL ; self.streamData _ NIL ; self.streamProcs _ IOUtils.closedStreamProcs }; -- Procs that are called via the property list mechanism. GetLength: PUBLIC PROC [self: STREAM] RETURNS [length: INT] = { selfData: FSDataHandle _ NARROW[self.streamData]; IF selfData.streamIsClosed THEN ERROR IO.Error[StreamClosed, self]; length _ EstablishFileLength[fileData: selfData.fileData ] ; selfData _ NIL ; }; clearLowBits: CARDINAL = CARDINAL.LAST-(bytesPerFilePage-1); clearHighBits: CARDINAL = (bytesPerFilePage-1); maxLength: INT = INT.LAST - bytesPerFilePage; SetLength: PUBLIC PROC [self: STREAM, length: INT] = { -- Note: do not reduce the size of a shortened file until stream closed. ENABLE FS.Error => { convertFStoIOError [self, error]; }; fsData: FSDataHandle _ NARROW[self.streamData]; IF fsData.streamIsClosed THEN ERROR IO.Error[StreamClosed, self]; IF length NOT IN [0 .. maxLength] THEN ERROR IO.Error[BadIndex, self]; SetLengthUnderMonitor[fileData: fsData.fileData, length: length]; IF fsData.index+fsData.currentNode.firstFileByteInBuffer > length THEN { -- If old index was past EOF, then move it to EOF. -- We leave the cloned read stream alone: if it does not do a setPosition then -- it will get an EOF on its next read. fsData.index _ 0 ; SetIndex[self: self, index: length]; }; fsData _ NIL ; }; 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]; }; PagesForRoundUpBytes: PROC [bytes: INT] RETURNS [INT] = INLINE { RETURN[RoundUpToPages[bytes]/bytesPerFilePage]; }; SetLengthUnderMonitor: ENTRY PROC [fileData: FileDataHandle, length: INT] = { ENABLE UNWIND => NULL; newFileBytes: INT = RoundUpToPages[length]; oldFileLength: INT ; nowNode: BufferNodeHandle _ fileData.firstBufferNode; writeData: FSDataHandle _ fileData.writeStreamData ; IF writeData # NIL AND writeData.currentNode.didPut THEN { -- CleanupAfterPut logic is copied here, but we cannot call CleanupAfterPut -- because it is an ENTRY currentNode: BufferNodeHandle = writeData.currentNode; currentNode.bufferDirty _ TRUE; currentNode.didPut _ FALSE; IF writeData.index > currentNode.dataBytesInBuffer THEN { currentNode.dataBytesInBuffer _ writeData.index; fileData.fileLength _ currentNode.firstFileByteInBuffer + writeData.index }; currentNode.didPut _ FALSE }; oldFileLength _ fileData.fileLength ; fileData.fileLength _ length; IF length < fileData.validBytesOnDisk THEN fileData.validBytesOnDisk _ length ; -- grow file if needed IF length > fileData.byteSize THEN { fileData.byteSize _ newFileBytes; SetFileSize[fileData.fileHandle, fileData.byteSize]; }; -- Look through nodes and adjust those past EOF or with EOF in buffer UNTIL nowNode = NIL DO IF (nowNode.status # invalid) THEN { IF (nowNode.firstFileByteInBuffer+nowNode.bufferBytes > length) THEN { IF nowNode.firstFileByteInBuffer >= length THEN { -- All of the buffer is past EOF. -- Set dataBytesInBuffer to 0 so that gets will find themselves at EOF, -- and clean the node to avoid redundant write nowNode.dataBytesInBuffer _ 0; nowNode.bufferDirty _ FALSE ; nowNode.didPut _ FALSE ; } ELSE { -- EOF is in (or just past) this buffer nowNode.dataBytesInBuffer _ length - nowNode.firstFileByteInBuffer ; IF nowNode.didPut THEN { nowNode.didPut _ FALSE ; nowNode.bufferDirty _ TRUE ; }; }; } ELSE { -- all of node is in the file nowNode.dataBytesInBuffer _ nowNode.bufferBytes ; }; }; nowNode _ nowNode.nextBufferNode ; ENDLOOP; writeData _ NIL ; }; EraseChar: PUBLIC 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: PUBLIC PROC [self: STREAM, char: CHAR] = { selfData: FSDataHandle _ 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]; selfData _ NIL ; }; CloseFileDataForStream: ENTRY PROC [fileData: FileDataHandle, fsData: FSDataHandle] = { -- Processing for "Close" that must be done under the monitor needDeleted: INT _ IF fileData.numberOfStreams = 0 THEN INT.LAST ELSE fileData.streamBufferParms.nBuffers ; lastNode: BufferNodeHandle _ NIL ; node: BufferNodeHandle ; IF (node _ fsData.currentNode) # NIL THEN node.useCount _ node.useCount-1 ; IF (node _ fsData.readAheadNode) # NIL THEN node.useCount _ node.useCount-1 ; fsData.currentNode _ NIL ; fsData.readAheadNode _ NIL; IF fsData.isWriteStream AND fileData.accessRights = $write AND fileData.streamOptions[truncatePagesOnClose] THEN { SetFileSize[fileData.fileHandle, fileData.fileLength] ; }; IF (fileData.numberOfStreams _ fileData.numberOfStreams - 1) = 0 THEN { IF fileData.streamOptions[closeFSOpenFileOnClose] THEN fileData.fileHandle.Close[]; fileData.fileHandle _ FS.nullOpenFile ; }; -- look for up to two buffers to free if another stream is around -- else, free all buffers node _ fileData.firstBufferNode ; UNTIL node = NIL OR needDeleted = 0 DO IF node.useCount = 0 THEN { TRUSTED{VM.Free[node.bufferInterval]}; IF fileData.firstBufferNode = node THEN fileData.firstBufferNode _ node.nextBufferNode ELSE lastNode.nextBufferNode _ node.nextBufferNode ; needDeleted _ needDeleted - 1 ; } ELSE { lastNode _ node ; }; node _ node.nextBufferNode ; ENDLOOP; fsData.streamIsClosed _ TRUE; BROADCAST fileData.somethingHappened ; fsData _ NIL ; }; -- Insure that all buffers, except the one corresponding to the currentNode, -- have been written to disk. -- Normal cases are too return immediately when no writes are outstanding, -- or to wait until one finishes. FinishWrites: ENTRY PROC [fileData: FileDataHandle, fsData: FSDataHandle, currentNode: BufferNodeHandle] = { ENABLE UNWIND => NULL; nowNode: BufferNodeHandle _ fileData.firstBufferNode; IF currentNode = NIL THEN { -- this is only true when called via ForceOut WHILE fileData.writeCount # 0 DO WAIT fileData.somethingHappened ; ENDLOOP; }; UNTIL nowNode = NIL DO IF nowNode # currentNode AND nowNode.bufferDirty AND (nowNode.status = needsSequentialWrite OR currentNode = NIL) THEN { -- What has happened is that an asynchronous write has failed, -- or we are trying to flush all dirty pages in ForceOut. -- The parallel process has given up, and we are about to re-do -- the write under the monitor to get the signal and the stack -- correct so that the client sees a correct view of the error. nowNode.status _ sequentialWriteActive ; TRUSTED{WriteFilePages[f: fsData.fileData.fileHandle, to: nowNode.firstFileByteInBuffer, numPages: PagesForRoundUpBytes[nowNode.dataBytesInBuffer], from: nowNode.buffer] }; nowNode.bufferDirty _ FALSE ; nowNode.status _ valid ; }; nowNode _ nowNode.nextBufferNode ; ENDLOOP; fsData _ NIL ; }; FinishRead: ENTRY PROC [fileData: FileDataHandle, node: BufferNodeHandle, bufferSize: INT] = INLINE { ENABLE UNWIND => NULL; node.status _ valid; node.dataBytesInBuffer _ bufferSize ; BROADCAST fileData.somethingHappened; }; FinishBadRead: ENTRY PROC [fileData: FileDataHandle, node: BufferNodeHandle] = INLINE { ENABLE UNWIND => NULL; node.status _ invalid; node.dataBytesInBuffer _ 0 ; BROADCAST fileData.somethingHappened; }; FinishBadPreRead: ENTRY PROC [fileData: FileDataHandle, node: BufferNodeHandle] = INLINE { ENABLE UNWIND => NULL; node.status _ needsSequentialRead; node.dataBytesInBuffer _ 0 ; BROADCAST fileData.somethingHappened; }; markNodeNotWritten: ENTRY PROC [fileData: FileDataHandle, node: BufferNodeHandle] = INLINE { ENABLE UNWIND => NULL; node.status _ needsSequentialWrite ; node.bufferDirty _ TRUE ; fileData.writeCount _ fileData.writeCount - 1; BROADCAST fileData.somethingHappened; }; markNodeWritten: ENTRY PROC [fileData: FileDataHandle, node: BufferNodeHandle] = INLINE { ENABLE UNWIND => NULL; node.status _ valid ; fileData.writeCount _ fileData.writeCount - 1; BROADCAST fileData.somethingHappened; }; bumpWriteCount: ENTRY PROC [fileData: FileDataHandle] = INLINE { ENABLE UNWIND => NULL; fileData.writeCount _ fileData.writeCount + 1; }; WaitForOneBufferNotWriting: ENTRY PROC [fileData: FileDataHandle] = INLINE { ENABLE UNWIND => NULL; WHILE fileData.writeCount >= fileData.streamBufferParms.nBuffers DO WAIT fileData.somethingHappened; ENDLOOP; }; ProcessNode: PUBLIC PROC [ fileData: FileDataHandle, node: BufferNodeHandle ] = { IF node.status = needsParallelRead THEN { node.status _ parallelReadActive ; ReadAhead [node: node, fileData: fileData]; RETURN ; }; IF node.status = needsParallelWrite THEN { node.status _ parallelWriteActive ; parallelWriteBuffer [ node: node, fileData: fileData ] ; RETURN ; }; ERROR ; }; parallelWriteBuffer: PROC [node: BufferNodeHandle, fileData: FileDataHandle] = { ENABLE FS.Error => { -- By catching and ignoring FS errors, we insure that the write will -- later be done in the process of the client so that signals will -- look correct. markNodeNotWritten[fileData: fileData, node: node]; GOTO done; }; TRUSTED{WriteFilePages[f: fileData.fileHandle, to: node.firstFileByteInBuffer, numPages: PagesForRoundUpBytes[node.dataBytesInBuffer], from: node.buffer];}; markNodeWritten[fileData: fileData, node: node]; EXITS done => RETURN }; ReadAhead: PROC [node: BufferNodeHandle, fileData: FileDataHandle] = { ENABLE FS.Error => { -- on FS errors, invalidate the pre-read FinishBadPreRead[fileData: fileData, node: node]; GOTO done; }; bytesToRead: INT ; fileByte: INT = node.firstFileByteInBuffer ; IF (bytesToRead _ MIN[fileData.fileLength - fileByte, node.bufferBytes]) > 0 THEN TRUSTED{ReadFilePages[f: fileData.fileHandle, from: fileByte, numPages: PagesForRoundUpBytes[bytesToRead], to: node.buffer]}; FinishRead[fileData: fileData, node: node, bufferSize: bytesToRead]; EXITS done => RETURN }; SetupBuffer: PUBLIC PROC [fileData: FileDataHandle, fsData: FSDataHandle, fileByte: INT] RETURNS [currentNode: BufferNodeHandle] = { -- For write streams, didPut = FALSE if on entry (someone else called CleanupAfterPut). -- Arranges buffer so that fileByte (must be buffer-aligned) is the first byte in it. -- If buffer is dirty, writes it to file. -- Maintains invariants of dataBytesInBuffer, bufferBytes, and -- firstFileByteInBuffer in the face of all this. DOES NOT update index. -- Called from AdvanceBuffer, SetIndex, SetLength, -- StreamFromOpenStream and StreamFromOpenFile. node: BufferNodeHandle _ fsData.currentNode ; readAheadNode: BufferNodeHandle; currentNodeStatus: FileStreamPrivate.NodeStatus; IF node = NIL THEN node _ fileData.firstBufferNode ; -- write buffer if needed IF node.bufferDirty AND fsData.isWriteStream THEN { -- See if there are buffers that must be written sequentially. FinishWrites[fileData: fileData, fsData: fsData, currentNode: node]; -- Extend file if we are about to write over it. IF node.dataBytesInBuffer + node.firstFileByteInBuffer > fileData.byteSize THEN { fileData.byteSize _ node.dataBytesInBuffer + node.firstFileByteInBuffer ; SetFileSize[fileData.fileHandle, fileData.byteSize] ; }; IF fileData.validBytesOnDisk < node.dataBytesInBuffer + node.firstFileByteInBuffer THEN fileData.validBytesOnDisk _ node.dataBytesInBuffer + node.firstFileByteInBuffer ; node.status _ needsParallelWrite ; node.bufferDirty _ FALSE ; bumpWriteCount[ fileData: fileData]; FileStreamPrivate.StartRequest [ fileData: fileData, node: node ] ; WaitForOneBufferNotWriting[fileData: fileData]; }; [currentNode, readAheadNode] _ SetUpNodes[fileData: fileData, fsData: fsData, fileByte: fileByte]; -- Copy the status out of the node before the tests. Since the status -- can change at any time, it would be possible to have none of the -- arms of the SELECT executed when there was a pre-read to do. currentNodeStatus _ currentNode.status ; SELECT TRUE FROM currentNodeStatus # valid AND readAheadNode = NIL => { makeNodeValid[fileData: fileData, node: currentNode ]; }; currentNodeStatus = valid AND readAheadNode # NIL => { FileStreamPrivate.StartRequest [ fileData: fileData, node: readAheadNode ]; }; currentNodeStatus # valid AND readAheadNode # NIL => { myPriority: Process.Priority ; myPriority _ Process.GetPriority[]; Process.SetPriority[Process.priorityForeground]; FileStreamPrivate.StartRequest [ fileData: fileData, node: readAheadNode ]; makeNodeValid[fileData: fileData, node: currentNode ]; Process.SetPriority[myPriority]; }; ENDCASE ; -- falls through if currentNode.status = valid AND readAheadNode = NIL fsData _ NIL ; }; makeNodeValid: PROC [fileData: FileDataHandle, node: BufferNodeHandle] = { bytesToRead: INT ; WHILE node.status # valid DO IF doTheRead[fileData: fileData, node: node] THEN { bytesToRead _ MIN[fileData.fileLength - node.firstFileByteInBuffer, node.bufferBytes]; IF fileData.validBytesOnDisk <= node.firstFileByteInBuffer THEN { -- Avoid read: the data is trash on disk. We are extending the file anyway. FinishRead[fileData: fileData, node: node, bufferSize: bytesToRead]; } ELSE { IF bytesToRead > 0 THEN TRUSTED{ ReadFilePages[f: fileData.fileHandle, from: node.firstFileByteInBuffer, numPages: PagesForRoundUpBytes[bytesToRead], to: node.buffer ! FS.Error => { FinishBadRead[ fileData: fileData, node: node]; }; ]; }; FinishRead[fileData: fileData, node: node, bufferSize: bytesToRead]; }; }; ENDLOOP; }; doTheRead: ENTRY PROC [fileData: FileDataHandle, node: BufferNodeHandle] RETURNS [BOOL] = INLINE { ENABLE UNWIND => NULL; IF node.status = invalid OR node.status = needsSequentialRead THEN { node.status _ sequentialReadActive ; RETURN [TRUE]; }; IF node.status = valid THEN RETURN [ FALSE ] ELSE WAIT fileData.somethingHappened ; RETURN [ FALSE ] ; }; SetUpNodes: ENTRY PROC [fileData: FileDataHandle, fsData: FSDataHandle, fileByte: INT] RETURNS [currentNode: BufferNodeHandle _ NIL, nextNode: BufferNodeHandle _ NIL] = { -- This procedure runs under the monitor. -- It looks for buffers for the current and next nodes. -- The node returned in currentNode is the one to use as current. If it is -- marked "active", then the caller must fill it before use. -- The node nextNode is returned as NIL if no preread is needed. If non-NIL, -- the caller should arrange to preread into this buffer. ENABLE UNWIND => NULL; nowNode: BufferNodeHandle _ fileData.firstBufferNode; availableNode: BufferNodeHandle _ NIL ; availableNodeLRUCount: INT _ 1000000; maxLRUCount: INT _ 0 ; node: BufferNodeHandle ; oldCurrentNode: BufferNodeHandle _ fsData.currentNode ; oldFirstByteInBuffer: INT = IF fsData.currentNode = NIL THEN -1 ELSE fsData.currentNode.firstFileByteInBuffer; bufferBytes: INT = nowNode.bufferBytes ; IF (node _ fsData.currentNode) # NIL THEN node.useCount _ node.useCount-1 ; IF (node _ fsData.readAheadNode) # NIL THEN node.useCount _ node.useCount-1 ; fsData.currentNode _ NIL ; fsData.readAheadNode _ NIL; UNTIL nowNode = NIL DO firstByte: INT _ nowNode.firstFileByteInBuffer; IF nowNode.LRUCount > maxLRUCount THEN maxLRUCount _ nowNode.LRUCount ; SELECT TRUE FROM -- buffer already has correct position in file firstByte = fileByte => { currentNode _ nowNode ; fsData.currentNode _ nowNode ; nowNode.useCount _ nowNode.useCount+1 ; }; -- buffer is next after current firstByte = fileByte+bufferBytes => { IF fileData.streamBufferParms.nBuffers > 1 THEN { nowNode.useCount _ nowNode.useCount+1 ; fsData.readAheadNode _ nowNode ; } ELSE { IF( nowNode.status = valid OR nowNode.status = invalid) AND nowNode.useCount = 0 AND nowNode.LRUCount <= availableNodeLRUCount THEN { availableNodeLRUCount _ nowNode.LRUCount ; availableNode _ nowNode ; }; }; }; -- buffer not "near" stream pointer (tested in above two cases) -- and it is not active, and it is not near the other stream (if it exits) ( nowNode.status = valid OR nowNode.status = invalid) AND nowNode.useCount = 0 AND nowNode.LRUCount <= availableNodeLRUCount => { availableNodeLRUCount _ nowNode.LRUCount ; availableNode _ nowNode ; }; ENDCASE; -- SELECT TRUE nowNode _ nowNode.nextBufferNode ; ENDLOOP; IF currentNode = NIL THEN { WHILE availableNode = NIL DO IF oldCurrentNode # NIL THEN { -- re-establish invariant about currentNode always being valid outside monitor -- (except during stream creation) fsData.currentNode _ oldCurrentNode ; oldCurrentNode.useCount _ oldCurrentNode.useCount + 1 ; }; WAIT fileData.somethingHappened; IF oldCurrentNode # NIL THEN { fsData.currentNode _ NIL ; oldCurrentNode.useCount _ oldCurrentNode.useCount - 1 ; }; nowNode _ fileData.firstBufferNode; UNTIL nowNode = NIL DO IF ( nowNode.status = valid OR nowNode.status = invalid) AND nowNode.useCount = 0 THEN availableNode _ nowNode ; nowNode.LRUCount _ 0 ; nowNode _ nowNode.nextBufferNode ; ENDLOOP; ENDLOOP; currentNode _ availableNode ; currentNode.LRUCount _ maxLRUCount + 1 ; currentNode.useCount _ currentNode.useCount+1 ; currentNode.status _ invalid ; currentNode.firstFileByteInBuffer _ fileByte ; availableNode _ NIL ; fsData.currentNode _ currentNode ; }; -- preread if there is not a preread in progress, and -- a sequence has been established. IF (NOT fileData.preReadInProgress) AND fileData.streamBufferParms.nBuffers > 1 AND (oldFirstByteInBuffer+bufferBytes = fileByte) AND (fsData.lastFirstByteInBuffer+bufferBytes = oldFirstByteInBuffer) AND (fileData.fileLength > fileByte+bufferBytes) AND (fileData.validBytesOnDisk > fsData.lastFirstByteInBuffer+bufferBytes) THEN { IF fsData.readAheadNode # NIL THEN { -- a node already points to the right place in the file IF fsData.readAheadNode.status = invalid THEN { nextNode _ fsData.readAheadNode ; nextNode.status _ needsParallelRead ; }; } ELSE { IF availableNode = NIL THEN { availableNodeLRUCount _ 1000000; nowNode _ fileData.firstBufferNode; UNTIL nowNode = NIL DO IF ( nowNode.status = valid OR nowNode.status = invalid) AND nowNode.useCount = 0 AND nowNode.LRUCount <= availableNodeLRUCount THEN availableNode _ nowNode ; nowNode _ nowNode.nextBufferNode ; ENDLOOP; }; IF availableNode # NIL THEN { nextNode _ availableNode ; nextNode.status _ needsParallelRead ; nextNode.LRUCount _ maxLRUCount + 1 ; nextNode.useCount _ nextNode.useCount+1 ; nextNode.firstFileByteInBuffer _ fileByte + bufferBytes ; fsData.readAheadNode _ nextNode ; }; }; }; fsData.lastFirstByteInBuffer _ oldFirstByteInBuffer ; fsData _ NIL ; }; ForceOut: PROC [fsData: FSDataHandle] = { -- Called from Flush for write streams, or Close for any stream -- This is the only proc that sets byte length, and only proc that finishes trans. fileData: FileDataHandle = fsData.fileData; node: BufferNodeHandle _ fsData.currentNode ; IF fsData.isWriteStream THEN CleanupAfterPut[fileData: fileData, selfData: fsData]; IF node.dataBytesInBuffer + node.firstFileByteInBuffer > fileData.byteSize THEN { fileData.byteSize _ node.dataBytesInBuffer + node.firstFileByteInBuffer ; SetFileSize[fileData.fileHandle, fileData.byteSize] ; }; IF fileData.validBytesOnDisk < node.dataBytesInBuffer + node.firstFileByteInBuffer THEN fileData.validBytesOnDisk _ node.dataBytesInBuffer + node.firstFileByteInBuffer ; -- This call does the writes under the monitor lock. -- This should be true for read streams since you want to stop the writer from -- dirting buffers. -- For write streams, you could get by without the lock provided you were -- extremely careful about a ForceOut on the read stream, a StreamFromOpenStream -- (it will allocate more buffers). The easy way out is to use the monitor. FinishWrites[ fileData: fileData, fsData: fsData, currentNode: NIL]; IF fsData.isWriteStream AND fileData.accessRights = $write AND fileData.fileLength # fileData.byteLength THEN { fileData.byteLength _ fileData.fileLength; fileData.fileHandle.SetByteCountAndCreatedTime[fileData.byteLength] }; fsData _ NIL ; }; SaveStreamError: PUBLIC PROCEDURE [self: STREAM, error: FS.ErrorDesc] = { WITH self.streamData SELECT FROM fsData: FSDataHandle => fsData.FSErrorDesc _ error ; ENDCASE => ERROR IO.Error[NotImplementedForThisStream, self]; }; ErrorFromStream: PUBLIC PROCEDURE [self: STREAM] RETURNS [FS.ErrorDesc] = { WITH self.streamData SELECT FROM fsData: FSDataHandle => RETURN [fsData.FSErrorDesc]; ENDCASE => ERROR IO.Error[NotImplementedForThisStream, self]; }; SetStreamClassData: PUBLIC PROCEDURE [self: STREAM, data: REF ANY] = { WITH self.streamData SELECT FROM fsData: FSDataHandle => fsData.StreamClassData _ data ; ENDCASE => ERROR IO.Error[NotImplementedForThisStream, self]; }; GetStreamClassData: PUBLIC PROCEDURE [self: STREAM] RETURNS [data: REF ANY] = { WITH self.streamData SELECT FROM fsData: FSDataHandle => RETURN [fsData.StreamClassData] ; ENDCASE => ERROR IO.Error[NotImplementedForThisStream, self]; }; SetFinalizationProc: PUBLIC PROCEDURE [self: STREAM, proc: FileStream.FinalizationProc] = { WITH self.streamData SELECT FROM fsData: FSDataHandle => fsData.FinalizationProc _ proc ; ENDCASE => ERROR IO.Error[NotImplementedForThisStream, self]; }; GetFinalizationProc: PUBLIC PROCEDURE [self: STREAM] RETURNS [proc: FileStream.FinalizationProc] = { WITH self.streamData SELECT FROM fsData: FSDataHandle => RETURN [fsData.FinalizationProc] ; ENDCASE => ERROR IO.Error[NotImplementedForThisStream, self]; }; -- Talking to FS ReadFilePages: PROC [f: FS.OpenFile, from: ByteNumber, numPages: INT, to: LONG POINTER] = INLINE { p: PageNumber = from/bytesPerFilePage; TRUSTED{f.Read[from: p, nPages: numPages, to: to]}; }; WriteFilePages: PROC [f: FS.OpenFile, to: ByteNumber, numPages: INT, from: LONG POINTER] = INLINE { p: PageNumber = to/bytesPerFilePage; f.Write[from: from, nPages: numPages, to: p]; }; SetFileSize: PROC [f: FS.OpenFile, byteSize: ByteCount] = { f.SetPageCount[pages: (byteSize+bytesPerFilePage-1)/bytesPerFilePage]; }; GetFileLock: PROC [f: FS.OpenFile] RETURNS [FS.Lock] = { RETURN [f.GetInfo[].lock] }; ProcHandleFromAccessRights: PUBLIC PROC [accessRights: FS.Lock] RETURNS [ procs: FileStreamPrivate.ProcHandle] = { SELECT accessRights FROM read => RETURN [nucleusFileIOReadProcs]; write => RETURN [nucleusFileIOAllProcs]; ENDCASE => RETURN[NIL]; }; -- Procedure records (never modified) nucleusFileIOReadProcs: PUBLIC FileStreamPrivate.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: PUBLIC FileStreamPrivate.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, eraseChar: EraseChar ]; fQ: SafeStorage.FinalizationQueue; Finalize: PROC = BEGIN DO fsData: FSDataHandle _ NARROW [ SafeStorage.FQNext[fQ] ]; streamIsClosed: BOOL ; FSLock.RemoveREF[fsData]; streamIsClosed _ fsData.streamIsClosed ; IF NOT fsData.streamIsClosed THEN { ForceOut[ fsData: fsData ! FS.Error => CONTINUE]; CloseFileDataForStream[fileData: fsData.fileData, fsData: fsData ! FS.Error => CONTINUE]; }; IF fsData.isWriteStream THEN { IF fsData.fileData.writeStreamData = fsData THEN fsData.fileData.writeStreamData _ NIL ; } ELSE { IF fsData.fileData.firstReadStream = fsData THEN fsData.fileData.firstReadStream _ NIL ; }; IF fsData.FinalizationProc # NIL THEN { fsData.FinalizationProc [ openFile: fsData.fileData.fileHandle, data: fsData.StreamClassData, closed: streamIsClosed]; }; fsData _ NIL; ENDLOOP; END; -- start code: start up finalization stuff IF FileStreamPrivate.DoFinalization THEN { fQ _ SafeStorage.NewFQ[]; SafeStorage.EstablishFinalization[CODE[Data], 2, fQ]; TRUSTED { Process.Detach[FORK Finalize[]] }; }; 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 Hagmann on November 22, 1983 4:29 pm -- Implement multiple-page stream buffer. -- Implement coupled read and write streams on same open file. -- Changed data structures in FileStreamPrivate, and fixed references in this module -- to the new data structures. This meant changes to nearly every routine. -- Close file during StreamOpen if an error occurs in StreamFromOpenFile. -- Implement streamOptions and streamBufferParms features. -- Added finalization. -- Changed name from FileIOFSImpl. -- Split out create code to make FileStreamCreateImpl smaller since compiler blows up in pass 3 Changed by Hagmann on November 28, 1983 12:00 pm -- Fixed EndOf bug for multiple streams. -- Added test for DoFinalization to enable FileStream testing without making a boot file Changed by Hagmann on December 6, 1983 4:52 pm -- Added code for process cache Ü DebugQueueItem: TYPE = RECORD [ current: BufferNodeHandle, currentStatus: FileStreamPrivate.NodeStatus, currentFirstFileByteInBuffer: INT, next: BufferNodeHandle, nextStatus: FileStreamPrivate.NodeStatus, nextFirstFileByteInBuffer: INT ]; DebugQueue: ARRAY [0..20) OF DebugQueueItem ; DebugQueuePut: INT _ 0 ; DebugQueue[DebugQueuePut].current _ currentNode; DebugQueue[DebugQueuePut].currentStatus _ currentNode.status; DebugQueue[DebugQueuePut].currentFirstFileByteInBuffer _ currentNode.firstFileByteInBuffer; DebugQueue[DebugQueuePut].next _ nextNode; IF nextNode = NIL THEN { DebugQueue[DebugQueuePut].nextStatus _ invalid; DebugQueue[DebugQueuePut].nextFirstFileByteInBuffer _ -123; } ELSE { DebugQueue[DebugQueuePut].nextStatus _ nextNode.status; DebugQueue[DebugQueuePut].nextFirstFileByteInBuffer _ nextNode.firstFileByteInBuffer; }; DebugQueuePut _ DebugQueuePut + 1 ; IF DebugQueuePut >= 20 THEN DebugQueuePut _ 0 ; Êý˜JšhÏcÏœÏk œžœžœžœžœžœ(žœ›žœžœ#žœžœGžœ*žœ žœHžœžœ-žœžœžœžœžœ)žœžœžœCžœžœ+žœžœžœžœžœžœžœžœžœžœžœžœœžœžœ"žœžœ$žœ!žœ+žœ1žœ5žœ9žœ1žœ-žœ%˜ÂJšœžœžœ·™×JšôœQœQœQœIœ,œœÏnœžœžœ6žœSœ"œ(œ.œ?žœžœ#žœžœ0žœªžœŸœžœžœžœžœžœMœOœFœHœSœOœKœAœSœœžœžœžœ;žœ žœžœžœžœžœ žœ_žœBžœšžœ$žœžœ žœžœžœ8žœ!Ÿœžœžœžœžœžœ žœžœXžœNžœžœžœ*žœ ?œDœI?œDœFœ:œ!œžœ<žœžœžœCœ<œJœ.œžœ#žœ'žœ[žœžœÐbnœžœžœžœžœ žœžœXžœKžœ#žœ%žœažœžœIœŸœžœžœžœžœžœÏrž¡Ðkr¡ž¡¢¡ž¡œžœžœ žœ¢¡œžœžœžœ žœžœžœžœ œžœžœžœ žœžœžœ žœžœžœžœžœžœXžœMžœžœžœ$¡œ0žœžœžœžœžœfžœžœžœ6žœžœàžœ žœ7žœžœžžœ:žœžœ Mœ Gœ @œžœžœžœQ8œ œžœ3žœ%žœžœžœ8žœžœ œžœžœžœ žœžœžœžœ žœžœžœžœXžœ/œ:žœžœ¡œ¡œžœ$žœHžœžœžœžœžœfžœžœžœ"žœžœÂœžœžœŠžœžœ:žœžœržœžœžœžœžœ(žœ8žœRœœ œžœžœžœžœ žœžœžœž œžœžœXžœOžœžœžœžœžœžœžœžœžœžœ"žœ 3œžœížœCœNœžœxžœzœWœœžœêžœžœ7žœžœ¤žœ:žœžœ žœ{žœžœ žœfžœ3žœ'žœžœ/žœžœ žœ;œŒžœžœžœ œžœžœžœ žœžœžœžœXžœOžœžœžœžœžœžœ"žœ 3œžœÙžœCœNœžœxžœzœ:œžœÊœžœžœŽžœ žœ:žœžœwžœžœ/žœžœ;œŒžœžœ Ÿ œžœžœ!Rœ/œ$œUœJžœcžœžœžœ!žœžœžœžœžœžœ<žœ*žœžœ žœžœžœ[žœ žœžœ&žœžœÔžœ œžœžœžœžœžœ#žœN&œžœ*žœ 4œžœ7žœ<žœžœžœžœžœžœ   œžœžœžœžœžœžœ žœžœžœ œžœžœžœžœ žœ#žœlžœ  œžœžœžœ žœ žœžœVžœažœgžœžœ žœžœžœEœ*œžœžœmžœ<žœžœžœžœ=œžœžœžœCžœ*žœ½žœ  œžœžœžœ0 œžœžœžœ žœžœVžœžœžœ*žœ  œžœžœžœ žœ žœžœVžœ‡žœžœ::œ  œžœžœžœžœ žœ#žœžœžœžœžœlžœžœžœžœ(žœ&žœžœžœ  œžœžœžœ žœ IœžœžœVžœžœžœžœžœ žœžœžœžœžœžœbžœ@žœ 3œOœ(œWžœ Ÿœžœ žœžœžœžœ+žœ%žœžœ2žœŸœžœ žœžœžœžœžœ2Ÿœžœžœ$žœ žœžœžœžœ.žœzžœ žœžœžœ Lœœ]žœžœžœ1žœªžœTžœ$žœ.œžœžœuFœžœ žœžœžœžœ žœ>žœ žœ)žœ "œ Hœ /œEžœžœžœ (œUžœžœžœ"žœ2žœœzžœžœ  œžœžœžœžœžœžœ žœžœžœ>žœžœ/žœ# œžœžœžœžœ#žœžœžœžœžœžœ=žœ žœžœžœ>žœžœžœžœIžœŸœžœžœ;>œžœžœ!žœžœžœžœFžœ#žœžœžœ%žœ!žœžœ:žœžœžœžœ žœ1žœHžœ?žœžœ0žœ7žœCœœ&žœžœžœžœžœžœžœžœ!žœ&žœ5žœ_žœCžœžœž œ,žœMœœKœ$œŸ œžœžœYžœžœžœ<žœžœžœ.œžœžœžœ"žœ žœ žœžœžœžœžœ/žœžœžœ ?œ :œ @œ Ïb!œ @œ1žœØžœYžœžœ Ÿ œžœžœCžœžœžœžœžœFž œ&Ÿ œžœžœ9žœžœžœžœ?ž œ(Ÿœžœžœ8žœžœžœžœKž œ:žœžœ8žœžœžœžœ>žœ6ž œ7žœžœ8žœžœžœžœMž œ4žœžœžœžœžœžœ:Ÿœžœžœžœžœžœžœžœ<žœžœ žœ   œžœžœ>žœ!žœ[žœ žœ"žœižœ žœ(žœ:žœžœEœCœœ:žœžœÚžœ žœ Ÿ œžœ:žœžœ)œ8žœžœžœ"žœžœ8žœžœÇžœ žœ Ÿ œžœžœ?žœžœ)XœVœ*œ?œJœ3œ2œšžœžœžœ5œžœžœžœ ?œR1œžœIžœžœQžœ™žœ²GœDœ@œ2žœžœžœ!žœžœlžœžœ„žœžœÙžœGœžœžœFžœžœžœžœ+žœžœLžœ9žœ Mœ\žœ žœžœžœ²žœ´žœžœžœ5žœžœžœžœžœžœžœžœ#žœ,žœžœ žœžœžœžœžœžœžœžœ Ÿ œžœžœ<žœžœ"žœžœ *œ8œLœ>œNœ;œžœžœžœ\žœžœžœqžœžœžœžœžœ9žœžœžœžœ$žœ!žœžœ9žœžœžœ žœžœžœ'žœ žœ'žœžœžœ /œ¤!œ/žœ)žœ^žœžœžœ žœžœ0žœp@œKœžœžœžœ‘žœœ-žœžœžœžœžœžœžœžœžœžœ Oœ#œ|žœ#žœžœžœžœ|žœ žœžœžœžœ#žœžœsžœžœõžœ<7œ$œžœžœžœ+žœ2žœEžœ0žœJžœžœžœžœ8œžœ'žœbžœžœžœžœTžœ žœžœžœžœžœžœ1žœPžœ žœžœžœÛžœ˜îªJšœü™üJšãœŸœžœ AœSœfžœžœ<žœIžœ™žœQžœa5œOœœJœQœNœCžœžœžœ žœ0žœŽžœ Ÿœžœž œžœ žœžœžœžœBžœžœžœ2Ÿœžœž œžœžœžœžœžœžœžœžœžœžœ3Ÿœžœž œžœžœžœžœžœžœEžœžœžœ1Ÿœžœž œžœžœžœžœžœžœžœžœ"žœžœžœ1Ÿœžœž œžœ*žœžœžœFžœžœžœ1Ÿœžœž œžœžœ+žœžœžœžœ#žœžœžœ;œŸ œžœžœ'žœ žœžœžœ2žœ7Ÿœžœžœ%žœ žœžœžœhŸ œžœžœyŸ œžœžœ žœžœžœ'Ÿœžœžœžœ žœ.žœžœ žœ'žœžœžœžœ&œžœ žœÏžœœžœœžœœ¾žœ žœ Ÿœžœžœžœžœ0žœOžœžœžœ ÏsÐks¤ ¥œO¥¤œ¥œžœžœžœ*žœ(žœ žœžœ*žœ(žœžœžœžœžœžœžœ +œžœ"žœCžœžœžœ ˜Ü>—…—¼Ói