DIRECTORY Basics USING [LongNumber, BITAND], BasicTime USING [GMT], FileStream USING [SetLength], FileStreamPrivate USING [BufferNode, BufferNodeHandle, Data, DoFinalization, FileData, FileDataHandle, FSDataHandle, ProcHandle, ProcHandleFromAccessRights, SetupBuffer], FS USING [ByteCount, BytesForPages, ExtendFileProc, GetInfo, GetName, InitialPosition, Lock, OpenFile, SetPageCount, StreamBufferParms, StreamOptions], FSBackdoor USING [ProduceError], FSLock USING [RecordREF ], IO USING [Close, CreateStream, Error, GetChar, GetIndex, GetLength, SetIndex, STREAM, StreamProcs], IOUtils USING [StoreData], Process USING [ Pause ], Rope USING [ROPE], SafeStorage USING [EnableFinalization], VM USING [AddressForPageNumber, Allocate, BytesForPages, CantAllocate, PageNumber, SwapIn]; 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; 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[streamBufferParms.vmPagesPerBuffer, accessRights]; FOR i: INT IN [2..streamBufferParms.nBuffers] DO node.nextBufferNode _ CreateBufferSpace[streamBufferParms.vmPagesPerBuffer, accessRights]; 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 { 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[fileData.streamBufferParms.vmPagesPerBuffer, read]; node _ node.nextBufferNode; ENDLOOP; fileData.numberOfStreams _ fileData.numberOfStreams + 1 ; filePos _ fsData.index + fsData.currentNode.firstFileByteInBuffer ; fsData _ NIL ; }; CreateBufferSpace: PROC [vmPagesPerBuffer: INT [1 .. 128], accessRights: FS.Lock] RETURNS [BufferNodeHandle] = { vmPages: INT _ MIN[vmPagesPerBuffer, maxVMPagesPerBuffer] ; newBuffer: BufferNodeHandle _ NEW[BufferNode]; newBuffer.bufferInterval _ VM.Allocate[count: vmPages ! VM.CantAllocate => { Process.Pause[4]; RETRY; }; ]; TRUSTED { newBuffer.buffer _ VM.AddressForPageNumber[newBuffer.bufferInterval.page]; IF accessRights = write THEN VM.SwapIn[newBuffer.bufferInterval]; }; 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 8FileStreamCreateImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Hagmann on January 31, 1985 5:40:52 pm PST Schroeder on November 28, 1983 12:37 pm Russ Atkinson (RRA) May 9, 1985 1:57:47 pm PDT Bob Hagmann May 14, 1985 11:12:14 am PDT 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. initialPosition = end 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. Russ Atkinson (RRA) May 9, 1985 1:56:42 pm PDT Added VM.SwapIn for write buffer pages to avoid bogus page faults changes to: StreamFromOpenFile, SetUpClonedStream, CreateBufferSpace Bob Hagmann May 14, 1985 11:10:58 am PDT put back in use of FSLock changes to: StreamFromOpenFile, StreamFromOpenStream Κ P˜codešœ™Kšœ Οmœ1™Kšœžœžœžœ ˜9Kšœ žœ˜KšœžœŸ2˜NKšœ žœ#Ÿ˜Lš  œžœžœžœžœ˜+Kš œžœžœžœž œ˜(K˜/K˜/Kšžœžœ ˜Kšœ<žœ˜@KšœŸ!˜5KšœŸ%˜=KšœŸ ˜=KšžœžœžœŸ ˜/KšœŸ˜5š žœžœžœžœŸ˜Kšžœ ˜ šž˜Kšœžœžœ ˜5Kšœžœžœ ˜4—K˜——˜K˜——Kšžœ˜K˜K˜—šžœž˜ K˜K˜/Kšœ$™$K˜K˜0KšœU™UK˜K˜.Kšœ™K˜K˜.Kšœ%™%K˜K˜—™.K™AKšœ Οr8™D—K™™(K™Kšœ4™4—K™—…—'*9²