-- File: IFSFileImplC.mesa -- Last edited by: -- Levin - 14-Sep-81 13:51:35 DIRECTORY ByteBlt USING [ByteBlt], IFSFile USING [Buffer, ByteCount, Completer, CompleterArg, Position, Problem], IFSFilePrivate USING [ FileHandle, FileObject, FileState, FreeIORequestBlock, GetIORequestBlock, IORequest], Leaf USING [ Answer, ByteCount, FileAddress, LeafType, OpSpecificFileAddress, readOp, readAns, Request, writeOp, WriteRequest], Mopcodes USING [zEXCH], Sequin USING [Broken, Buffer, Get, GetEmptyBuffer, Put, ReleaseBuffer]; IFSFileImplC: MONITOR LOCKS file.LOCK USING file: IFSFilePrivate.FileHandle IMPORTS ByteBlt, IFSFilePrivate, Sequin EXPORTS IFSFile, IFSFilePrivate = BEGIN OPEN IFSFilePrivate; -- Miscellaneous -- InvalidFile: ERROR = CODE; LeafGibberish: ERROR = CODE; TheWellIsDry: ERROR = CODE; -- Procedures, Types, and Signals Exported to IFSFile -- FileObject: PUBLIC TYPE = IFSFilePrivate.FileObject; Error: PUBLIC ERROR [reason: IFSFile.Problem] = CODE; StartRead: PUBLIC PROCEDURE [file: FileHandle, pos: IFSFile.Position, bytes: IFSFile.ByteCount, buffer: IFSFile.Buffer, callback: IFSFile.Completer, arg: IFSFile.CompleterArg] = BEGIN request: IORequest; ValidateFile[file]; request ← GetIORequestBlock[]; request↑ ← [link: , buffer: buffer, address: PositionToFileAddress[pos], op: read, bytesToGo: bytes, proc: callback, arg: arg]; DoRead[file, request]; END; StartWrite: PUBLIC PROCEDURE [file: FileHandle, pos: IFSFile.Position, bytes: IFSFile.ByteCount, buffer: IFSFile.Buffer, callback: IFSFile.Completer, arg: IFSFile.CompleterArg] = BEGIN request: IORequest; ValidateFile[file]; request ← GetIORequestBlock[]; request↑ ← [link: , buffer: buffer,address: PositionToFileAddress[pos], op: write, bytesToGo: bytes, proc: callback, arg: arg]; DoWrite[file, request]; END; -- Procedures Exported to IFSFilePrivate -- DoRead: PUBLIC PROCEDURE [file: FileHandle, request: IORequest] = BEGIN sequinBuffer: Sequin.Buffer ← Sequin.GetEmptyBuffer[]; sequinRequest: Leaf.Request ← LOOPHOLE[sequinBuffer.data]; AcquireIOLock[file]; EnqueueRequest[file, request]; sequinRequest↑ ← [op: Leaf.readOp, opSpecific: read[handle: file.leafHandle, address: request.address, length: request.bytesToGo]]; sequinBuffer.nBytes ← Leaf.readOp.length; Sequin.Put[file.sequin, sequinBuffer ! Sequin.Broken => CONTINUE]; ReleaseIOLock[file]; END; DoWrite: PUBLIC PROCEDURE [file: FileHandle, request: IORequest] = BEGIN OPEN IFSFile; bytesSent: Leaf.ByteCount ← 0; bytesToGo: Leaf.ByteCount = request.bytesToGo; initialPosition: Position = FileAddressToPosition[request.address]; positionAfterWrite: Position = initialPosition + request.bytesToGo; AcquireIOLock[file]; IF file.length < positionAfterWrite THEN file.length ← positionAfterWrite; EnqueueRequest[file, request]; DO positionThisTime: Position = initialPosition + bytesSent; buffer: Sequin.Buffer ← Sequin.GetEmptyBuffer[]; sequinRequest: Leaf.WriteRequest ← LOOPHOLE[buffer.data]; bytesThisTime: Leaf.ByteCount; buffer.nBytes ← Leaf.writeOp.length; bytesThisTime ← MIN[buffer.maxBytes - buffer.nBytes, bytesToGo - bytesSent]; sequinRequest↑ ← [op: Leaf.writeOp, opSpecific: write[handle: file.leafHandle, address: PositionToFileAddress[positionThisTime, request.address.opSpecific], length: bytesThisTime, writeBody: ]]; [] ← ByteBlt.ByteBlt[ to: [blockPointer: @sequinRequest.writeBytes, startIndex: 0, stopIndexPlusOne: bytesThisTime], from: [blockPointer: request.buffer, startIndex: bytesSent, stopIndexPlusOne: bytesSent+bytesThisTime]]; sequinRequest.op.length ← buffer.nBytes ← buffer.nBytes + bytesThisTime; Sequin.Put[file.sequin, buffer ! Sequin.Broken => EXIT]; bytesSent ← bytesSent + bytesThisTime; IF bytesSent = bytesToGo THEN EXIT; ENDLOOP; ReleaseIOLock[file]; END; FileWatcher: PUBLIC PROCEDURE [file: FileHandle] RETURNS [BOOLEAN] = BEGIN current: IORequest ← NIL; fatalError: BOOLEAN ← FALSE; offset: CARDINAL; DO buffer: Sequin.Buffer; outcome: IFSFile.Problem; CheckState: ENTRY PROCEDURE [file: FileHandle] RETURNS [FileState] = -- INLINE -- BEGIN DO SELECT file.state FROM alive => IF fatalError THEN file.state ← flush; flush => NULL; closing => BEGIN IF file.ioPending = NIL THEN {file.state ← closed; BROADCAST file.ioSynch}; EXIT END; ENDCASE; IF file.ioPending ~= NIL THEN EXIT; WAIT file.ioSynch; ENDLOOP; RETURN[file.state] END; CompleteRequest: PROCEDURE = BEGIN request: IORequest = DequeueRequest[file]; request.proc[request.arg, outcome]; FreeIORequestBlock[request]; current ← NIL; END; SELECT CheckState[file] FROM alive => BEGIN answer: Leaf.Answer; position: IFSFile.Position; EnsureCurrent: ENTRY PROCEDURE [file: FileHandle] = -- INLINE -- BEGIN IF current ~= NIL THEN RETURN; IF file.ioPending = NIL THEN ERROR TheWellIsDry; current ← file.ioPending.link; position ← FileAddressToPosition[current.address]; offset ← 0; END; ValidateArrival: PROCEDURE [address: Leaf.FileAddress, bytes: Leaf.ByteCount] = BEGIN pos: IFSFile.Position = FileAddressToPosition[address]; IF current.op = answer.op.type AND pos = position THEN BEGIN outcome ← ok; current.bytesToGo ← current.bytesToGo - bytes; position ← position + bytes; END ELSE fatalError ← TRUE; END; EnsureCurrent[file]; outcome ← other; buffer ← Sequin.Get[file.sequin ! Sequin.Broken => {outcome ← io; fatalError ← TRUE; LOOP}]; answer ← LOOPHOLE[buffer.data]; IF answer.op.sense ~= reply THEN GO TO protocolViolation; WITH ans: answer SELECT answer.op.type FROM error => BEGIN IF current.op ~= ans.errorOp.type THEN GO TO protocolViolation; SELECT ans.error FROM accessDenied, fileBusy, IN [userName .. connectPassword] => outcome ← credentials; allocExceeded, fileSystemFull => outcome ← resources; brokenLeaf => {outcome ← io; GO TO broken}; illegalRead, illegalWrite => outcome ← illegalIO; IN [unimplementedOp..illegalTruncate] => ERROR LeafGibberish; ENDCASE; current.bytesToGo ← 0; END; read => BEGIN dataBytes: Leaf.ByteCount = ans.op.length - Leaf.readAns.length; ValidateArrival[ans.address, dataBytes]; [] ← ByteBlt.ByteBlt[ to: [blockPointer: current.buffer, startIndex: offset, stopIndexPlusOne: offset+dataBytes], from: [blockPointer: @ans.readBytes, startIndex: 0, stopIndexPlusOne: dataBytes]]; offset ← offset + dataBytes; END; write => ValidateArrival[ans.address, ans.length]; ENDCASE => GO TO protocolViolation; IF current.bytesToGo = 0 THEN CompleteRequest[]; Sequin.ReleaseBuffer[buffer]; EXITS protocolViolation, broken => {fatalError ← TRUE; Sequin.ReleaseBuffer[buffer]}; END; flush => CompleteRequest[]; closing => {outcome ← other; CompleteRequest[]}; closed => EXIT; ENDCASE; ENDLOOP; RETURN[~fatalError] END; ValidateFile: PUBLIC PROCEDURE [file: FileHandle] = {IF file.seal ~= openSeal THEN ERROR InvalidFile}; FileAddressToPosition: PUBLIC PROCEDURE [fa: Leaf.FileAddress] RETURNS [pos: IFSFile.Position] = -- Note: regards 'fa' as a signed quantity (to allow leader page access). BEGIN Flip: PROCEDURE [Leaf.FileAddress] RETURNS [IFSFile.Position] = MACHINE CODE BEGIN Mopcodes.zEXCH; END; fa.opSpecific ← Leaf.OpSpecificFileAddress[open[fill: IF fa.high > 1777B THEN 37B ELSE 0]]; RETURN[Flip[fa]] END; PositionToFileAddress: PUBLIC PROCEDURE [ pos: IFSFile.Position, opSpecific: Leaf.OpSpecificFileAddress ← [read[]]] RETURNS [fa: Leaf.FileAddress] = -- Note: regards 'pos' as a signed quantity (to allow leader page access). BEGIN Flip: PROCEDURE [IFSFile.Position] RETURNS [Leaf.FileAddress] = MACHINE CODE BEGIN Mopcodes.zEXCH; END; fa ← Flip[pos]; fa.opSpecific ← opSpecific; END; -- Internal Procedures -- AcquireIOLock: ENTRY PROCEDURE [file: FileHandle] = BEGIN WHILE file.locked DO WAIT file.ioSynch ENDLOOP; file.locked ← TRUE; END; ReleaseIOLock: ENTRY PROCEDURE [file: FileHandle] = BEGIN file.locked ← FALSE; BROADCAST file.ioSynch; END; EnqueueRequest: ENTRY PROCEDURE [file: FileHandle, request: IORequest] = BEGIN IF file.ioPending = NIL THEN request.link ← request ELSE {request.link ← file.ioPending.link; file.ioPending.link ← request}; file.ioPending ← request; BROADCAST file.ioSynch; END; DequeueRequest: ENTRY PROCEDURE [file: FileHandle] RETURNS [request: IORequest] = BEGIN IF file.ioPending = NIL THEN ERROR TheWellIsDry; request ← file.ioPending.link; file.ioPending.link ← request.link; IF request = file.ioPending THEN file.ioPending ← NIL; END; END.