-- FileIOAlpineImpl.mesa -- Last Edited by -- MBrown on June 8, 1983 12:10 pm -- Kolling on June 3, 1983 4:54 pm DIRECTORY AlpineEnvironment, AlpineInterimDirectory, AlpInstance, AlpTransaction, AlpFile, ByteBlt USING [ByteBlt], Environment USING [Block, Byte, bytesPerWord], FileIO, FileIOAlpine, FileIOPrivate, Inline USING [BITAND, BITOR, LongNumber, LowHalf], IO, RPC, Rope, Runtime USING [BoundsFault], SafeStorage USING [NarrowRefFault], Space USING [Create, Delete, Handle, LongPointer, Map, virtualMemory], System USING [GreenwichMeanTime, GetGreenwichMeanTime]; FileIOAlpineImpl: CEDAR PROGRAM IMPORTS AlpineInterimDirectory, AlpTransaction, AlpFile, ByteBlt, FileIO, FileIOAlpine, FileIOPrivate, I: Inline, IO, RPC, Runtime, SafeStorage, Space, System EXPORTS FileIOAlpine, FileIOPrivate = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; UniversalFile: TYPE = AlpineEnvironment.UniversalFile; ByteCount: TYPE = AlpineEnvironment.ByteCount; ByteNumber: TYPE = ByteCount; -- index rather than count PageNumber: TYPE = AlpineEnvironment.PageNumber; bytesPerPage: CARDINAL = AlpineEnvironment.bytesPerPage; bytesPerWord: CARDINAL = Environment.bytesPerWord; wordsPerPage: CARDINAL = AlpineEnvironment.wordsPerPage; Data: TYPE = FileIOPrivate.Data; DataHandle: TYPE = REF Data; AlpineDataHandle: TYPE = FileIOPrivate.AlpineDataHandle; ProcHandle: TYPE = REF IO.StreamProcs; Aborted: PUBLIC ERROR [trans: REF ANY] = CODE; Failure: PUBLIC ERROR [what: ATOM, info: ROPE] = CODE; -- Stream creation AlpineOpen: PUBLIC PROC [ fileName: ROPE, accessOptions: FileIO.AccessOptions, createOptions: FileIO.CreateOptions, closeOptions: FileIO.CloseOptions, transaction: REF ANY, raw: BOOL, createLength: INT, streamBufferParms: FileIO.StreamBufferParms] RETURNS [STREAM] = TRUSTED { openFailure: FileIO.OpenFailure; { -- block for failure exit instance: AlpInstance.Handle; refUniversalFile: REF UniversalFile; createdFile: BOOL; transHandle: AlpTransaction.Handle _ NARROW[transaction ! SafeStorage.NarrowRefFault => { openFailure _ wrongTransactionType; GOTO failure }]; fileHandle: AlpFile.Handle; needRetry: BOOL _ FALSE; haveRetried: BOOL _ FALSE; DO [instance, refUniversalFile, createdFile] _ AlpineInterimDirectory.Open[ fileName, createOptions, createLength ! AlpineInterimDirectory.Error => SELECT why FROM authenticateFailed => ERROR FileIOAlpine.Failure[ $authentication, transHandle.inst.fileStore]; damaged, ownerRecordFull => REJECT; fileAlreadyExists => { openFailure _ fileAlreadyExists; GOTO failure }; serverNotFound, ownerNotFound, fileNotFound => { openFailure _ fileNotFound; GOTO failure }; illegalFileName => { openFailure _ illegalFileName; GOTO failure }; insufficientPermission => ERROR FileIOAlpine.Failure[ $accessDenied, transHandle.inst.fileStore]; lockFailed, transAborted => needRetry _ TRUE; quota => ERROR FileIOAlpine.Failure[ $quotaExceeded, transHandle.inst.fileStore]; remoteCallFailed, regServersUnavailable => ERROR FileIOAlpine.Failure[$communication, transHandle.inst.fileStore]; serverBusy => ERROR FileIOAlpine.Failure[ $serverBusy, transHandle.inst.fileStore]; ENDCASE => REJECT]; -- DirectoryInconsistent {ownerRootFileNotFound} IF NOT needRetry THEN EXIT; IF haveRetried THEN ERROR FileIOAlpine.Failure[ $lockConflict, transHandle.inst.fileStore]; needRetry _ FALSE; haveRetried _ TRUE; ENDLOOP; IF transHandle = NIL THEN transHandle _ AlpTransaction.Create[instance]; { ENABLE BEGIN AlpFile.Unknown => SELECT what FROM transID, openFileID => ERROR FileIOAlpine.Aborted[transHandle]; ENDCASE => REJECT; RPC.CallFailed => IF why IN [timeout .. busy] THEN ERROR FileIOAlpine.Failure[ $communications, transHandle.inst.fileStore]; END; [fileHandle, ] _ AlpFile.Open[transHandle, refUniversalFile^, IF accessOptions = $read THEN $readOnly ELSE $readWrite, [IF accessOptions = $read THEN $read ELSE $write, $wait], $log, $random]; }; RETURN[StreamFromAlpineOpenFile[ fileHandle, fileName, accessOptions, closeOptions, raw, streamBufferParms ! FileIO.OpenFailed => CHECKED { openFailure _ why; GOTO failure }]]; EXITS failure => { retryFileName: ROPE = SIGNAL FileIO.OpenFailed[why: openFailure, fileName: fileName]; RETURN [AlpineOpen[retryFileName, accessOptions, createOptions, closeOptions, transaction, raw, createLength, streamBufferParms]] } }};--AlpineOpen StreamFromAlpineOpenFile: PUBLIC PROC [ fileHandle: REF ANY, fileName: ROPE, accessOptions: FileIO.AccessOptions, closeOptions: FileIO.CloseOptions, raw: FileIO.RawOption, streamBufferParms: FileIO.StreamBufferParms] RETURNS [stream: STREAM] = TRUSTED { openFailure: FileIO.OpenFailure; f: AlpFile.Handle = NARROW[fileHandle]; { -- block for failure exit PageContainingLastByte: PROC [byteLen: INT] RETURNS [INT] = TRUSTED INLINE { IF byteLen = 0 THEN RETURN[0] ELSE { byteLen _ byteLen - 1; LOOPHOLE[byteLen, I.LongNumber[num]].lowbits _ I.BITAND[LOOPHOLE[byteLen, I.LongNumber[num]].lowbits, clearLowBits]; RETURN[byteLen] }}; checkForTiogaFormat: BOOL = (NOT raw) AND accessOptions # overwrite; byteSize: ByteCount = GetFileSize[f]; byteLength: ByteCount = GetFileByteLength[f]; alpineData: AlpineDataHandle _ NEW[Data.alpine _ [ accessOptions: accessOptions, closeOptions: closeOptions, fileName: fileName, fileLength: byteLength, body: alpine[fileHandle: f, byteLength: byteLength, byteSize: byteSize]]]; stream _ IO.CreateProcsStream[ ProcHandleFromAccessOptions[IF checkForTiogaFormat THEN read ELSE accessOptions], alpineData]; IO.StoreData[self: stream, key: $Name, data: fileName]; -- for more intelligible debugging IF accessOptions # read THEN SetFileByteLength[f, byteLength]; -- to change create time CreateBufferSpace[alpineData]; SetupBuffer[alpineData: alpineData, fileByte: IF accessOptions = append THEN PageContainingLastByte[alpineData.fileLength] --get last byte of file into buffer ELSE 0]; --get first byte of file into buffer -- alpineData.index = 0 now. SELECT accessOptions FROM append => { -- assert alpineData.eofInBuffer (due to filePage used in SetupBuffer above). alpineData.index _ alpineData.dataBytesInBuffer }; overwrite => { alpineData.fileLength _ 0; alpineData.dataBytesInBuffer _ 0; alpineData.eofInBuffer _ TRUE; }; ENDCASE; IF checkForTiogaFormat THEN { isTioga: BOOL; len: INT; [yes: isTioga, len: len] _ FileIOPrivate.IsThisThingATiogaFile[stream]; IF isTioga THEN { IF accessOptions = read THEN { -- make length look changed by sneaky call to SetLength (not in h's stream procs). -- since stream is opened for read only, this call won't change the length in the file. setLength[stream, len]; alpineData.tiogaReader _ TRUE } ELSE { -- you can't incrementally update a Tioga file with IO! stream.Close[]; openFailure _ cantUpdateTiogaFile; GOTO failure } } ELSE { --NOT isTioga -- must set procs to correct value (they were read for benefit of IsThisThingATiogaFile). stream.streamProcs _ ProcHandleFromAccessOptions[accessOptions]; }; };--checkForTiogaFormat RETURN[stream]; EXITS failure => { retryFileName: ROPE = SIGNAL FileIO.OpenFailed[why: openFailure, fileName: fileName]; RETURN [AlpineOpen[fileName: retryFileName, accessOptions: accessOptions, closeOptions: closeOptions, transaction: f.trans, raw: raw, streamBufferParms: streamBufferParms, createOptions: none, createLength: 0]] } }};--StreamFromAlpineOpenFile -- 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] = TRUSTED { 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] }; c _ LOOPHOLE[selfData.buffer[selfData.index]]; selfData.index _ selfData.index + 1; RETURN[c] }; putChar: PROC [self: STREAM, char: CHAR] = TRUSTED { selfData: DataHandle = NARROW[self.streamData]; IF selfData.index = selfData.bufferBytes THEN AdvanceBuffer[selfData]; selfData.buffer[selfData.index] _ LOOPHOLE[char]; selfData.index _ selfData.index + 1; selfData.didPut _ TRUE }; getBlock: PROC [self: STREAM, block: REF TEXT, startIndex: NAT, stopIndexPlusOne: NAT] RETURNS [nBytesRead: NAT] = TRUSTED { selfData: DataHandle = NARROW[self.streamData]; textBlock: Environment.Block; countRemaining: NAT; -- Fail if startIndex<0 or stopIndexPlusOne<0. IF LOOPHOLE[I.BITOR[startIndex, stopIndexPlusOne], INTEGER] < 0 THEN ERROR Runtime.BoundsFault; -- Apply default on stopIndexPlusOne. stopIndexPlusOne _ MIN [block.maxLength, stopIndexPlusOne]; textBlock _ [ blockPointer: LOOPHOLE[block, LONG POINTER] + SIZE[TEXT[0]], startIndex: startIndex, stopIndexPlusOne: stopIndexPlusOne]; countRemaining _ IF startIndex > stopIndexPlusOne THEN 0 ELSE stopIndexPlusOne-startIndex; nBytesRead _ 0; WHILE countRemaining # 0 DO bufferBlock: Environment.Block _ [ blockPointer: selfData.buffer, startIndex: selfData.index, stopIndexPlusOne: selfData.dataBytesInBuffer]; countTransferred: CARDINAL _ ByteBlt.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, stopIndexPlusOne: NAT] = TRUSTED { selfData: DataHandle = NARROW[self.streamData]; -- Fail if startIndex<0 or stopIndexPlusOne<0. textBlock: Environment.Block; countRemaining: NAT; IF block = NIL THEN RETURN; IF LOOPHOLE[I.BITOR[startIndex, stopIndexPlusOne], INTEGER] < 0 THEN ERROR Runtime.BoundsFault; -- Apply default on stopIndexPlusOne. IF stopIndexPlusOne > block.maxLength THEN stopIndexPlusOne _ block.length; textBlock _ [ blockPointer: LOOPHOLE[block, LONG POINTER] + SIZE[TEXT[0]], startIndex: startIndex, stopIndexPlusOne: stopIndexPlusOne]; countRemaining _ IF startIndex > stopIndexPlusOne THEN 0 ELSE stopIndexPlusOne-startIndex; WHILE countRemaining # 0 DO bufferBlock: Environment.Block _ [ blockPointer: selfData.buffer, startIndex: selfData.index, stopIndexPlusOne: selfData.bufferBytes]; -- allow put past current eof. countTransferred: CARDINAL _ ByteBlt.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 + 1; -- 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: Environment.Block; IF block.startIndex < 0 OR block.stopIndexPlusOne < 0 THEN ERROR IO.Error[BadIndex, self]; IF block.startIndex >= block.stopIndexPlusOne 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; block.stopIndexPlusOne _ block.stopIndexPlusOne - wordOffset*bytesPerWord; }; nBytesRead _ 0; DO -- Transfer at most maxBytesMoved bytes from the stream to block^. -- Assert block.startIndex IN [0 .. maxStopIndexPlusOne), < block.stopIndexPlusOne countRemaining: CARDINAL; textBlock _ [ blockPointer: block.base, startIndex: block.startIndex, stopIndexPlusOne: MIN[maxStopIndexPlusOne, block.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: Environment.Block _ [ blockPointer: selfData.buffer, startIndex: selfData.index, stopIndexPlusOne: selfData.dataBytesInBuffer]; countTransferred: CARDINAL _ ByteBlt.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 = block.stopIndexPlusOne THEN GOTO return; -- Assert textBlock.stopIndexPlusOne = maxStopIndexPlusOne block.base _ block.base + maxWordsMoved; block.startIndex _ 0; block.stopIndexPlusOne _ block.stopIndexPlusOne - maxBytesMoved; ENDLOOP; EXITS return => RETURN [nBytesRead] }; unsafePutBlock: PROC [self: STREAM, block: IO.UnsafeBlock] = TRUSTED { selfData: DataHandle = NARROW[self.streamData]; textBlock: Environment.Block; IF block.startIndex < 0 OR block.stopIndexPlusOne < 0 THEN ERROR IO.Error[BadIndex, self]; IF block.startIndex >= block.stopIndexPlusOne THEN RETURN; 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; block.stopIndexPlusOne _ block.stopIndexPlusOne - wordOffset*bytesPerWord; }; DO -- Transfer at most maxBytesMoved bytes from block^ to the stream. -- Assert block.startIndex IN [0 .. maxStopIndexPlusOne), < block.stopIndexPlusOne countRemaining: CARDINAL; textBlock _ [ blockPointer: block.base, startIndex: block.startIndex, stopIndexPlusOne: MIN[maxStopIndexPlusOne, block.stopIndexPlusOne]]; countRemaining _ textBlock.stopIndexPlusOne - textBlock.startIndex; -- Assert countRemaining > 0 -- The following loop transfers textBlock^ to the stream. DO bufferBlock: Environment.Block _ [ blockPointer: selfData.buffer, startIndex: selfData.index, stopIndexPlusOne: selfData.bufferBytes]; -- allow put past current eof. countTransferred: CARDINAL _ ByteBlt.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 = block.stopIndexPlusOne THEN EXIT; -- Assert textBlock.stopIndexPlusOne = maxStopIndexPlusOne block.base _ block.base + maxWordsMoved; block.startIndex _ 0; block.stopIndexPlusOne _ block.stopIndexPlusOne - maxBytesMoved; ENDLOOP }; AdvanceBuffer: PROC [selfData: DataHandle] = TRUSTED { -- 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. alpineData: AlpineDataHandle = NARROW[selfData]; firstByteOfNextPage: INT = alpineData.firstFileByteInBuffer + alpineData.bufferBytes; changeSize: BOOL _ FALSE; IF firstByteOfNextPage = maxLength THEN ERROR IO.Error[FileTooLong, NIL]; CleanupAfterPut[alpineData]; IF firstByteOfNextPage >= alpineData.byteSize THEN { alpineData.byteSize _ NewByteSize[alpineData.byteSize]; SetFileSize[alpineData.fileHandle, alpineData.byteSize] }; SetupBuffer[alpineData: alpineData, fileByte: firstByteOfNextPage]; alpineData.index _ I.LowHalf[firstByteOfNextPage-alpineData.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] RETURNS [BOOL] = { RETURN[TRUE] }; 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] = TRUSTED { firstByte: INT _ index; byte: CARDINAL; --will contain index - firstByte alpineData: AlpineDataHandle = NARROW[self.streamData]; firstBufferByte: INT = alpineData.firstFileByteInBuffer; newIndex: CARDINAL; IF index < 0 THEN ERROR IO.Error[BadIndex, self]; LOOPHOLE[firstByte, I.LongNumber[num]].lowbits _ I.BITAND[LOOPHOLE[firstByte, I.LongNumber[num]].lowbits, clearLowBits]; -- firstByte contains index of first byte of file page containing byte "index" byte _ I.BITAND[LOOPHOLE[index, I.LongNumber[num]].lowbits, clearHighBits]; -- byte contains index - firstByte IF alpineData.didPut THEN { -- CleanupAfterPut[alpineData] alpineData.bufferDirty _ TRUE; IF alpineData.index > alpineData.dataBytesInBuffer THEN { alpineData.dataBytesInBuffer _ alpineData.index; alpineData.fileLength _ alpineData.firstFileByteInBuffer + alpineData.index }; alpineData.didPut _ FALSE }; -- ensure that page containing byte "index" is in the buffer IF firstByte NOT IN [firstBufferByte .. firstBufferByte+alpineData.bufferBytes) THEN { IF index > alpineData.fileLength THEN ERROR IO.EndOfStream[self]; IF alpineData.byteSize < firstBufferByte+alpineData.dataBytesInBuffer THEN { -- Analogous to AdvanceBuffer (bytes in buffer are not covered by file, but this --time we may be jumping away from the EOF.) alpineData.byteSize _ NewByteSize[alpineData.byteSize]; SetFileSize[alpineData.fileHandle, alpineData.byteSize] }; SetupBuffer[alpineData: alpineData, fileByte: firstByte]; }; newIndex _ I.LowHalf[firstByte-alpineData.firstFileByteInBuffer] + byte; IF newIndex > alpineData.dataBytesInBuffer THEN ERROR IO.EndOfStream[self]; alpineData.index _ newIndex }; reset: PROC [self: STREAM] = { setIndex[self, getLength[self]] }; flush: PROC [self: STREAM] = TRUSTED { alpineData: AlpineDataHandle = NARROW[self.streamData]; commitAndReopenTrans: BOOL = I.BITAND[alpineData.closeOptions, FileIO.commitAndReopenTransOnFlush] # 0; ForceOut[alpineData, IF commitAndReopenTrans THEN $continue ELSE $none, FALSE]; }; close: PROC [self: STREAM, abort: BOOL] = TRUSTED { alpineData: AlpineDataHandle = NARROW[self.streamData]; finishTrans: BOOL = I.BITAND[alpineData.closeOptions, FileIO.finishTransOnClose] # 0; ForceOut[alpineData, IF finishTrans THEN $complete ELSE $none, abort]; DeleteBufferSpace[alpineData]; alpineData.streamIsClosed _ TRUE; FileIOPrivate.Invalidate[self] }; -- 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-(bytesPerPage-1); clearHighBits: CARDINAL = (bytesPerPage-1); maxLength: INT = INT.LAST - bytesPerPage; setLength: PROC [self: STREAM, length: INT] = TRUSTED { -- Note: do not reduce the size of a shortened file until stream closed. RoundUpToPages: PROC [bytes: INT] RETURNS [INT] = TRUSTED INLINE { bytes _ bytes + (bytesPerPage-1); LOOPHOLE[bytes, I.LongNumber[num]].lowbits _ I.BITAND[LOOPHOLE[bytes, I.LongNumber[num]].lowbits, clearLowBits]; RETURN[bytes] }; alpineData: AlpineDataHandle = NARROW[self.streamData]; newFileBytes, firstBufferByte: INT; IF alpineData.streamIsClosed THEN ERROR IO.Error[StreamClosed, self]; IF length NOT IN [0 .. maxLength] THEN ERROR IO.Error[BadIndex, self]; newFileBytes _ RoundUpToPages[length]; IF alpineData.didPut THEN { -- CleanupAfterPut[alpineData] alpineData.bufferDirty _ TRUE; alpineData.didPut _ FALSE }; firstBufferByte _ alpineData.firstFileByteInBuffer; alpineData.fileLength _ length; SELECT TRUE FROM length > firstBufferByte + alpineData.bufferBytes => { -- new last byte of file is past end of current buffer. IF length > alpineData.byteSize THEN { --extend file to newFileBytes now alpineData.byteSize _ newFileBytes; SetFileSize[alpineData.fileHandle, alpineData.byteSize] }; alpineData.dataBytesInBuffer _ alpineData.bufferBytes; alpineData.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. alpineData.eofInBuffer _ TRUE; alpineData.dataBytesInBuffer _ length - firstBufferByte; alpineData.index _ MIN[alpineData.index, alpineData.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). alpineData.bufferDirty _ FALSE; --avoid redundant write if buffer dirty. SetupBuffer[alpineData: alpineData, fileByte: newFileBytes-bytesPerPage]; alpineData.index _ alpineData.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*bytesPerPage; CreateBufferSpace: PROC [alpineData: AlpineDataHandle] = TRUSTED { alpineData.bufferSpace _ Space.Create[1, Space.virtualMemory]; Space.Map[alpineData.bufferSpace]; alpineData.buffer _ Space.LongPointer[alpineData.bufferSpace]; alpineData.bufferBytes _ bytesPerPage }; DeleteBufferSpace: PROC [alpineData: AlpineDataHandle] = TRUSTED { Space.Delete[alpineData.bufferSpace]; alpineData.buffer _ NIL }; SetupBuffer: PROC [alpineData: AlpineDataHandle, fileByte: INT] = TRUSTED { -- 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 alpineData.bufferDirty THEN { WriteFilePage[f: alpineData.fileHandle, to: alpineData.firstFileByteInBuffer, from: alpineData.buffer]; alpineData.bufferDirty _ FALSE }; IF (bytesToRead _ I.LowHalf[MIN[alpineData.fileLength - fileByte, bytesPerPage]]) > 0 THEN ReadFilePage[f: alpineData.fileHandle, from: fileByte, to: alpineData.buffer]; alpineData.firstFileByteInBuffer _ fileByte; alpineData.eofInBuffer _ alpineData.fileLength <= (fileByte + bytesPerPage); alpineData.dataBytesInBuffer _ bytesToRead }; ForceOut: PROC [alpineData: AlpineDataHandle, commitAction: {none, complete, continue}, abort: BOOL] = TRUSTED { -- Called from Flush, Close. -- This is the only proc that sets byte length, and only proc that finishes trans. CleanupAfterPut[alpineData]; IF alpineData.bufferDirty THEN { WriteFilePage[f: alpineData.fileHandle, to: alpineData.firstFileByteInBuffer, from: alpineData.buffer]; alpineData.bufferDirty _ FALSE }; IF alpineData.fileLength # alpineData.byteLength AND NOT alpineData.tiogaReader THEN { alpineData.byteLength _ alpineData.fileLength; SetFileByteLength[alpineData.fileHandle, alpineData.byteLength] }; IF commitAction # $none THEN Finish[ f: alpineData.fileHandle, requestedOutcome: IF abort THEN $abort ELSE $commit, continue: commitAction = $continue]; }; -- Talking to Alpine (catch phrases galore ... ) ReadFilePage: PROC [f: REF ANY, from: ByteNumber, to: LONG POINTER] = TRUSTED { fileHandle: AlpFile.Handle = NARROW[f]; p: PageNumber = from/bytesPerPage; { ENABLE BEGIN AlpFile.Unknown => SELECT what FROM transID, openFileID => ERROR FileIOAlpine.Aborted[fileHandle.trans]; ENDCASE => REJECT; RPC.CallFailed => IF why IN [timeout .. busy] THEN ERROR FileIOAlpine.Failure[ $communications, fileHandle.trans.inst.fileStore]; END; fileHandle.ReadPages[ pageRun: [firstPage: p], pageBuffer: DESCRIPTOR [to, wordsPerPage]]; }}; WriteFilePage: PROC [f: REF ANY, to: ByteNumber, from: LONG POINTER] = TRUSTED { fileHandle: AlpFile.Handle = NARROW[f]; p: PageNumber = to/bytesPerPage; { ENABLE BEGIN AlpFile.Unknown => SELECT what FROM transID, openFileID => ERROR FileIOAlpine.Aborted[fileHandle.trans]; ENDCASE => REJECT; RPC.CallFailed => IF why IN [timeout .. busy] THEN ERROR FileIOAlpine.Failure[ $communications, fileHandle.trans.inst.fileStore]; END; fileHandle.WritePages[ pageRun: [firstPage: p], pageBuffer: DESCRIPTOR [from, wordsPerPage]]; }}; SetFileSize: PROC [f: REF ANY, byteSize: ByteCount] = TRUSTED { fileHandle: AlpFile.Handle = NARROW[f]; { ENABLE BEGIN AlpFile.Unknown => SELECT what FROM transID, openFileID => ERROR FileIOAlpine.Aborted[fileHandle.trans]; ENDCASE => REJECT; RPC.CallFailed => IF why IN [timeout .. busy] THEN ERROR FileIOAlpine.Failure[ $communications, fileHandle.trans.inst.fileStore]; END; fileHandle.SetSize[byteSize/bytesPerPage]; }}; GetFileSize: PROC [f: REF ANY] RETURNS [ByteCount] = TRUSTED { fileHandle: AlpFile.Handle = NARROW[f]; { ENABLE BEGIN AlpFile.Unknown => SELECT what FROM transID, openFileID => ERROR FileIOAlpine.Aborted[fileHandle.trans]; ENDCASE => REJECT; RPC.CallFailed => IF why IN [timeout .. busy] THEN ERROR FileIOAlpine.Failure[ $communications, fileHandle.trans.inst.fileStore]; END; RETURN [fileHandle.GetSize[]*bytesPerPage]; }}; SetFileByteLength: PROC [f: REF ANY, byteLength: ByteCount] = TRUSTED { fileHandle: AlpFile.Handle = NARROW[f]; now: System.GreenwichMeanTime = System.GetGreenwichMeanTime[]; { ENABLE BEGIN AlpFile.Unknown => SELECT what FROM transID, openFileID => ERROR FileIOAlpine.Aborted[fileHandle.trans]; ENDCASE => REJECT; RPC.CallFailed => IF why IN [timeout .. busy] THEN ERROR FileIOAlpine.Failure[ $communications, fileHandle.trans.inst.fileStore]; END; fileHandle.WriteProperties[properties: LIST[[byteLength[byteLength: byteLength]], [createTime[createTime: now]]]]; }}; GetFileByteLength: PROC [f: REF ANY] RETURNS [ByteCount] = TRUSTED { fileHandle: AlpFile.Handle = NARROW[f]; { ENABLE BEGIN AlpFile.Unknown => SELECT what FROM transID, openFileID => ERROR FileIOAlpine.Aborted[fileHandle.trans]; ENDCASE => REJECT; RPC.CallFailed => IF why IN [timeout .. busy] THEN ERROR FileIOAlpine.Failure[ $communications, fileHandle.trans.inst.fileStore]; END; byteLengthProperty: LIST OF AlpineEnvironment.PropertyValuePair = fileHandle.ReadProperties[[byteLength: TRUE]]; RETURN [NARROW[byteLengthProperty.first, AlpineEnvironment.PropertyValuePair.byteLength].byteLength]; }}; Finish: PROC [f: REF ANY, requestedOutcome: AlpineEnvironment.CommitOrAbort, continue: BOOL] = { fileHandle: AlpFile.Handle = NARROW[f]; { ENABLE BEGIN RPC.CallFailed => IF why IN [timeout .. busy] THEN ERROR FileIOAlpine.Failure[ $communications, fileHandle.trans.inst.fileStore]; END; outcome: AlpineEnvironment.Outcome = fileHandle.trans.Finish[ requestedOutcome: requestedOutcome, continue: continue]; IF NOT (requestedOutcome = $abort) AND outcome = $abort THEN ERROR FileIOAlpine.Aborted[fileHandle.trans]; }}; -- Procedure records (never modified) alpineFileIOReadProcs: ProcHandle = IO.CreateRefStreamProcs[ getChar: getChar, endOf: endOf, charsAvail: charsAvail, getBlock: getBlock, unsafeGetBlock: unsafeGetBlock, putChar: FileIOPrivate.PutCharDisallowed, putBlock: FileIOPrivate.PutBlockDisallowed, unsafePutBlock: FileIOPrivate.UnsafePutBlockDisallowed, flush: flush, reset: reset, close: close, getIndex: getIndex, setIndex: setIndex, backup: backup, getLength: getLength, name: "ReadOnly Alpine File" ]; alpineFileIOAppendProcs: ProcHandle = IO.CreateRefStreamProcs[ getChar: FileIOPrivate.GetCharDisallowed, endOf: endOf, charsAvail: charsAvail, getBlock: FileIOPrivate.GetBlockDisallowed, unsafeGetBlock: FileIOPrivate.UnsafeGetBlockDisallowed, putChar: putChar, putBlock: putBlock, unsafePutBlock: unsafePutBlock, flush: flush, reset: reset, close: close, getIndex: getIndex, setIndex: FileIOPrivate.SetIndexDisallowed, getLength: getLength, name: "AppendOnly Alpine File" ]; alpineFileIOAllProcs: ProcHandle = IO.CreateRefStreamProcs[ 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, name: "Read/Write Alpine File" ]; ProcHandleFromAccessOptions: ARRAY FileIO.AccessOptions OF ProcHandle = [ read: alpineFileIOReadProcs, append: alpineFileIOAppendProcs, write: alpineFileIOAllProcs, overwrite: alpineFileIOAllProcs]; END. CHANGE LOG Created by MBrown on May 10, 1983 8:36 pm -- By editing FileIOJuniperImpl Changed by MBrown on June 3, 1983 4:46 pm -- Change create time of non-readonly file on Open, and whenever byte length changes.