DIRECTORY BasicTime USING [GMT], Basics USING [LongNumber, BITAND], FS, FileStream , FileStreamPrivate , FSBackdoor USING [ProduceError], FSLock USING [RecordREF ], Process USING [ Pause ], IO, IOUtils, Rope, SafeStorage USING [EnableFinalization], VM USING [ AddressForPageNumber, Allocate, BytesForPages, CantAllocate, Free, PageNumber] ; FileStreamCreateImpl: CEDAR MONITOR LOCKS fileData.lockRecord USING fileData: FileDataHandle IMPORTS Basics, FileStream, FileStreamPrivate, FS, FSBackdoor, FSLock, IO, IOUtils, Process, SafeStorage, VM EXPORTS 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; PageCount: TYPE = VM.PageNumber; bytesPerFilePage: CARDINAL = FS.BytesForPages[1]; maxVMPagesPerBuffer: INT = 65536/VM.BytesForPages[pages: 1] ; clearLowBits: CARDINAL = CARDINAL.LAST-(bytesPerFilePage-1); 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 = REF IO.StreamProcs; StreamFromOpenFile: PUBLIC PROC [ openFile: FS.OpenFile, accessRights: FS.Lock, initialPosition: FS.InitialPosition, streamOptions: FS.StreamOptions, streamBufferParms: FS.StreamBufferParms, extendFileProc: FS.ExtendFileProc] RETURNS [stream: STREAM] = { pageAllocation: PageCount; byteLength: ByteCount; fileName: ROPE = openFile.GetName[].fullFName; fsData: FSDataHandle; fsDataFile: FileDataHandle ; node: BufferNodeHandle ; IF streamBufferParms.vmPagesPerBuffer = 128 THEN streamBufferParms.vmPagesPerBuffer _ 127 ; IF accessRights = $write AND GetFileLock[openFile] # $write THEN FSBackdoor.ProduceError[wrongLock, fileName]; [pages: pageAllocation, bytes: byteLength] _ openFile.GetInfo[]; fsData _ NEW[Data _ []]; fsDataFile _ NEW[FileData _ [ fileName: fileName, accessRights: accessRights, fileLength: byteLength, fileHandle: openFile, streamBufferParms: streamBufferParms, extendFileProc: extendFileProc, streamOptions: streamOptions, byteLength: byteLength, byteSize: pageAllocation*bytesPerFilePage, validBytesOnDisk: byteLength] ]; IF fsDataFile.byteLength > fsDataFile.byteSize THEN ERROR; fsData.fileData _ fsDataFile ; fsDataFile.firstBufferNode _ node _ CreateBufferSpace[vmPagesPerBuffer: streamBufferParms.vmPagesPerBuffer]; FOR i: INT IN [2..streamBufferParms.nBuffers] DO node.nextBufferNode _ CreateBufferSpace[vmPagesPerBuffer: streamBufferParms.vmPagesPerBuffer]; node _ node.nextBufferNode ; ENDLOOP; stream _ IO.CreateStream[FileStreamPrivate.ProcHandleFromAccessRights[accessRights], fsData]; IOUtils.StoreData[self: stream, key: $Name, data: fsDataFile.fileName]; IF accessRights = $write THEN { fsDataFile.writeStreamData _ fsData ; fsData.isWriteStream _ TRUE ; IF fsDataFile.byteSize = 0 THEN { fsDataFile.byteSize _ NewByteSize[fsDataFile.byteSize]; SetFileSize[fsDataFile.fileHandle, fsDataFile.byteSize]; }; } ELSE { fsDataFile.firstReadStream _ fsData; }; IF initialPosition = start THEN { [] _ FileStreamPrivate.SetupBuffer[fileData: fsDataFile, fsData: fsData, fileByte: 0] } ELSE { -- initialPosition = end node _ FileStreamPrivate.SetupBuffer[fileData: fsDataFile, fsData: fsData, fileByte: PageContainingLastByte[fsDataFile.fileLength]]; fsData.index _ node.dataBytesInBuffer; }; IF streamOptions[tiogaRead] AND byteLength > 0 THEN { isTioga: BOOL; len: INT; [yes: isTioga, len: len] _ IsThisThingATiogaFile[stream]; IF isTioga THEN { IF accessRights = $read THEN { FileStream.SetLength[stream, len]; fsDataFile.tiogaReader _ TRUE } ELSE { stream.Close[]; FSBackdoor.ProduceError[cantUpdateTiogaFile, fileName]; } } }; IF FileStreamPrivate.DoFinalization THEN { FSLock.RecordREF[fsData]; SafeStorage.EnableFinalization[fsData]; }; fsData.ConvertFStoIOErrors _ TRUE; fsData _ NIL ; RETURN[stream]; };--StreamFromOpenFile PageContainingLastByte: PROC [byteLen: INT] RETURNS [INT] = INLINE { IF byteLen = 0 THEN RETURN[0] ELSE { byteLen _ byteLen - 1; LOOPHOLE[byteLen, LongNumber[num]].lowbits _ BITAND[LOOPHOLE[byteLen, LongNumber[num]].lowbits, clearLowBits]; RETURN[byteLen] }}; OpenFileFromStream: PUBLIC PROC [self: STREAM] RETURNS [FS.OpenFile] = { WITH self.streamData SELECT FROM fsData: FSDataHandle => RETURN [fsData.fileData.fileHandle]; ENDCASE => ERROR IO.Error[NotImplementedForThisStream, self]; }; StreamFromOpenStream: PUBLIC PROC [self: STREAM] RETURNS [stream: STREAM] = { newData: FSDataHandle ; filePos: INT ; WITH self.streamData SELECT FROM selfData: FSDataHandle => { fileData: FileDataHandle = selfData.fileData ; IF NOT selfData.isWriteStream OR fileData.firstReadStream # NIL THEN FSBackdoor.ProduceError [code: notImplemented, explanation: "self is not a write stream, or there already is a read stream"]; newData _ NEW[Data _ [ ] ]; newData.fileData _ fileData ; stream _ IO.CreateStream[FileStreamPrivate.ProcHandleFromAccessRights[$read], newData]; IOUtils.StoreData[self: stream, key: $Name, data: fileData.fileName]; fileData.firstReadStream _ newData; filePos _ SetUpClonedStream[fileData: fileData, fsData: selfData]; [] _ FileStreamPrivate.SetupBuffer[fileData: fileData, fsData: newData, fileByte: selfData.currentNode.firstFileByteInBuffer] ; newData.index _ selfData.index ; IF FileStreamPrivate.DoFinalization THEN { FSLock.RecordREF[newData]; SafeStorage.EnableFinalization[newData]; }; newData.ConvertFStoIOErrors _ TRUE; newData _ NIL ; }; ENDCASE => ERROR IO.Error[NotImplementedForThisStream, self]; }; SetUpClonedStream: ENTRY PROC [fileData: FileDataHandle, fsData: FSDataHandle] RETURNS [filePos: INT]= { ENABLE UNWIND => NULL; node: BufferNodeHandle _ fileData.firstBufferNode; UNTIL node.nextBufferNode = NIL DO node _ node.nextBufferNode ; ENDLOOP ; FOR i:INT IN [1..fileData.streamBufferParms.nBuffers] DO node.nextBufferNode _ CreateBufferSpace[vmPagesPerBuffer: fileData.streamBufferParms.vmPagesPerBuffer]; node _ node.nextBufferNode ; ENDLOOP; fileData.numberOfStreams _ fileData.numberOfStreams + 1 ; filePos _ fsData.index + fsData.currentNode.firstFileByteInBuffer ; fsData _ NIL ; }; CreateBufferSpace: PROC [vmPagesPerBuffer: INT [1 .. 128]] RETURNS [BufferNodeHandle] = { vmPages: INT _ MIN[vmPagesPerBuffer, maxVMPagesPerBuffer] ; newBuffer: BufferNodeHandle _ NEW[BufferNode]; newBuffer.bufferInterval _ VM.Allocate[count: vmPages ! VM.CantAllocate => { TRUSTED {VM.Free[interval: bestInterval]}; Process.Pause[4]; RETRY; }; ]; TRUSTED{newBuffer.buffer _ VM.AddressForPageNumber[newBuffer.bufferInterval.page]}; newBuffer.bufferBytes _ VM.BytesForPages[pages: vmPages] ; RETURN[newBuffer] }; NewByteSize: PROC [byteCount: ByteCount] RETURNS [ByteCount] = { RETURN [byteCount+5120]; }; 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] }; IsThisThingATiogaFile: PROC [h: STREAM] RETURNS [yes: BOOL, len: INT] = { pos, length: INT; { -- block so EXITS code can use pos, len, and length. controlHeaderId: ARRAY [0..fileIdSize) OF CHAR = [235C,312C]; controlTrailerId: ARRAY [0..fileIdSize) OF CHAR = [205C,227C]; commentHeaderId: ARRAY [0..fileIdSize) OF CHAR = [0C,0C]; fileIdSize: NAT = 2; numTrailerLengths: NAT = 3; -- endSize: NAT = fileIdSize+numTrailerLengths*4; -- trailer plus three lengths ReadLen: PROC [h: STREAM] RETURNS [INT] = { start: PACKED ARRAY [0..3] OF CHARACTER; start[0] _ h.GetChar[]; start[1] _ h.GetChar[]; start[2] _ h.GetChar[]; start[3] _ h.GetChar[]; RETURN [LOOPHOLE[start]] }; commentStart, commentLen, propsLen, controlLen, controlEnd: INT; pos _ h.GetIndex[]; -- save position to restore later length _ h.GetLength[]; -- length including any trailer stuff controlEnd _ length-endSize; -- where the trailer info starts IF controlEnd <= 0 THEN GOTO fail; -- too small h.SetIndex[controlEnd]; -- set up to read the trailer FOR i:NAT IN [0..fileIdSize) DO -- read the controlTrailerId IF h.GetChar[] # controlTrailerId[i] THEN GOTO fail; ENDLOOP; IF (propsLen _ ReadLen[h]) NOT IN [0..controlEnd) THEN GOTO fail; IF (commentStart _ ReadLen[h]) NOT IN [0..controlEnd) THEN GOTO fail; IF ReadLen[h] # length THEN GOTO fail; IF commentStart > 0 THEN { -- may have padded text with a null h.SetIndex[commentStart-1]; len _ IF h.GetChar[]=0C THEN commentStart-1 ELSE commentStart } ELSE h.SetIndex[len _ commentStart]; FOR i:NAT IN [0..fileIdSize) DO -- read the commentHeaderId IF h.GetChar[] # commentHeaderId[i] THEN GOTO fail; ENDLOOP; commentLen _ ReadLen[h]; -- the length of the comment section IF commentStart+commentLen NOT IN [0..controlEnd) THEN GOTO fail; h.SetIndex[commentStart+commentLen]; -- go to start of control info FOR i:NAT IN [0..fileIdSize) DO -- check the controlHeaderId IF h.GetChar[] # controlHeaderId[i] THEN GOTO fail; ENDLOOP; controlLen _ ReadLen[h]; -- the length of the control section IF commentStart+commentLen+controlLen # length THEN GOTO fail; GOTO succeed; EXITS fail => { h.SetIndex[pos]; RETURN [FALSE, length] }; succeed => { h.SetIndex[pos]; RETURN [TRUE, len] }; }}; END. CHANGE LOG Created by Hagmann on November 22, 1983 4:30 pm Changed by Hagmann on November 28, 1983 12:01 pm Changed by Hagmann on December 6, 1983 4:52 pm Changed by Hagmann on January 3, 1984 11:33 am dFileStreamCreateImpl.mesa Last Edited by Hagmann on January 3, 1984 11:32 am Schroeder on November 28, 1983 12:37 pm Please maintain change log at end of file. To Do: What to do when byte length > allocated length at Open time? 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. Stream creation no monitors are needed in this code since, until this proc returns, no other code can refer to the streams Index must always be less than 64K, so we have to clip off a page from the max. make length look changed by sneaky call to SetLength (not in stream procs). since stream is opened for read only, this call won't change the length in the file. you can't incrementally update a Tioga file with IO! Find last node Allocate some more nodes. Buffer management We cannot accept anything but the right size interval. The program assumes that all buffers are the same size. Talking to FS Tioga By cutting this out of FSFileIOImpl. Added test for DoFinalization to enable FileStream testing without making a boot file Removed code for process cache Added enable for ConvertFStoIOErrors. Κ =˜headšœ™šœ™Jšœ#™#Jšœ'™'—J˜Jšœ*™*J™šœ™Jšœ<™Jšœœœœ ˜9Jšœ œ˜Jšœœž2˜NJšœ œ#ž˜Lš Ÿœœœœœ˜+Jš œœœœ œ˜(J˜/J˜/Jšœœ ˜Jšœ<œ˜@Jšœž!˜5Jšœž%˜=Jšœž ˜=Jšœœœž ˜/Jšœž˜5š œœœœž˜J˜Jšœœœœ˜?—Jšœ ˜$š œœœœž˜;Jšœ"œœ˜3Jšœ˜—Jšœž$˜=Jš œœœœœ˜AJšœ%ž˜Cš œœœœž˜Jšœ ˜ š˜Jšœœœ ˜5Jšœœœ ˜4—J˜J˜——Jšœ˜J˜˜Jšœ˜ J˜J˜/Jšœ$™$J˜J˜0JšœU™UJ˜J˜.Jšœ™J˜J˜.Jšœ%™%J˜J˜J˜———…—%„6%