-- FileIOPilotImpl.mesa -- Last Edited by -- MBrown on May 16, 1983 3:44 pm -- WTeitelman on February 3, 1983 12:14 pm -- note: "size" refers to pages, "length" to bytes. DIRECTORY ByteBlt USING [ByteBlt], CIFS USING [Close, OpenFile, Error, GetFC, GetPathname, OpenButDontHandleError, read, dontCheck], DCSFileTypes USING [tLeaderPage], Directory USING [maxPathNameLength, CreateFile, Error, Lookup, ignore, PutProperty], Environment USING [Block, Byte, bytesPerPage, bytesPerWord, PageCount, wordsPerPage], File USING [ Capability, nullCapability, GetAttributes, GetSize, grow, PageCount, Permissions, read, SetSize, shrink, write, Unknown], FileIO USING [AccessOptions, CreateOptions, CloseOptions, commitAndReopenTransOnFlush, finishTransOnClose, truncatePagesOnClose, RawOption, StreamBufferParms, OpenFailed, OpenFailure, Open], FileIOPrivate USING [Data, DataHandle, PilotDataHandle, PutCharDisallowed, PutBlockDisallowed, GetBlockDisallowed, UnsafePutBlockDisallowed, GetCharDisallowed, UnsafeGetBlockDisallowed, SetIndexDisallowed, maxLeaderNameCharacters, IsThisThingATiogaFile, LeaderPage, Invalidate, leaderVersionID], Inline USING [BITAND, BITOR, LongCOPY, LongNumber], IO USING [Close, UnsafeBlock, Error, EndOfStream, CreateProcsStream, CreateRefStreamProcs, StoreData, StreamProcs, STREAM], PropertyTypes USING [tByteLength], Rope USING [Fetch, ROPE, Length, SkipTo], RopeInline USING [InlineFlatten], Runtime USING [BoundsFault], Space USING [ CopyIn, CopyOut, Create, CreateUniformSwapUnits, Delete, ForceOut, Handle, Kill, LongPointer, Map, Unmap, virtualMemory], System USING [GetGreenwichMeanTime, GreenwichMeanTime, gmtEpoch], Transaction USING [Abort, Begin, Commit, Handle, nullHandle]; FileIOPilotImpl: CEDAR PROGRAM IMPORTS ByteBlt, CIFS, Directory, File, FileIO, FileIOPrivate, IO, I: Inline, Rope, RopeInline, Runtime, Space, System, Transaction EXPORTS FileIO, FileIOPrivate SHARES File, --to access capability.permissions IO = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Data: TYPE = FileIOPrivate.Data; DataHandle: TYPE = FileIOPrivate.DataHandle; PilotDataHandle: TYPE = FileIOPrivate.PilotDataHandle; ProcHandle: TYPE = REF IO.StreamProcs; bytesPerPage: CARDINAL = Environment.bytesPerPage; bytesPerWord: CARDINAL = Environment.bytesPerWord; -- Stream creation ComSoftOpen: PUBLIC PROC [ fileName: ROPE, accessOptions: FileIO.AccessOptions, createOptions: FileIO.CreateOptions, closeOptions: FileIO.CloseOptions, transaction: Transaction.Handle, raw: FileIO.RawOption, createLength: INT, streamBufferParms: FileIO.StreamBufferParms] RETURNS [STREAM] = TRUSTED { -- FileIOPrivate.ComSoftOpen openFailure: FileIO.OpenFailure; { -- block for failure exit FileNameOK: PROC [fileName: ROPE] RETURNS [ok: BOOL] = CHECKED { -- Make sure that full fileName (including subdirs) is not too long, and that the final part (excluding subdirs) is not too long either. ComSoft Directory should do its own checking, but it does not. l: INT _ fileName.Length[]; p: INT _ 0; --index+1 of current ">" found IF l > Directory.maxPathNameLength THEN RETURN [ok: FALSE]; DO t: INT; IF (t _ fileName.SkipTo[p, ">"]) = l THEN EXIT; p _ t+1; ENDLOOP; IF l-p > FileIOPrivate.maxLeaderNameCharacters THEN RETURN [ok: FALSE]; RETURN [ok: TRUE] }; file: File.Capability _ File.nullCapability; fileName _ RopeInline.InlineFlatten[fileName]; IF NOT FileNameOK[fileName] THEN { openFailure _ illegalFileName; GOTO failure }; file _ Directory.Lookup[fileName: LOOPHOLE[fileName], permissions: Directory.ignore ! Directory.Error => IF type = fileNotFound THEN CONTINUE ELSE { openFailure _ illegalFileName; GOTO failure }]; IF file = File.nullCapability THEN { -- file was not found; create it? IF createOptions = oldOnly THEN { openFailure _ fileNotFound; GOTO failure } ELSE { -- createOptions = none or newOnly file _ Directory.CreateFile[ fileName: LOOPHOLE[fileName], fileType: DCSFileTypes.tLeaderPage, size: RoundUpToPages[createLength, NIL]/bytesPerPage]; -- We don't expect a directory error here since we already did a lookup --using the same name. We might run out of disk space, however; is --Volume.InsufficientSpace raised? IF createLength > 0 THEN { fileLength: LONG CARDINAL _ 0; Directory.PutProperty[file: file, property: PropertyTypes.tByteLength, propertyValue: DESCRIPTOR[@fileLength, SIZE[LONG CARDINAL]]]; }; }; } ELSE { -- file was found. IF createOptions = newOnly THEN { openFailure _ fileAlreadyExists; GOTO failure }; }; RETURN[StreamFromCapability[ file, accessOptions, closeOptions, fileName, transaction, raw, streamBufferParms ! FileIO.OpenFailed => CHECKED { openFailure _ why; GOTO failure }]]; EXITS failure => { retryFileName: ROPE = SIGNAL FileIO.OpenFailed[why: openFailure, fileName: fileName]; RETURN [FileIO.Open[retryFileName, accessOptions, createOptions, closeOptions, [pilot[transaction]], raw, createLength, streamBufferParms]] } }};--ComSoftOpen CIFSOpen: PUBLIC PROC [ fileName: ROPE, accessOptions: FileIO.AccessOptions, createOptions: FileIO.CreateOptions, closeOptions: FileIO.CloseOptions, transaction: Transaction.Handle, raw: FileIO.RawOption, createLength: INT, streamBufferParms: FileIO.StreamBufferParms] RETURNS [STREAM] = { -- FileIOPrivate.CIFSOpen openFailure: FileIO.OpenFailure; { -- block for failure exit openFile: CIFS.OpenFile; IF accessOptions # read THEN { openFailure _ notImplementedYet; GOTO failure }; openFile _ CIFS.OpenButDontHandleError[name: fileName, mode: CIFS.read+CIFS.dontCheck ! CIFS.Error => SELECT code FROM illegalFileName => { openFailure _ illegalFileName; GOTO failure }; noSuchHost, noSuchFile => { openFailure _ fileNotFound; GOTO failure }; ENDCASE => REJECT ]; -- We expect many errors on this call but do not know how to handle most of them yet. RETURN [StreamFromOpenFile[ openFile, accessOptions, closeOptions, transaction, raw, streamBufferParms ! FileIO.OpenFailed => { openFailure _ why; GOTO failure }]]; EXITS failure => { retryFileName: ROPE = SIGNAL FileIO.OpenFailed[why: openFailure, fileName: fileName]; RETURN [FileIO.Open[retryFileName, accessOptions, createOptions, closeOptions, [pilot[transaction]], raw, createLength, streamBufferParms]] } }};--CIFSOpen StreamFromOpenFile: PUBLIC PROC [ openFile: CIFS.OpenFile, accessOptions: FileIO.AccessOptions, closeOptions: FileIO.CloseOptions, transaction: Transaction.Handle, raw: FileIO.RawOption, streamBufferParms: FileIO.StreamBufferParms] RETURNS [STREAM] = { -- FileIO.StreamFromOpenFile openFailure: FileIO.OpenFailure; { -- block for failure exit stream: STREAM; IF accessOptions # read THEN { openFailure _ notImplementedYet; GOTO failure }; stream _ StreamFromCapability[ CIFS.GetFC[openFile], accessOptions, closeOptions, CIFS.GetPathname[openFile], transaction, raw, streamBufferParms]; -- Do not catch errors. NARROW[stream.streamData, PilotDataHandle].openFile _ openFile; RETURN [stream]; EXITS failure => { retryFileName: ROPE = SIGNAL FileIO.OpenFailed[why: openFailure, fileName: CIFS.GetPathname[openFile]]; RETURN [FileIO.Open[fileName: retryFileName, accessOptions: accessOptions, closeOptions: closeOptions, transaction: [pilot[transaction]], raw: raw, streamBufferParms: streamBufferParms]] } }};--StreamFromOpenFile StreamFromCapability: PUBLIC PROC [ capability: File.Capability, accessOptions: FileIO.AccessOptions, closeOptions: FileIO.CloseOptions, fileName: ROPE, transaction: Transaction.Handle, raw: FileIO.RawOption, streamBufferParms: FileIO.StreamBufferParms] RETURNS [STREAM] = { -- FileIO.StreamFromCapability openFailure: FileIO.OpenFailure; -- cantUpdateTiogaFile, unknownFileCapability { stream: STREAM; checkForTiogaFormat: BOOL = (NOT raw) AND accessOptions # overwrite; pilotData: PilotDataHandle; needPermissions: File.Permissions = SELECT accessOptions FROM read => File.read, append => File.read+File.write+File.grow, write, overwrite => File.read+File.write+File.grow+File.shrink ENDCASE => ERROR; capability.permissions _ needPermissions; pilotData _ NEW[pilot Data _ [ accessOptions: accessOptions, closeOptions: closeOptions, fileName: fileName, body: pilot[file: capability, trans: transaction]]]; stream _ IO.CreateProcsStream[ ProcHandleFromAccessOptions[IF checkForTiogaFormat THEN read ELSE accessOptions], pilotData]; IO.StoreData[self: stream, key: $Name, data: fileName]; -- for more intelligible debugging IF InitLeader[pilotData].failed THEN { -- The InitLeader call is our first attempt to touch the file. Failure means that --it does not exist. openFailure _ unknownFileCapability; GOTO failure }; CreateBuffer[pilotData: pilotData, bufferParms: streamBufferParms]; SetupBuffer[pilotData: pilotData, fileByte: IF accessOptions = append THEN PageContainingLastByte[pilotData.fileLength] ELSE 0, option: init]; -- pilotData.index = 0 now. SELECT accessOptions FROM append => { -- assert pilotData.eofInBuffer (due to fileByte used in SetupBuffer above). pilotData.index _ pilotData.dataBytesInBuffer }; overwrite => { pilotData.fileLength _ 0; pilotData.dataBytesInBuffer _ 0; pilotData.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]; pilotData.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 [FileIO.Open[fileName: retryFileName, accessOptions: accessOptions, closeOptions: closeOptions, transaction: [pilot[transaction]], raw: raw, streamBufferParms: streamBufferParms]] } }};--StreamFromCapability CapabilityFromStream: PUBLIC PROC [self: STREAM] RETURNS [File.Capability] = { -- FileIO.CapabilityFromStream WITH self.streamData SELECT FROM pilotData: PilotDataHandle => RETURN [pilotData.file]; 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 _ LOOPHOLE[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] _ 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]; textBlock: Environment.Block; countRemaining: NAT; -- Fail if startIndex<0 or stopIndexPlusOne<0. 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 }; 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 { fileBytes: INT _ index; --will contain index of first byte of page containing byte i byte: CARDINAL; --will contain index - fileBytes pilotData: PilotDataHandle = NARROW[self.streamData]; firstBufferByte: INT = pilotData.firstFileByteInBuffer; newIndex: CARDINAL; IF index < 0 THEN ERROR IO.Error[BadIndex, self]; LOOPHOLE[fileBytes, I.LongNumber[num]].lowbits _ --clear low bits of fileBytes I.BITAND[LOOPHOLE[fileBytes, I.LongNumber[num]].lowbits, clearLowBits]; byte _ I.BITAND[LOOPHOLE[index, I.LongNumber[num]].lowbits, clearHighBits]; -- CleanupAfterPut[selfData]; here we must do the real thing! IF pilotData.didPut THEN { IF pilotData.index > pilotData.dataBytesInBuffer THEN { pilotData.dataBytesInBuffer _ pilotData.index; pilotData.fileLength _ pilotData.firstFileByteInBuffer + pilotData.index }; pilotData.didPut _ FALSE }; -- ensure that page containing byte i is in the buffer IF fileBytes NOT IN[firstBufferByte..firstBufferByte+pilotData.bufferBytes) THEN { IF index > pilotData.fileLength THEN ERROR IO.EndOfStream[self]; SetupBuffer[pilotData: pilotData, fileByte: FirstBufferByteFromFileByte[pilotData, fileBytes]]; }; newIndex _ fileBytes-pilotData.firstFileByteInBuffer + byte; IF newIndex > pilotData.dataBytesInBuffer THEN ERROR IO.EndOfStream[self]; pilotData.index _ newIndex }; flush: PROC [self: STREAM] = TRUSTED { pilotData: PilotDataHandle = NARROW[self.streamData]; commitAndReopenTrans: BOOL = I.BITAND[pilotData.closeOptions, FileIO.commitAndReopenTransOnFlush] # 0; CleanupAfterPut[pilotData]; ForceOutStreamUpdates[pilotData]; IF commitAndReopenTrans AND pilotData.trans # Transaction.nullHandle THEN { Transaction.Commit[pilotData.trans]; pilotData.trans _ Transaction.Begin[]}; }; reset: PROC [self: STREAM] = { selfData: DataHandle = NARROW[self.streamData]; setIndex[self, getLength[self]] }; close: PROC [self: STREAM, abort: BOOL] = TRUSTED { pilotData: PilotDataHandle = NARROW[self.streamData]; truncateFile: BOOL = I.BITAND[pilotData.closeOptions, FileIO.truncatePagesOnClose] # 0; finishTrans: BOOL = I.BITAND[pilotData.closeOptions, FileIO.finishTransOnClose] # 0; CleanupAfterPut[pilotData]; IF NOT abort THEN ForceOutStreamUpdates[pilotData] ELSE Space.Kill[pilotData.bufferSpace]; Space.Delete[pilotData.bufferSpace]; CloseLeader[pilotData]; IF truncateFile AND (pilotData.accessOptions IN [write..overwrite]) THEN { newFileBytes: INT _ RoundUpToPages[pilotData.fileLength, self]; IF newFileBytes < pilotData.fileBytes THEN SetFileSize[pilotData, newFileBytes/bytesPerPage]; }; IF finishTrans AND pilotData.trans # Transaction.nullHandle THEN { IF abort THEN Transaction.Abort[pilotData.trans] ELSE Transaction.Commit[pilotData.trans] }; IF pilotData.openFile # NIL THEN { CIFS.Close[pilotData.openFile]; pilotData.openFile _ NIL; }; pilotData.streamIsClosed _ TRUE; FileIOPrivate.Invalidate[self] }; clearLowBits: CARDINAL = LAST[CARDINAL]-(bytesPerPage-1); clearHighBits: CARDINAL = (bytesPerPage-1); PageContainingLastByte: PROC [bytes: INT] RETURNS [INT] = TRUSTED INLINE { -- The index of the first byte of the last page of a file of length bytes. SELECT bytes FROM < 0 => ERROR; = 0 => RETURN[0]; ENDCASE => { bytes _ bytes - 1; LOOPHOLE[bytes, I.LongNumber[num]].lowbits _ I.BITAND[LOOPHOLE[bytes, I.LongNumber[num]].lowbits, clearLowBits]; RETURN[bytes] }}; RoundUpToPages: PROC [bytes: INT, stream: STREAM] RETURNS [INT] = TRUSTED { -- The minimum number of bytes in the pages of a file of length bytes. bytes _ bytes + (bytesPerPage-1); --overflow if l > maxFileBytes IF bytes < 0 THEN ERROR IO.Error[FileTooLong, stream]; LOOPHOLE[bytes, I.LongNumber[num]].lowbits _ I.BITAND[LOOPHOLE[bytes, I.LongNumber[num]].lowbits, clearLowBits]; RETURN[bytes] }; -- 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 and 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] }; setLength: PROC [self: STREAM, length: INT] = TRUSTED { pilotData: PilotDataHandle = NARROW[self.streamData]; newFileBytes, firstBufferByte: INT; IF pilotData.streamIsClosed THEN ERROR IO.Error[StreamClosed, self]; -- setLength is relatively complicated, for the following reason: a mapped file's --size cannot be increased while any space is mapped to the file such that the end --of file is properly contained in the space. -- note that we do not reduce the size of a shortened file until the stream is --closed. IF length < 0 THEN ERROR IO.Error[BadIndex, self]; newFileBytes _ RoundUpToPages[length, self]; -- special CleanupAfterPut[pilotData]; we don't care about fileLength or --dataBytesInBuffer. pilotData.didPut _ FALSE; firstBufferByte _ pilotData.firstFileByteInBuffer; pilotData.fileLength _ length; SELECT TRUE FROM newFileBytes > firstBufferByte + pilotData.bufferBytes => { -- new last byte of file is past end of current buffer. IF newFileBytes > pilotData.fileBytes THEN { pilotData.fileBytes _ newFileBytes; IF pilotData.eofInBuffer THEN SetupBuffer[pilotData: pilotData, fileByte: pilotData.firstFileByteInBuffer, option: setSize] ELSE SetFileSize[pilotData, newFileBytes/bytesPerPage] }; pilotData.dataBytesInBuffer _ pilotData.bufferBytes; pilotData.eofInBuffer _ FALSE }; newFileBytes > 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. pilotData.eofInBuffer _ TRUE; pilotData.dataBytesInBuffer _ length - firstBufferByte; IF pilotData.index > pilotData.dataBytesInBuffer THEN pilotData.index _ pilotData.dataBytesInBuffer }; ENDCASE => { -- new last byte of file precedes current buffer. (a special case of this, l --= 0 and firstBufferPage = 0, was already handled above without a --SetupBuffer call). SetupBuffer[ pilotData: pilotData, fileByte: FirstBufferByteFromFileByte[pilotData, newFileBytes]]; pilotData.index _ pilotData.dataBytesInBuffer } };--setLength 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] = { index: INT = 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 = 10; --if we ever need to grow file, we grow it by this many pages. growIncrementBytes: CARDINAL = growIncrement*bytesPerPage; maxFileBytes: INT = LAST[INT] - (bytesPerPage-1); FirstBufferByteFromFileByte: PROC [pilotData: PilotDataHandle, i: INT] RETURNS [INT] = INLINE { -- Returns the "correct" choice of first byte in buffer, given that byte i should be --accessible. This choice avoids a thrash if we do a putback on a page boundary. RETURN[MAX[i,pilotData.bufferSwapUnitLength]-pilotData.bufferSwapUnitLength] }; AdvanceBuffer: PROC [selfData: DataHandle] = { -- On entry, index = dataBytesInBuffer = bufferBytes. Exit with same position in --file, but index < dataBytesInBuffer or EOF. -- Called from getChar, putChar, getBlock, putBlock. pilotData: PilotDataHandle = NARROW[selfData]; firstByteOfNextPage: INT = pilotData.firstFileByteInBuffer + pilotData.bufferBytes; changeSize: BOOL _ FALSE; IF firstByteOfNextPage = maxFileBytes THEN ERROR IO.Error[FileTooLong, NIL]; CleanupAfterPut[pilotData]; IF firstByteOfNextPage >= pilotData.fileBytes THEN { pilotData.fileBytes _ MIN[pilotData.fileBytes + growIncrementBytes, maxFileBytes]; changeSize _ TRUE}; SetupBuffer[pilotData: pilotData, fileByte: FirstBufferByteFromFileByte[pilotData, firstByteOfNextPage], option: IF changeSize THEN setSize ELSE none]; pilotData.index _ firstByteOfNextPage-selfData.firstFileByteInBuffer }; CreateBuffer: PROC [ pilotData: PilotDataHandle, bufferParms: FileIO.StreamBufferParms] = { -- Creates buffer during stream creation. Modifies bufferParms request as necessary. bSize: NAT _ bufferParms.bufferSize; sSize: NAT _ bufferParms.bufferSwapUnitSize; bSize _ MIN [MAX [2, bSize], 127]; sSize _ MIN [MAX [1, sSize], 32, bSize/2]; bSize _ bSize - bSize MOD sSize; pilotData.bufferLength _ bSize * bytesPerPage; pilotData.bufferSwapUnitLength _ sSize * bytesPerPage; TRUSTED { pilotData.bufferSpace _ Space.Create[bSize, Space.virtualMemory]; Space.CreateUniformSwapUnits[sSize, pilotData.bufferSpace]; pilotData.buffer _ Space.LongPointer[pilotData.bufferSpace] }; }; SetupBuffer: PROC [ pilotData: PilotDataHandle, fileByte: INT, option: {init, setSize, none} _ none] = TRUSTED { -- didPut = FALSE on entry (someone else called CleanupAfterPut). Ignores value of --dataBytesInBuffer, uses fileLength. -- Arranges buffer so that fileByte (must be page-aligned) is the first page in it. --Forces all changes to propogate to file asynchronously. --Does not force any changes to leader page. If setSize, sets --file size to pilotData.fileBytes / bytesPerPage pages while the buffer space is --unmapped. -- Maintains invariants of eofInBuffer, dataBytesInBuffer, bufferBytes, and --firstFilePageInBuffer in the face of all this. DOES NOT update index. -- Called from HandleFromCapability, setLength, setIndex, AdvanceBuffer. filePage: INT = fileByte / bytesPerPage; IF option # init THEN Space.Unmap[pilotData.bufferSpace]; IF option = setSize THEN SetFileSize[pilotData, pilotData.fileBytes / bytesPerPage]; TRUSTED { Space.Map[space: pilotData.bufferSpace, window: [pilotData.file, filePage + pilotData.dataOffset], transaction: pilotData.trans] }; IF pilotData.eofInBuffer _ (pilotData.fileLength <= fileByte + pilotData.bufferLength) THEN pilotData.dataBytesInBuffer _ pilotData.fileLength - fileByte ELSE pilotData.dataBytesInBuffer _ pilotData.bufferLength; pilotData.bufferBytes _ MIN[(pilotData.fileBytes - fileByte), pilotData.bufferLength]; pilotData.firstFileByteInBuffer _ fileByte }; ForceOutStreamUpdates: PROC [pilotData: PilotDataHandle] = TRUSTED { -- Force all changes from buffer to file, including current byte length to leader --page. Called from flush, close. Safe to call anytime that length-related --state in DataObject is consistent (e.g. lengthChanged, firstFilePageInBuffer, --dataBytesInBuffer). Space.ForceOut[pilotData.bufferSpace]; SetLengthInLeader[pilotData] }; -- LeaderPage operations InitLeader: PROC [pilotData: PilotDataHandle] RETURNS [failed: BOOL] = TRUSTED { -- Reads leader page (creates one if file type is right and no leader present). -- If accessOptions # read, updates dates in leader page and writes changed page to file. -- Inits filePages, fileBytes (also dataOffset, leaderSpace, leader) in pilotData. hasLeader: [0..1] = IF File.GetAttributes[pilotData.file, pilotData.trans ! File.Unknown => GOTO failureReturn].type = DCSFileTypes.tLeaderPage THEN 1 ELSE 0; rawFilePages: INT _ File.GetSize[file: pilotData.file, transaction: pilotData.trans]; fileHadNoPages: BOOL _ FALSE; pilotData.dataOffset _ hasLeader; IF rawFilePages = 0 THEN { fileHadNoPages _ TRUE; rawFilePages _ growIncrement + hasLeader; SetFileSize[pilotData, growIncrement] }; pilotData.fileBytes _ (rawFilePages - hasLeader)*bytesPerPage; IF hasLeader # 0 THEN TRUSTED { -- file is supposed to have a leader page. leader might need to be initialized if --the file is newly allocated or if leader is trashed. -- leader is always updated now (to set either read or create date). now: System.GreenwichMeanTime = System.GetGreenwichMeanTime[]; leaderSpace: Space.Handle = Space.Create[1, Space.virtualMemory]; leader: LONG POINTER TO FileIOPrivate.LeaderPage = Space.LongPointer[leaderSpace]; Space.Map[leaderSpace]; Space.CopyIn[space: leaderSpace, window: [pilotData.file, 0]]; IF leader.versionID # FileIOPrivate.leaderVersionID THEN { -- rewrite leader, setting length to full length of the file unless --fileHadNoPages. Zero[leader, Environment.wordsPerPage]; leader.versionID _ FileIOPrivate.leaderVersionID; leader.length _ IF fileHadNoPages THEN 0 ELSE pilotData.fileBytes; IF pilotData.fileName # NIL THEN { destination: LONG POINTER TO PACKED ARRAY [0..FileIOPrivate.maxLeaderNameCharacters) OF CHAR _ @leader.name; size: INT _ MIN[pilotData.fileName.Length[], FileIOPrivate.maxLeaderNameCharacters]; leader.nameLength _ LOOPHOLE[size, I.LongNumber[num]].lowbits; FOR i: CARDINAL IN [0..leader.nameLength) DO destination[i] _ pilotData.fileName.Fetch[i]; ENDLOOP; } }--rewrite leader ELSE IF leader.length > pilotData.fileBytes THEN TRUSTED { -- inconsistent length in leader: rewrite length to full length of the file. leader.length _ pilotData.fileBytes }; pilotData.fileLength _ leader.length; pilotData.leaderSpace _ leaderSpace; pilotData.leader _ leader; IF pilotData.accessOptions = read THEN CloseLeader[pilotData] ELSE { leader.create _ leader.write _ now; IF pilotData.accessOptions # append THEN leader.read _ now ELSE leader.read _ System.gmtEpoch; -- appending to a file creates a version that nobody has read; approximate this --with the oldest expressible time. Space.CopyOut[space: leaderSpace, window: [[pilotData.file.fID, File.read+File.write], 0], transaction: pilotData.trans]; }; } ELSE { -- file has no leader page. muddle on without. pilotData.fileLength _ IF fileHadNoPages THEN 0 ELSE pilotData.fileBytes }; RETURN [failed: FALSE]; EXITS failureReturn => RETURN [failed: TRUE] }; CloseLeader: PROC [pilotData: PilotDataHandle] = TRUSTED { IF pilotData.leader # NIL THEN { Space.Delete[pilotData.leaderSpace]; pilotData.leader _ NIL } }; SetLengthInLeader: UNSAFE PROC [pilotData: PilotDataHandle] = UNCHECKED { -- Sets leader page file length from stream, and writes change to file. -- Called from ForceOutStreamUpdates. IF pilotData.leader # NIL AND pilotData.leader.length # pilotData.fileLength THEN { pilotData.leader.length _ pilotData.fileLength; pilotData.leader.create _ System.GetGreenwichMeanTime[]; Space.CopyOut[ space: pilotData.leaderSpace, window: [[pilotData.file.fID, File.read+File.write], 0], transaction: pilotData.trans]; } }; SetFileSize: PROC [pilotData: PilotDataHandle, newFilePages: INT] = TRUSTED { -- newFilePages is new file size, expressed in "data pages" (ignores leader). -- Called from setLength, close, InitLeader, SetupBuffer. File.SetSize[file: pilotData.file, size: newFilePages + pilotData.dataOffset, transaction: pilotData.trans] }; Zero: PROC [p: LONG POINTER, n: CARDINAL] = TRUSTED INLINE { IF n = 0 THEN RETURN; p^ _ 0; I.LongCOPY[from: p, to: p+1, nwords: n-1] }; -- Procedure records (never modified) pilotFileIOReadProcs: 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, getLength: getLength, backup: backup, name: "ReadOnly File" ]; pilotFileIOAppendProcs: ProcHandle = IO.CreateRefStreamProcs[ getChar: FileIOPrivate.GetCharDisallowed, endOf: endOf, charsAvail: charsAvail, getBlock: FileIOPrivate.GetBlockDisallowed, unsafeGetBlock: FileIOPrivate.UnsafeGetBlockDisallowed, putChar: putChar, putBlock: putBlock, unsafePutBlock: unsafePutBlock, flush: flush, eraseChar: eraseChar, reset: reset, close: close, getIndex: getIndex, setIndex: FileIOPrivate.SetIndexDisallowed, getLength: getLength, name: "AppendOnly File" ]; pilotFileIOAllProcs: ProcHandle = IO.CreateRefStreamProcs[ getChar: getChar, endOf: endOf, charsAvail: charsAvail, getBlock: getBlock, unsafeGetBlock: unsafeGetBlock, putChar: putChar, putBlock: putBlock, unsafePutBlock: unsafePutBlock, flush: flush, eraseChar: eraseChar, reset: reset, close: close, getIndex: getIndex, setIndex: setIndex, backup: backup, getLength: getLength, setLength: setLength, name: "Read/Write File" ]; ProcHandleFromAccessOptions: ARRAY FileIO.AccessOptions OF ProcHandle = [ read: pilotFileIOReadProcs, append: pilotFileIOAppendProcs, write: pilotFileIOAllProcs, overwrite: pilotFileIOAllProcs]; END. CHANGE LOG Created by MBrown on December 12, 1980 1:14 PM Changed by MBrown on January 6, 1981 8:07 PM -- Changes to accomodate interface changes. Moved HandleFromFileName here (from --Common). Changed by MBrown on January 7, 1981 2:32 PM -- Renamed HandleFromFileName -> Open (Morris' suggestion). Changed by MBrown on January 16, 1981 12:17 PM -- Changing module from being paper mache for Common Software FileStreams to being --independent of Pilot Streams. Changed by MBrown on January 18, 1981 1:43 PM -- Still not compiled. Major changes: cleaner handling of leader page (isolated --accesses to dataOffset, leader). Stream now contains file byte length. SetLength --only changes file mappings when it must do so. append and overwrite stream --initializations are handled efficiently. Added truncateFile parm to close. Exceptions --are raised as specified in the interface (paper mache didn't know about all the --signals to catch). -- Hopefully, simple juniper streams can be implemented on this base in a --straightforward way (if we don't try double buffering or anything fancy). Maybe Leaf --streams too. Changed by MBrown on 18-Jan-81 16:51:25 -- Compiled. Interesting problems caused by interaction between exported types and --variant Changed by MBrown on 21-Jan-81 0:40:55 -- Bug (found with RandomStreamTest): in SetLength, when file longer than buffer but --also longer than new length, did not set selfData.dataBytesInBuffer and --selfData.eofInBuffer to reflect the new length. Changed by MBrown on January 21, 1981 10:05 PM -- Changed representation to (1) maintain "didPut" instead of "lengthChanged", (2) --maintain all lengths in terms of bytes, not pages. Open makes initial file size 1 --(not 0). Reordered parms to Open and HandleFromCapability. Changed by MBrown on 22-Jan-81 0:06:59 -- Bug (found with RandomStreamTest): SetLength thought that SetFileSize wanted a byte --length when it wanted a page count. Changed by MBrown on January 22, 1981 3:52 PM -- Ran 1.6 million ticks of random test. Cleanup for release. Still needs a better --interface to the leader page. In particular, it is a kludge that Open relies on --HandleFromCapability to format the leader page of a newly-created file, and this makes --it awkward to create files of a default length > 0 (the wrong length will be written --to the leader page initially). Changed by MBrown on January 27, 1981 2:43 PM -- Moved Open to ...CommonImpl. Changed by MBrown on 29-Jan-81 19:49:10 -- Moved POpen back here. Changed by MBrown on 1-Apr-81 21:51:06 -- Changed leader page I/O to be read/write, to "solve" problem of multiple readers being multiple --leader page writers. Changed by Russ Atkinson on 26-May-81 14:21:38 -- CedarString -> Rope, LONG CARDINAL -> LONG INTEGER Changed by MBrown on 29-May-81 11:57:36 -- In POpen, use Lookup instead of LookupUnlimited (to avoid changing all of the leader page --dates.) Changed by MBrown on 22-Jul-81 17:09:36 -- In POpen, use size: 0 in Directory.CreateFile, since it sets byte length from size (!) Changed by MBrown on 24-Jul-81 10:51:57 -- In CloseData, truncate the file only if file open for write or overwrite. Changed by MBrown on 7-Aug-81 11:25:44 -- Renamed all object procedures to avoid compiler warnings ("xxx is private but matches an --export".) Changed by MBrown on 2-Sep-81 17:23:52 -- In POpen, added a check for filenames that are too long for ComSoft dir package. Changed by MBrown on 2-Nov-81 14:46:58 -- Fixed bug in setLength: newFileBytes was incorrectly computed. Fixed previously --undiscovered bug in POpen (introduced on 2-Sep-81): loop that searches for --rightmost ">" ran forever if file name contained a ">" anywhere (!). Read text --name of file (in InitLeader) using Rope primitives, rather than flattening the Rope --and using ByteBlt. Changed by MBrown on 7-Dec-81 10:36:36 -- Convert to IO. Changed by WTeitelman on 7-Jan-82 13:23:30 -- Use CreateRefStreamProcs, eliminate direct call to Implements Changed by WTeitelman on 15-Jan-82 12:42:59 -- Added NIL check for block argument to putBlock. Changed by MBrown on March 26, 1982 5:12 pm -- Add stuff to read just the plain text from Tioga files. Changed by WTeitelman on May 26, 1982 10:49 pm -- Name change from IOStream to IO, convert to safe language. eliminate Inline.LowHalf Changed by MBrown on August 24, 1982 9:14 pm -- New error handling, CIFS access, etc. Changed by MBrown on September 22, 1982 5:05 pm -- Implement unsafe get/put block with arbitrary nonnegative INT indices. Changed by MBrown on October 4, 1982 4:09 pm -- Fixed bug in change of August 2: when creating a file with createLength > 0, failed to --set file length to 0. Changed by MBrown on October 21, 1982 7:49 pm -- Fixed performance bug in SetupBuffer; was causing the leader page to be rewritten --unnecessarily. Also eliminate update to read date in leader page when file is opened --for read. (Read dates are a bad idea). Changed by WTeitelman on October 27, 1982 10:32 am -- Name change from putBack to backup, added stream argument to errors. Changed by WTeitelman on January 22, 1983 3:38 pm -- added EraseChar. Changed by WTeitelman on February 3, 1983 12:14 pm -- store file name on property list of stream. Changed by MBrown on February 8, 1983 4:49 pm -- In close, do CIFS.Close if file has been CIFS-opened. Changed by MBrown on April 22, 1983 9:59 am -- In reset, do setIndex to end of file, instead of setIndex to start of file. Changed by MBrown on May 16, 1983 3:40 pm -- In SetLengthInLeader, set create time to "now", to eliminate need for Walnut to Close and --Open stream to change create time