-- Copyright (C) 1981, 1982, 1984, 1985 by Xerox Corporation. All rights reserved. -- PilotFileSystem.mesa -- HGM, 16-Sep-85 22:01:34 -- Wobber, 3-Nov-82 11:30:48 -- Gobbel, 25-Sep-81 15:24:50 -- MJohnson, 1-Feb-82 15:03:33 -- Hankins 8-Aug-84 9:52:49 (Klamath update - space rework) -- TO DO: -- - Remove LOOPHOLEs DIRECTORY Environment USING [bytesPerPage], File USING [File], FileDefs USING [ Buffer, ComparePositions, Comparison, Completer, CompleterArg, FileTime, OpenOptions, Operations, OperationsRecord, PageNumber, Position], Heap USING [systemZone], Inline USING [HighHalf, LDIVMOD, LowHalf], MFile USING [ Acquire, DeleteWhenReleased, dontRelease, Error, GetLength, GetTimes, Handle, Release, SetAccess, SetLength, SetTimes], Process USING [Abort, EnableAborts, InitializeCondition, SecondsToTicks], Space USING [CopyIn, CopyOut, Window], SpecialMFile USING [GetCapaWithAccess], String USING [AppendString, EquivalentStrings], VMDefs USING [AccessFailure, CantOpen, Error, PageByteIndex, PageNumber]; PilotFileSystem: MONITOR IMPORTS FileDefs, Heap, Inline, MFile, Process, Space, SpecialMFile, String, VMDefs EXPORTS FileDefs = BEGIN OPEN FileDefs; -- Types -- FSInstance: PUBLIC TYPE = LONG POINTER TO FSObject; FSObject: PUBLIC TYPE = RECORD [ fileList: FileHandle ← NIL, ops: Operations ← NIL, loginCount: CARDINAL ← 0]; FileHandle: PUBLIC TYPE = LONG POINTER TO FileObject; FileObject: PUBLIC TYPE = RECORD [ pilotFile: File.File, mfh: MFile.Handle, link: FileHandle, name: LONG STRING, openCount: [0..77777B] ← 1, lengthKnown: BOOLEAN ← FALSE, length: Position]; IORequestPtr: TYPE = LONG POINTER TO IORequest; IORequest: TYPE = RECORD [ direction: Direction, file: FileHandle, filePage: PageNumber, buf: Buffer, completer: Completer, arg: CompleterArg, next: IORequestPtr]; qHead, qFree: IORequestPtr; Direction: TYPE = {read, write}; -- Constants -- bytesPerPage: CARDINAL = Environment.bytesPerPage; nRequests: CARDINAL = 5; ioInProgress: CARDINAL ← 0; -- Global variables -- ioRequestFreed, ioRequestWaiting: CONDITION; ioProcess: PROCESS; goingAway: BOOLEAN ← FALSE; IllegalDestroy: ERROR = CODE; IllegalExtend: ERROR = CODE; InsufficientLogouts: ERROR = CODE; InvalidFS: ERROR = CODE; InvalidFileHandle: ERROR = CODE; TooManyLogouts: ERROR = CODE; pilotFS: FSInstance; -- Procedures -- -- FileDefs.Operations implementing procedures -- PilotLogin: PUBLIC PROCEDURE [ server, userName, password, secondaryName, secondaryPassword: LONG STRING ← NIL] RETURNS [FSInstance] = { pilotFS.loginCount ← pilotFS.loginCount + 1; RETURN[pilotFS]}; PilotAbort, PilotCheckpoint: PUBLIC PROCEDURE [fs: FSInstance] = { -- unimplemented -- }; PilotLogout: PUBLIC PROCEDURE [fs: FSInstance] = BEGIN ValidateFS[fs]; IF pilotFS.loginCount = 0 THEN ERROR TooManyLogouts; pilotFS.loginCount ← pilotFS.loginCount - 1; END; PilotOpen: PUBLIC ENTRY PROCEDURE [ instance: FSInstance, name: LONG STRING, options: OpenOptions ← oldReadOnly] RETURNS [file: FileHandle] = BEGIN ENABLE UNWIND => NULL; error: VMDefs.AccessFailure ← ok; BEGIN fHandle: MFile.Handle; exists: BOOLEAN ← TRUE; ValidateFS[instance]; file ← FindFile[name]; IF file = NIL THEN fHandle ← MFile.Acquire[ name, anchor, MFile.dontRelease ! MFile.Error => SELECT code FROM noSuchFile => {exists ← FALSE; CONTINUE}; ENDCASE => {error ← other; GOTO OpenFailed}]; SELECT options FROM new => BEGIN IF exists THEN { MFile.Release[fHandle]; error ← alreadyExists; GOTO OpenFailed}; fHandle ← MFile.Acquire[ name, readWrite, MFile.dontRelease ! MFile.Error => {error ← other; GOTO OpenFailed}]; END; old, oldReadOnly => BEGIN IF file # NIL THEN GOTO AlreadyOpen; IF ~exists THEN {error ← notFound; GOTO OpenFailed}; MFile.SetAccess[fHandle, IF options = old THEN readWrite ELSE readOnly]; END; oldOrNew => BEGIN IF file # NIL THEN GOTO AlreadyOpen; IF exists THEN MFile.SetAccess[fHandle, readWrite] ELSE fHandle ← MFile.Acquire[ name, readWrite, MFile.dontRelease ! MFile.Error => {error ← other; GOTO OpenFailed}]; END; ENDCASE; file ← Heap.systemZone.NEW[FileObject]; file.mfh ← fHandle; file.name ← Heap.systemZone.NEW[StringBody [name.length]]; file.name.length ← 0; String.AppendString[file.name, name]; file.pilotFile ← SpecialMFile.GetCapaWithAccess[fHandle]; InsertFile[file]; EXITS OpenFailed => ERROR VMDefs.CantOpen[error]; AlreadyOpen => file.openCount ← file.openCount + 1; END; -- 'enabled' END; PilotAbandon, PilotClose: PUBLIC ENTRY PROCEDURE [file: FileHandle] = BEGIN ENABLE UNWIND => NULL; ValidateFile[file]; SELECT file.openCount FROM 0 => ERROR VMDefs.Error[other]; 1 => RemoveFile[file]; ENDCASE => file.openCount ← file.openCount - 1; END; PilotDestroy: PUBLIC ENTRY PROCEDURE [file: FileHandle] = BEGIN ENABLE UNWIND => NULL; ValidateFile[file]; IF file.openCount # 1 THEN ERROR IllegalDestroy; MFile.DeleteWhenReleased[file.mfh]; RemoveFile[file, TRUE]; END; PilotGetLength: PUBLIC ENTRY PROCEDURE [file: FileHandle] RETURNS [pos: Position] = BEGIN ENABLE UNWIND => NULL; ValidateFile[file]; IF file.lengthKnown THEN RETURN[file.length]; file.length ← MakePosition[MFile.GetLength[file.mfh]]; file.lengthKnown ← TRUE; RETURN[file.length]; END; PilotSetLength, PilotTruncate: PUBLIC ENTRY PROCEDURE [ file: FileHandle, pos: Position] = BEGIN ENABLE UNWIND => NULL; ValidateFile[file]; MFile.SetLength[file.mfh, LONG[pos.page] * bytesPerPage + pos.byte]; file.length ← pos; file.lengthKnown ← TRUE; END; PilotExtend: PUBLIC ENTRY PROCEDURE [ file: FileHandle, pos: Position, buf: Buffer] = BEGIN OPEN Space; ENABLE UNWIND => NULL; ValidateFile[file]; IF ~file.lengthKnown THEN { file.length ← MakePosition[MFile.GetLength[file.mfh]]; file.lengthKnown ← TRUE}; IF ComparePositions[pos, file.length] # greater OR ~(pos.page = file.length.page OR (pos.page = file.length.page + 1 AND pos.byte = 0)) THEN ERROR IllegalExtend; MFile.SetLength[file.mfh, LONG[pos.page] * bytesPerPage + pos.byte]; -- worry about out of space errors? [] ← Space.CopyOut[pointer: buf, window: [file.pilotFile, pos.page, 1]]; -- why does NSFileSystem do Inline.LongCOPY of buf to a space then CopyOut that space?? file.length ← pos; END; PilotStartRead: PUBLIC ENTRY PROCEDURE [ file: FileHandle, page: PageNumber, buf: Buffer, completer: Completer, arg: CompleterArg] = BEGIN ENABLE UNWIND => NULL; ValidateFile[file]; RequestTransfer[read, file, page, buf, completer, arg]; END; PilotStartWrite: PUBLIC ENTRY PROCEDURE [ file: FileHandle, page: PageNumber, buf: Buffer, completer: Completer, arg: CompleterArg] = BEGIN ENABLE UNWIND => NULL; ValidateFile[file]; RequestTransfer[write, file, page, buf, completer, arg]; END; PilotGetTimes: PUBLIC ENTRY PROCEDURE [file: FileHandle] RETURNS [read, write, create: FileTime] = BEGIN ENABLE UNWIND => NULL; ValidateFile[file]; [read: read, write: write, create: create] ← MFile.GetTimes[file.mfh]; END; PilotSetCreationTime: PUBLIC ENTRY PROCEDURE [ file: FileHandle, create: FileTime] = BEGIN ENABLE UNWIND => NULL; ValidateFile[file]; MFile.SetTimes[file: file.mfh, create: LOOPHOLE[create]]; END; -- Private procedures -- FindFile: INTERNAL PROCEDURE [name: LONG STRING] RETURNS [file: FileHandle] = BEGIN FOR file ← pilotFS.fileList, file.link UNTIL file = NIL DO IF String.EquivalentStrings[name, file.name] THEN RETURN ENDLOOP; END; InsertFile: INTERNAL PROCEDURE [file: FileHandle] = BEGIN file.link ← pilotFS.fileList; pilotFS.fileList ← file; END; MakePosition: INTERNAL PROCEDURE [byteLength: LONG CARDINAL] RETURNS [pos: Position] = { OPEN Inline; [quotient: pos.page, remainder: pos.byte] ← LDIVMOD[ numlow: LowHalf[byteLength], numhigh: HighHalf[byteLength], den: bytesPerPage]}; RemoveFile: INTERNAL PROCEDURE [ file: FileHandle, alreadyReleased: BOOLEAN ← FALSE] = BEGIN prev: LONG POINTER TO FileHandle ← @pilotFS.fileList; FOR cur: FileHandle ← pilotFS.fileList, cur.link UNTIL cur = NIL DO IF cur = file THEN {prev↑ ← file.link; EXIT}; prev ← @cur.link; REPEAT FINISHED => ERROR InvalidFileHandle; ENDLOOP; IF ~alreadyReleased THEN MFile.Release[file.mfh]; Heap.systemZone.FREE[@file]; END; ValidateFile: INTERNAL PROCEDURE [file: FileHandle] = BEGIN FOR f: FileHandle ← pilotFS.fileList, f.link UNTIL f = NIL DO IF f = file THEN RETURN ENDLOOP; ERROR InvalidFileHandle; END; -- Asynchronous I/O facility PageIOProcess: PROCEDURE = BEGIN GetIORequest: ENTRY PROCEDURE = BEGIN ENABLE UNWIND => NULL; WHILE QueueEmpty[] DO WAIT ioRequestWaiting ENDLOOP; req ← qHead↑; FreeIORequest[]; ioInProgress ← ioInProgress + 1; END; IOCompleted: ENTRY PROCEDURE = INLINE { ENABLE UNWIND => NULL; ioInProgress ← ioInProgress - 1; NOTIFY ioRequestFreed}; req: IORequest; w: Space.Window; UNTIL goingAway DO GetIORequest[ ! ABORTED => LOOP]; w ← [ file: req.file.pilotFile, base: req.filePage + 1, -- incr to skip over leader page count: 1]; SELECT req.direction FROM read => [] ← Space.CopyIn[pointer: req.buf, window: w]; write => [] ← Space.CopyOut[pointer: req.buf, window: w]; ENDCASE => ERROR; IOCompleted[]; req.completer[req.arg, ok]; ENDLOOP; END; -- proc. PageIOProcess RequestTransfer: INTERNAL PROCEDURE [ direction: Direction, file: FileHandle, page: PageNumber, buf: Buffer, completer: Completer, arg: CompleterArg] = BEGIN req: IORequestPtr; WHILE QueueFull[] DO WAIT ioRequestFreed ENDLOOP; req ← AllocateIORequest[]; req↑ ← [ direction: direction, file: file, filePage: page, buf: buf, completer: completer, arg: arg, next: req.next]; NOTIFY ioRequestWaiting; END; ValidateFS: PROCEDURE [fs: FSInstance] = {IF fs # pilotFS THEN ERROR InvalidFS}; -- IORequest management AllocateIORequest: INTERNAL PROC RETURNS [req: IORequestPtr] = INLINE { req ← qFree; IF (qFree ← qFree.next) = qHead THEN qFree ← NIL}; FreeIORequest: INTERNAL PROC = INLINE BEGIN IF QueueFull[] THEN qFree ← qHead; qHead ← qHead.next; NOTIFY ioRequestFreed; END; QueueFull: INTERNAL PROC RETURNS [BOOLEAN] = INLINE {RETURN[qFree = NIL]}; QueueEmpty: INTERNAL PROC RETURNS [BOOLEAN] = INLINE {RETURN[qHead = qFree]}; -- Initialize and finalize -- InitializeLocal: PUBLIC PROCEDURE RETURNS [ops: FileDefs.Operations] = BEGIN req, reqFirst: IORequestPtr; req ← reqFirst ← Heap.systemZone.NEW[IORequest]; THROUGH (0..nRequests) DO req ← req.next ← Heap.systemZone.NEW[IORequest] ENDLOOP; req.next ← reqFirst; -- close the queue (ring) of requests qHead ← qFree ← req; -- initial state = queue empty ioProcess ← FORK PageIOProcess[]; pilotFS ← Heap.systemZone.NEW[FSObject]; pilotFS.ops ← Heap.systemZone.NEW[ FileDefs.OperationsRecord ← [ login: LOOPHOLE[PilotLogin], logout: LOOPHOLE[PilotLogout], checkpoint: LOOPHOLE[PilotCheckpoint], abort: LOOPHOLE[PilotAbort], open: LOOPHOLE[PilotOpen], close: LOOPHOLE[PilotClose], abandon: LOOPHOLE[PilotAbandon], destroy: LOOPHOLE[PilotDestroy], getLength: LOOPHOLE[PilotGetLength], setLength: LOOPHOLE[PilotSetLength], extend: LOOPHOLE[PilotExtend], truncate: LOOPHOLE[PilotTruncate], startRead: LOOPHOLE[PilotStartRead], startWrite: LOOPHOLE[PilotStartWrite], getTimes: LOOPHOLE[PilotGetTimes], setCreation: LOOPHOLE[PilotSetCreationTime]]]; RETURN[pilotFS.ops]; END; FinalizeLocal: PUBLIC PROCEDURE = BEGIN IF pilotFS.loginCount # 0 THEN ERROR InsufficientLogouts; goingAway ← TRUE; Process.Abort[ioProcess]; JOIN ioProcess; Heap.systemZone.FREE[@pilotFS.ops]; Heap.systemZone.FREE[@pilotFS]; END; Process.InitializeCondition[@ioRequestWaiting, Process.SecondsToTicks[600]]; Process.InitializeCondition[@ioRequestFreed, Process.SecondsToTicks[600]]; Process.EnableAborts[@ioRequestWaiting]; END...