-- File: IFSFileOpsA.mesa -- Last edited by Levin: 6-Mar-81 16:39:17 DIRECTORY FileDefs USING [ bytesPerPage, ComparePositions, Completer, defaultTime, FileTime, Position], IFSFilePrivate USING [ DoRead, DoWrite, FileAddressToPosition, FileHandle, fileTimeAddress, FileWatcher, FSInstance, GetIORequestBlock, ifsFiles, IFSTIME, IFSTimes, InsertFile, IORequest, PositionToFileAddress, PurgeFile, ReleaseFile, ValidateFile], Inline USING [COPY], Leaf USING [ Answer, closeOp, deleteOp, FileAddress, IfsError, openOp, paramsOp, ptLeaf, Request, RequestObject], Mopcodes USING [zEXCH], Sequin USING [ Broken, Buffer, Create, Destroy, Get, GetEmptyBuffer, Put, ReleaseBuffer], StringDefs USING [WordsForString], Time USING [Current], VMDefs USING [AccessFailure, Error, OpenOptions, Problem], VMStorage USING [shortTerm]; IFSFileOpsA: MONITOR LOCKS file.LOCK USING file: IFSFilePrivate.FileHandle IMPORTS FileDefs, IFSFilePrivate, Inline, Sequin, StringDefs, Time, VMDefs, VMStorage EXPORTS IFSFilePrivate = BEGIN OPEN IFSFilePrivate; -- Types -- IOSynchRecord: TYPE = RECORD [ done: BOOLEAN, outcome: VMDefs.Problem, file: FileHandle, finished: CONDITION ← [timeout: 0]]; IOSynch: TYPE = POINTER TO IOSynchRecord; -- Miscellaneous -- IllegalExtend: ERROR = CODE; IllegalTruncate: ERROR = CODE; PacketTooSmall: ERROR = CODE; -- Operations (exported to IFSFilePrivate on behalf of FileDefs) -- Open: PUBLIC PROCEDURE [ instance: FSInstance, name: STRING, options: VMDefs.OpenOptions ← oldReadOnly] RETURNS [file: FileHandle] = BEGIN DoOpen: PROCEDURE [file: FileHandle] RETURNS [problem: VMDefs.AccessFailure] = BEGIN buffer: Sequin.Buffer ← Sequin.GetEmptyBuffer[]; request: Leaf.Request ← LOOPHOLE[buffer.data]; openOpOffset: CARDINAL; Flush: PROCEDURE = BEGIN Sequin.Put[file.sequin, buffer]; buffer ← Sequin.GetEmptyBuffer[]; request ← LOOPHOLE[buffer.data]; openOpOffset ← 0; END; MapError: PROCEDURE [leafError: Leaf.IfsError] RETURNS [VMDefs.AccessFailure] = {RETURN[ SELECT leafError FROM accessDenied, fileBusy, illegalDIFAccess, IN [userName .. connectPassword] => accessDenied, fileNotFound, dirNotFound => notFound, fileAlreadyExists => alreadyExists, IN [nameMalformed .. nameTooLong] => illegalFileName, ENDCASE => other]}; file.sequin ← Sequin.Create[dest: instance.serverAddr, pupType: Leaf.ptLeaf]; request↑ ← [Leaf.paramsOp, params[packetDataBytes: buffer.maxBytes, fileLockTimeout: 2*60/5, connectionTimeout: 10*60/5]]; buffer.nBytes ← openOpOffset ← Leaf.paramsOp.length; IF buffer.maxBytes - buffer.nBytes <= Leaf.openOp.length THEN Flush[! Sequin.Broken => GO TO serverDead] ELSE request ← LOOPHOLE[@buffer.data.words[SIZE[params Leaf.RequestObject]]]; request↑ ← [Leaf.openOp, open[]]; BEGIN ENABLE PacketTooSmall => {IF openOpOffset ~= 0 THEN {Flush[! Sequin.Broken => GO TO serverDead]; RETRY}}; fs: FSInstance = file.fs; WITH openReq: request SELECT open FROM open => BEGIN IF options ~= oldReadOnly THEN openReq.write ← openReq.extend ← TRUE; SELECT options FROM oldOrNew => openReq.create ← TRUE; new => {openReq.vDefault ← next; openReq.create ← TRUE}; ENDCASE; END; ENDCASE; buffer.nBytes ← buffer.nBytes + Leaf.openOp.length; AddStringToBuffer[@buffer, fs.primaryName]; AddStringToBuffer[@buffer, fs.primaryPassword]; AddStringToBuffer[@buffer, fs.secondaryName]; AddStringToBuffer[@buffer, fs.secondaryPassword]; AddStringToBuffer[@buffer, name]; END; request.op.length ← buffer.nBytes - openOpOffset; Sequin.Put[file.sequin, buffer ! Sequin.Broken => GO TO serverDead]; buffer ← Sequin.Get[file.sequin ! Sequin.Broken => GO TO serverDead]; BEGIN answer: Leaf.Answer ← LOOPHOLE[buffer.data]; IF answer.op.sense ~= reply THEN {problem ← other; GO TO cantOpen}; WITH ans: answer SELECT answer.op.type FROM error => {problem ← MapError[ans.error]; GO TO cantOpen}; params => NULL; ENDCASE => {problem ← other; GO TO cantOpen}; IF answer.op.length ~= buffer.nBytes THEN answer ← answer + answer.op.length/2 ELSE BEGIN Sequin.ReleaseBuffer[buffer]; buffer ← Sequin.Get[file.sequin ! Sequin.Broken => GO TO serverDead]; answer ← LOOPHOLE[buffer.data]; END; IF answer.op.sense ~= reply THEN {problem ← other; GO TO cantOpen}; WITH ans: answer SELECT answer.op.type FROM error => {problem ← MapError[ans.error]; GO TO cantOpen}; open => BEGIN problem ← ok; file.leafHandle ← ans.handle; file.length ← FileAddressToPosition[ans.eofAddress]; ifsFiles ← ifsFiles + 1; END; ENDCASE => {problem ← other; GO TO cantOpen}; EXITS cantOpen => Sequin.Destroy[file.sequin]; END; Sequin.ReleaseBuffer[buffer]; EXITS serverDead => {problem ← io; Sequin.Destroy[file.sequin]}; END; file ← InsertFile[instance, name, DoOpen]; file.watcher ← FORK FileWatcher[file]; END; Close: PUBLIC PROCEDURE [file: FileHandle] = BEGIN DoClose: PROCEDURE [file: FileHandle] = {CleanupFile[file, close]}; ValidateFile[file]; ReleaseFile[file, DoClose]; END; Abandon: PUBLIC PROCEDURE [file: FileHandle] = BEGIN DoAbandon: PROCEDURE [file: FileHandle] = {CleanupFile[file, abandon]}; ValidateFile[file]; ReleaseFile[file, DoAbandon]; END; Destroy: PUBLIC PROCEDURE [file: FileHandle] = BEGIN DoDestroy: PROCEDURE [file: FileHandle] = {CleanupFile[file, destroy]}; ValidateFile[file]; PurgeFile[file, DoDestroy]; END; GetLength: PUBLIC ENTRY PROCEDURE [file: FileHandle] RETURNS [FileDefs.Position] = BEGIN RETURN[file.length] END; SetLength: PUBLIC PROCEDURE [file: FileHandle, length: FileDefs.Position] = BEGIN oldLength: FileDefs.Position; ValidateFile[file]; oldLength ← GetLength[file]; SELECT FileDefs.ComparePositions[oldLength, length] FROM less => DoIO[file, PrepareSetLength[file, length]]; equal => NULL; greater => Truncate[file, length]; ENDCASE; END; Extend: PUBLIC PROCEDURE [file: FileHandle, length: FileDefs.Position, buffer: POINTER] = BEGIN PrepareExtend: ENTRY PROCEDURE [file: FileHandle] RETURNS [request: IORequest] = INLINE BEGIN request ← GetIORequestBlock[]; ValidateFile[file]; request↑ ← [buffer: buffer, address: PositionToFileAddress[[file.length.page, 0], [write[newEOF: TRUE]]], op: write, bytesToGo: ]; IF FileDefs.ComparePositions[length, file.length] ~= greater THEN GO TO bogus; IF length.page = file.length.page THEN {request.bytesToGo ← length.byte} ELSE BEGIN IF ~(length.page = file.length.page + 1 AND length.byte = 0) THEN GO TO bogus; request.bytesToGo ← FileDefs.bytesPerPage; END; file.length ← length; EXITS bogus => ERROR IllegalExtend; END; DoIO[file, PrepareExtend[file]]; END; Truncate: PUBLIC PROCEDURE [file: FileHandle, length: FileDefs.Position] = BEGIN ValidateFile[file]; SELECT FileDefs.ComparePositions[file.length, length] FROM less => ERROR IllegalTruncate; equal => NULL; greater => DoIO[file, PrepareSetLength[file, length]]; ENDCASE; END; GetTimes: PUBLIC PROCEDURE [file: FileHandle] RETURNS [read, write, create: FileDefs.FileTime] = BEGIN times: IFSTimes; request: IORequest ← GetIORequestBlock[]; request↑ ← [buffer: @times, address: fileTimeAddress, op: read, bytesToGo: 2*SIZE[IFSTimes]]; DoIO[file, request]; RETURN[ IFSToMesaTime[times.read], IFSToMesaTime[times.write], IFSToMesaTime[times.create]] END; SetCreationTime: PUBLIC PROCEDURE [ file: FileHandle, create: FileDefs.FileTime ← FileDefs.defaultTime] = BEGIN creation: IFSTIME; request: IORequest ← GetIORequestBlock[]; IF create = FileDefs.defaultTime THEN create ← Time.Current[]; creation ← MesaToIFSTime[create]; request↑ ← [buffer: @creation, address: fileTimeAddress, op: write, bytesToGo: 2*SIZE[IFSTIME]]; DoIO[file, request]; END; -- Other Procedures exported to IFSFilePrivate -- CopyString: PUBLIC PROCEDURE [s: STRING] RETURNS [ns: STRING] = BEGIN IF s = NIL THEN RETURN[NIL]; ns ← VMStorage.shortTerm.NEW[StringBody[s.length]]; Inline.COPY[from: s, to: ns, nwords: StringDefs.WordsForString[s.length]]; END; AddStringToBuffer: PUBLIC PROCEDURE [buffer: POINTER TO Sequin.Buffer, s: STRING] = BEGIN startWord: CARDINAL = (buffer.nBytes + 1)/2; nWords: CARDINAL; IF s = NIL THEN s ← ""L; nWords ← (s.length + 1)/2; IF buffer.maxBytes < 2*(startWord + nWords) THEN ERROR PacketTooSmall; buffer.data.words[startWord] ← s.length; Inline.COPY[from: @s.text, to: @buffer.data.words[startWord + 1], nwords: nWords]; buffer.nBytes ← buffer.nBytes + 2*(nWords + 1); END; -- Internal Procedures -- PrepareSetLength: ENTRY PROCEDURE [file: FileHandle, length: FileDefs.Position] RETURNS [request: IORequest] = BEGIN request ← GetIORequestBlock[]; request↑ ← [buffer: NIL, address: PositionToFileAddress[length, [write[newEOF: TRUE]]], op: write, bytesToGo: 0]; file.length ← length; END; CleanupFile: ENTRY PROCEDURE [file: FileHandle, op: {close, destroy, abandon}] = BEGIN file.state ← closing; NOTIFY file.ioSynch; UNTIL file.state = closed DO WAIT file.ioSynch ENDLOOP; IF (JOIN file.watcher) AND op ~= abandon THEN BEGIN OPEN Sequin; buffer: Buffer ← GetEmptyBuffer[]; request: Leaf.Request ← LOOPHOLE[buffer.data]; IF op = close THEN BEGIN request↑ ← [Leaf.closeOp, close[handle: file.leafHandle]]; buffer.nBytes ← Leaf.closeOp.length; END ELSE -- op = destroy BEGIN request↑ ← [Leaf.deleteOp, delete[handle: file.leafHandle]]; buffer.nBytes ← Leaf.deleteOp.length; END; Put[file.sequin, buffer ! Broken => GO TO ignore]; buffer ← Get[file.sequin ! Broken => GO TO ignore]; ReleaseBuffer[buffer]; EXITS ignore => NULL; END; Sequin.Destroy[file.sequin]; VMStorage.shortTerm.FREE[@file.name]; ifsFiles ← ifsFiles - 1; END; DoIO: PROCEDURE [file: FileHandle, request: IORequest] = BEGIN ioSynch: IOSynchRecord ← [done: FALSE, outcome: ok, file: file]; WaitForCompletion: ENTRY PROCEDURE [file: FileHandle] = INLINE {UNTIL ioSynch.done DO WAIT ioSynch.finished; ENDLOOP}; request.proc ← NoteCompletion; request.arg ← @ioSynch; SELECT request.op FROM read => DoRead[file, request]; write => DoWrite[file, request]; ENDCASE; WaitForCompletion[file]; IF ioSynch.outcome ~= ok THEN ERROR VMDefs.Error[ioSynch.outcome]; END; NoteCompletion: FileDefs.Completer = BEGIN ioSynch: IOSynch = LOOPHOLE[arg]; DoNotify: ENTRY PROCEDURE [file: FileHandle] = INLINE BEGIN ioSynch.done ← TRUE; ioSynch.outcome ← outcome; NOTIFY ioSynch.finished; END; DoNotify[ioSynch.file]; END; IFSToMesaTime: PROCEDURE [IFSTIME] RETURNS [FileDefs.FileTime] = MACHINE CODE BEGIN Mopcodes.zEXCH; END; MesaToIFSTime: PROCEDURE [FileDefs.FileTime] RETURNS [IFSTIME] = MACHINE CODE BEGIN Mopcodes.zEXCH; END; END.