-- File: IFSFileOpsB.mesa -- Last edited by Levin: 4-Feb-82 14:52:09 -- edit by Andrew: 11-Feb-81 14:51:07 DIRECTORY FileDefs USING [ Buffer, bytesPerPage, ComparePositions, Completer, CompleterArg, IncrementPosition, PageNumber, Position, Problem], IFSFilePrivate USING [ FileHandle, FileState, FreeIORequestBlock, GetIORequestBlock, IORequest, openSeal], Leaf USING [ Answer, ByteCount, FileAddress, LeafType, OpSpecificFileAddress, readOp, readAns, Request, writeOp, WriteRequest], MiscDefs USING [ByteBlt], Sequin USING [Broken, Buffer, Get, GetEmptyBuffer, Put, ReleaseBuffer]; IFSFileOpsB: MONITOR LOCKS file.LOCK USING file: IFSFilePrivate.FileHandle IMPORTS FileDefs, IFSFilePrivate, MiscDefs, Sequin EXPORTS IFSFilePrivate = BEGIN OPEN IFSFilePrivate; -- Types -- FAPieces: TYPE = MACHINE DEPENDENT RECORD [ ugh(0): SELECT OVERLAID * FROM fa => [fa(0): Leaf.FileAddress], faPieces => [opSpecific(0:0..4): Leaf.OpSpecificFileAddress, faHigh(0:5..15): [0..3777B], faMiddle(1:0..6): [0..177B], faLow(1:7..15): [0..777B]], posPieces => [posHigh(0:0..8): [0..777B], posMiddle(0:9..15): [0..177B], posLow(1:0..15): [0..777B]], position => [pos(0): FileDefs.Position], ENDCASE]; -- Miscellaneous -- bytesPerPage: CARDINAL = FileDefs.bytesPerPage; FileTooBig: ERROR = CODE; InvalidFile: ERROR = CODE; LeafGibberish: ERROR = CODE; TheWellIsDry: ERROR = CODE; -- Operations (exported to IFSFilePrivate on behalf of FileDefs) -- StartRead: PUBLIC PROCEDURE [file: FileHandle, page: FileDefs.PageNumber, buffer: FileDefs.Buffer, callback: FileDefs.Completer, arg: FileDefs.CompleterArg] = BEGIN request: IORequest; bytesToRead: CARDINAL; ValidateFile[file]; bytesToRead ← IF file.length.page = page THEN file.length.byte ELSE bytesPerPage; request ← GetIORequestBlock[]; request↑ ← [link: , buffer: buffer, address: PositionToFileAddress[[page: page, byte: 0]], op: read, bytesToGo: bytesToRead, proc: callback, arg: arg]; DoRead[file, request]; END; StartWrite: PUBLIC PROCEDURE [file: FileHandle, page: FileDefs.PageNumber, buffer: FileDefs.Buffer, callback: FileDefs.Completer, arg: FileDefs.CompleterArg] = BEGIN request: IORequest; bytesToWrite: CARDINAL; ValidateFile[file]; bytesToWrite ← IF file.length.page = page THEN file.length.byte ELSE bytesPerPage; request ← GetIORequestBlock[]; request↑ ← [link: , buffer: buffer, address: PositionToFileAddress[[page: page, byte: 0]], op: write, bytesToGo: bytesToWrite, proc: callback, arg: arg]; DoWrite[file, request]; END; -- Other 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 FileDefs; bytesSent: Leaf.ByteCount ← 0; bytesToGo: Leaf.ByteCount = request.bytesToGo; initialPosition: Position = FileAddressToPosition[request.address]; positionAfterWrite: Position = IncrementPosition[initialPosition, request.bytesToGo]; AcquireIOLock[file]; IF ComparePositions[file.length, positionAfterWrite] = less THEN file.length ← positionAfterWrite; EnqueueRequest[file, request]; DO positionThisTime: Position = IncrementPosition[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: ]]; MiscDefs.ByteBlt[to: @sequinRequest.writeBytes, from: request.buffer, toByte: 0, fromByte: bytesSent, nBytes: 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: FileDefs.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: FileDefs.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: FileDefs.Position = FileAddressToPosition[address]; IF current.op = answer.op.type AND pos = position THEN BEGIN outcome ← ok; current.bytesToGo ← current.bytesToGo - bytes; position ← FileDefs.IncrementPosition[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 ELSE 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}; IN [unimplementedOp..illegalWrite] => ERROR LeafGibberish; ENDCASE; current.bytesToGo ← 0; END; read => BEGIN dataBytes: Leaf.ByteCount = ans.op.length - Leaf.readAns.length; ValidateArrival[ans.address, dataBytes]; MiscDefs.ByteBlt[to: current.buffer, from: @ans.readBytes, toByte: offset, fromByte: 0, nBytes: 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 [FileDefs.Position] = -- Note: regards 'fa' as a signed quantity (to allow leader page access). BEGIN p1, p2: FAPieces; p1.fa ← fa; IF ~(p1.faHigh <= 777B OR p1.faHigh IN [3400B..3777B]) THEN ERROR FileTooBig; p2 ← [posPieces[posHigh: p1.faHigh, posMiddle: p1.faMiddle, posLow: p1.faLow]]; RETURN[p2.pos] END; PositionToFileAddress: PUBLIC PROCEDURE [ pos: FileDefs.Position, opSpecific: Leaf.OpSpecificFileAddress ← [read[]]] RETURNS [Leaf.FileAddress] = -- Note: regards 'pos' as a signed quantity (to allow leader page access). BEGIN p1, p2: FAPieces; p1.pos ← pos; p2 ← [faPieces[opSpecific: opSpecific, faHigh: p1.posHigh, faMiddle: p1.posMiddle, faLow: p1.posLow]]; IF p1.posHigh >= 400B THEN p2.faHigh ← p1.posHigh + 3000B; RETURN[p2.fa] 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.