<> <> <> <> DIRECTORY PrincOpsUtils 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], PrincOps USING [zEXCH], Sequin USING [Broken, Buffer, Get, GetEmptyBuffer, Put, ReleaseBuffer]; IFSFileImplC: MONITOR LOCKS file.LOCK USING file: IFSFilePrivate.FileHandle IMPORTS PrincOpsUtils, IFSFilePrivate, Sequin EXPORTS IFSFile, IFSFilePrivate = { OPEN IFSFilePrivate; <> InvalidFile: ERROR = CODE; LeafGibberish: ERROR = CODE; TheWellIsDry: ERROR = CODE; <> FileObject: PUBLIC TYPE = IFSFilePrivate.FileObject; Error: PUBLIC ERROR [reason: IFSFile.Problem] = CODE; StartRead: PUBLIC PROC [file: FileHandle, pos: IFSFile.Position, bytes: IFSFile.ByteCount, buffer: IFSFile.Buffer, callback: IFSFile.Completer, arg: IFSFile.CompleterArg] = { request: IORequest; ValidateFile[file]; request _ GetIORequestBlock[]; request^ _ [link: , buffer: buffer, address: PositionToFileAddress[pos], op: read, bytesToGo: bytes, proc: callback, arg: arg]; DoRead[file, request]; }; StartWrite: PUBLIC PROC [file: FileHandle, pos: IFSFile.Position, bytes: IFSFile.ByteCount, buffer: IFSFile.Buffer, callback: IFSFile.Completer, arg: IFSFile.CompleterArg] = { request: IORequest; ValidateFile[file]; request _ GetIORequestBlock[]; request^ _ [link: , buffer: buffer,address: PositionToFileAddress[pos], op: write, bytesToGo: bytes, proc: callback, arg: arg]; DoWrite[file, request]; }; <> DoRead: PUBLIC PROC [file: FileHandle, request: IORequest] = { 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]; }; DoWrite: PUBLIC PROC [file: FileHandle, request: IORequest] = { 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: ]]; [] _ PrincOpsUtils.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]; }; FileWatcher: PUBLIC PROC [file: FileHandle] RETURNS [BOOL] = { current: IORequest _ NIL; fatalError: BOOL _ FALSE; offset: CARDINAL; DO buffer: Sequin.Buffer; outcome: IFSFile.Problem; CheckState: ENTRY PROC [file: FileHandle] RETURNS [FileState] = { DO SELECT file.state FROM alive => IF fatalError THEN file.state _ flush; flush => NULL; closing => { IF file.ioPending = NIL THEN {file.state _ closed; BROADCAST file.ioSynch}; EXIT }; ENDCASE; IF file.ioPending ~= NIL THEN EXIT; WAIT file.ioSynch; ENDLOOP; RETURN[file.state] }; CompleteRequest: PROC = { request: IORequest = DequeueRequest[file]; request.proc[request.arg, outcome]; FreeIORequestBlock[request]; current _ NIL; }; SELECT CheckState[file] FROM alive => { answer: Leaf.Answer; position: IFSFile.Position; EnsureCurrent: ENTRY PROC [file: FileHandle] = { IF current ~= NIL THEN RETURN; IF file.ioPending = NIL THEN ERROR TheWellIsDry; current _ file.ioPending.link; position _ FileAddressToPosition[current.address]; offset _ 0; }; ValidateArrival: PROC [address: Leaf.FileAddress, bytes: Leaf.ByteCount] = { pos: IFSFile.Position = FileAddressToPosition[address]; IF current.op = answer.op.type AND pos = position THEN { outcome _ ok; current.bytesToGo _ current.bytesToGo - bytes; position _ position + bytes; } ELSE fatalError _ TRUE; }; 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 => { 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; }; read => { dataBytes: Leaf.ByteCount = ans.op.length - Leaf.readAns.length; ValidateArrival[ans.address, dataBytes]; [] _ PrincOpsUtils.ByteBlt[ to: [blockPointer: current.buffer, startIndex: offset, stopIndexPlusOne: offset+dataBytes], from: [blockPointer: @ans.readBytes, startIndex: 0, stopIndexPlusOne: dataBytes]]; offset _ offset + dataBytes; }; 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]}; }; flush => CompleteRequest[]; closing => {outcome _ other; CompleteRequest[]}; closed => EXIT; ENDCASE; ENDLOOP; RETURN[~fatalError] }; ValidateFile: PUBLIC PROC [file: FileHandle] = {IF file.seal ~= openSeal THEN ERROR InvalidFile}; FileAddressToPosition: PUBLIC PROC [fa: Leaf.FileAddress] RETURNS [pos: IFSFile.Position] = { <> Flip: PROC [Leaf.FileAddress] RETURNS [IFSFile.Position] = MACHINE CODE { PrincOps.zEXCH; }; fa.opSpecific _ Leaf.OpSpecificFileAddress[open[fill: IF fa.high > 1777B THEN 37B ELSE 0]]; RETURN[Flip[fa]] }; PositionToFileAddress: PUBLIC PROC [ pos: IFSFile.Position, opSpecific: Leaf.OpSpecificFileAddress _ [read[]]] RETURNS [fa: Leaf.FileAddress] = { <> Flip: PROC [IFSFile.Position] RETURNS [Leaf.FileAddress] = MACHINE CODE { PrincOps.zEXCH; }; fa _ Flip[pos]; fa.opSpecific _ opSpecific; }; <> AcquireIOLock: ENTRY PROC [file: FileHandle] = { WHILE file.locked DO WAIT file.ioSynch ENDLOOP; file.locked _ TRUE; }; ReleaseIOLock: ENTRY PROC [file: FileHandle] = { file.locked _ FALSE; BROADCAST file.ioSynch; }; EnqueueRequest: ENTRY PROC [file: FileHandle, request: IORequest] = { IF file.ioPending = NIL THEN request.link _ request ELSE {request.link _ file.ioPending.link; file.ioPending.link _ request}; file.ioPending _ request; BROADCAST file.ioSynch; }; DequeueRequest: ENTRY PROC [file: FileHandle] RETURNS [request: IORequest] = { IF file.ioPending = NIL THEN ERROR TheWellIsDry; request _ file.ioPending.link; file.ioPending.link _ request.link; IF request = file.ioPending THEN file.ioPending _ NIL; }; }.