-- Copyright (C) 1981, 1982, 1984, 1985 by Xerox Corporation. All rights reserved. -- File: VMFile.mesa -- HGM, 16-Sep-85 21:49:54 -- Last edited by Wobber: 2-Nov-82 12:27:34 -- Last edited by Gobbel: 11-Sep-81 16:41:44 DIRECTORY FileDefs USING [ ComparePositions, FileHandle, FinalizeLocal, FinalizeIFS, FSInstance, InitializeLocal, InitializeIFS, Operations], Heap USING [systemZone], LogDefs USING [ShowTwoStrings], Time USING [Current], VMDefs USING [ AccessFailure, defaultTime, FileHandle, FileSystemType, FileTime, OpenOptions, Page, PageNumber, Percentage, Position, Problem, Release, Start, UsePage, Wait], VMPrivate USING [ CacheIndex, closedSeal, EnumerateCachePagesInFile, FileHandle, FileObject, FileSystem, FSInstance, Object, openSeal, PageHandle, underwaySeal, ValidateFile, ValidatePageNumber, Writable]; VMFile: MONITOR IMPORTS FileDefs, Heap, LogDefs, Time, VMDefs, VMPrivate EXPORTS VMDefs, VMPrivate = BEGIN OPEN VMDefs, VMPrivate; -- Global Variables -- fileList: FileHandle; ChangeInOpenState: CONDITION; cacheLimit: CacheIndex; fsOps: PUBLIC ARRAY FileSystemType OF FileDefs.Operations; pilotFileSystem: FileSystem; -- Miscellaneous Declarations -- CantDestroyReadOnlyFile: ERROR = CODE; FileInUse: ERROR = CODE; FileNotInList: ERROR = CODE; FileListSmashed: ERROR = CODE; LogoutIllegalWithOpenFiles: ERROR = CODE; PageInUse: ERROR = CODE; UseCountWrong: ERROR = CODE; -- Procedures, Signals, and Types Exported to VMDefs -- FileObject: PUBLIC TYPE = VMPrivate.FileObject; FSInstance: PUBLIC TYPE = VMPrivate.FSInstance; -- File system access -- Error: PUBLIC ERROR [reason: Problem] = CODE; UnableToLogin: PUBLIC ERROR [reason: Problem] = CODE; Login: PUBLIC ENTRY PROCEDURE [ system: FileSystemType, server, userName, password, secondaryName, secondaryPassword: LONG STRING ← NIL] RETURNS [FileSystem] = BEGIN ENABLE UNWIND => NULL; instance: FileDefs.FSInstance; IF fsOps[system] = NIL THEN ERROR UnableToLogin[other]; instance ← fsOps[system].login[ server, userName, password, secondaryName, secondaryPassword]; RETURN[Heap.systemZone.NEW[FSInstance ← [fsOps[system], instance]]] END; Logout: PUBLIC ENTRY PROCEDURE [fs: FileSystem] = BEGIN ENABLE UNWIND => NULL; EnsureNoOpenFiles[fs]; fs.ops.logout[fs.instance]; Heap.systemZone.FREE[@fs]; END; AbortTransaction, CheckpointTransaction: PUBLIC PROCEDURE [fs: FileSystem] = {}; -- File handling -- OpenFile: PUBLIC PROCEDURE [ system: FileSystem ← NIL, name: LONG STRING, options: OpenOptions ← oldReadOnly, cacheFraction: Percentage ← 0] RETURNS [vmFile: FileHandle] = BEGIN ENABLE UNWIND => LogDefs.ShowTwoStrings["Can't open "L, name]; IF system = NIL THEN system ← pilotFileSystem; RETURN[OpenFileInner[system, name, options, cacheFraction]]; END; OpenFileInner: ENTRY PROCEDURE [ system: FileSystem, name: LONG STRING, options: OpenOptions ← oldReadOnly, cacheFraction: Percentage ← 0] RETURNS [vmFile: FileHandle] = BEGIN ENABLE UNWIND => NULL; new: BOOLEAN; [new, vmFile] ← AddToList[ system.ops.open[system.instance, name, options], options]; IF new THEN { vmFile.fs ← system; vmFile.cachePages ← (cacheFraction * cacheLimit) / 100}; END; CantOpen: PUBLIC ERROR [reason: AccessFailure] = CODE; CloseFile: PUBLIC ENTRY PROCEDURE [file: FileHandle] = { ENABLE UNWIND => NULL; StartFileInternal[file]; DoCloseDestroyOrAbandon[file, close]}; GetOpenFileParameters: PUBLIC ENTRY PROCEDURE [file: FileHandle] RETURNS [ system: FileSystem, options: OpenOptions, cacheFraction: Percentage] = { RETURN[file.fs, file.options, (file.cachePages * 100) / cacheLimit]}; AbandonFile: PUBLIC ENTRY PROCEDURE [file: FileHandle] = { ENABLE UNWIND => NULL; DoCloseDestroyOrAbandon[file, abandon]}; DestroyFile: PUBLIC ENTRY PROCEDURE [file: FileHandle] = { ENABLE UNWIND => NULL; StartFileInternal[file]; DoCloseDestroyOrAbandon[file, destroy]}; GetFileLength: PUBLIC ENTRY PROCEDURE [file: FileHandle] RETURNS [length: Position] = { ENABLE UNWIND => NULL; ValidateFile[file]; RETURN[file.fs.ops.getLength[file.fh]]}; SetFileLength: PUBLIC ENTRY PROCEDURE [file: FileHandle, position: Position] = BEGIN ENABLE UNWIND => NULL; oldLength: Position; atomicExtend: BOOLEAN ← FALSE; FlushTruncatedPage: PROCEDURE [page: PageHandle] RETURNS [found, unmap: BOOLEAN] = -- checks to see if 'page' should be flushed because of file truncation. BEGIN found ← unmap ← FALSE; IF page.page < position.page OR (page.page = position.page AND position.byte > 0) THEN RETURN; IF page.useCount ~= 1 THEN ERROR PageInUse; -- useCount = 1 from enumeration unmap ← TRUE; END; ValidateFile[file]; oldLength ← file.fs.ops.getLength[file.fh]; ValidatePageNumber[position.page]; SELECT FileDefs.ComparePositions[oldLength, position] FROM greater => [] ← EnumerateCachePagesInFile[file, FlushTruncatedPage]; equal => RETURN; less => atomicExtend ← oldLength.byte # 0 AND (oldLength.page = position.page OR position = Position[oldLength.page + 1, 0]); ENDCASE; IF atomicExtend THEN BEGIN data: Page = UsePage[[file, oldLength.page]]; file.fs.ops.extend[file.fh, position, data ! Error => Release[data]]; Release[data]; END ELSE file.fs.ops.setLength[file.fh, position]; END; StartFile: PUBLIC ENTRY PROCEDURE [file: FileHandle] = { ENABLE UNWIND => NULL; StartFileInternal[file]}; StartFileInternal: INTERNAL PROCEDURE [file: FileHandle] = BEGIN StartPage: PROCEDURE [page: PageHandle] RETURNS [found, unmap: BOOLEAN] = -- if 'page' is dirty, initiates a transfer of 'page' to the file. {Start[page.pointer]; RETURN[FALSE, FALSE]}; ValidateFile[file]; [] ← EnumerateCachePagesInFile[file, StartPage]; END; WaitFile: PUBLIC ENTRY PROCEDURE [file: FileHandle] = { ENABLE UNWIND => NULL; WaitFileInternal[file]}; WaitFileInternal: INTERNAL PROCEDURE [file: FileHandle] = BEGIN WaitPage: PROCEDURE [page: PageHandle] RETURNS [found, unmap: BOOLEAN] = -- waits until 'page' is in a stable state. {Wait[page.pointer]; RETURN[FALSE, FALSE]}; ValidateFile[file]; [] ← EnumerateCachePagesInFile[file, WaitPage]; END; GetFileTimes: PUBLIC ENTRY PROCEDURE [file: FileHandle] RETURNS [read, write, create: VMDefs.FileTime] = { ENABLE UNWIND => NULL; [read, write, create] ← file.fs.ops.getTimes[file.fh]}; SetCreationTime: PUBLIC ENTRY PROCEDURE [ file: FileHandle, create: FileTime ← defaultTime] = BEGIN ENABLE UNWIND => NULL; IF create = defaultTime THEN create ← Time.Current[]; file.fs.ops.setCreation[file.fh, create]; END; -- Procedures and Signals Exported to VMPrivate -- InitializeVMFile: PUBLIC PROCEDURE [maxCachePages: CacheIndex] = BEGIN fileList ← NIL; cacheLimit ← maxCachePages; fsOps[local] ← FileDefs.InitializeLocal[]; pilotFileSystem ← Heap.systemZone.NEW[FSInstance ← FSInstance[fsOps[local], NIL]]; pilotFileSystem.instance ← fsOps[local].login["Gobbel"L, "Randy"L]; fsOps[ifs] ← FileDefs.InitializeIFS[]; END; FinalizeVMFile: PUBLIC PROCEDURE = BEGIN fsOps[local].logout[pilotFileSystem.instance]; Heap.systemZone.FREE[@pilotFileSystem]; IF fileList ~= NIL THEN ERROR FileInUse; FileDefs.FinalizeIFS[]; FileDefs.FinalizeLocal[]; END; InvalidFile: PUBLIC ERROR = CODE; InvalidPageNumber: PUBLIC ERROR = CODE; -- Internal Procedures -- AddToList: INTERNAL PROCEDURE [fFile: FileDefs.FileHandle, options: OpenOptions] RETURNS [newlyEntered: BOOLEAN, vmFile: FileHandle] = -- ensures that only a single entry for file 'fFile' appears in the fileList. INLINE --only called from OpenFile-- BEGIN writable: BOOLEAN = options ~= oldReadOnly; newlyEntered ← FALSE; vmFile ← fileList; UNTIL vmFile = NIL DO IF vmFile.fh = fFile THEN SELECT vmFile.seal FROM openSeal => IF writable THEN RETURN WITH ERROR CantOpen[accessDenied] ELSE {vmFile.openCount ← vmFile.openCount + 1; RETURN}; underwaySeal => {WAIT ChangeInOpenState; vmFile ← fileList}; ENDCASE => GO TO bogusList ELSE SELECT vmFile.seal FROM openSeal, underwaySeal => vmFile ← vmFile.link; ENDCASE => GO TO bogusList; REPEAT bogusList => ERROR FileListSmashed; ENDLOOP; vmFile ← Heap.systemZone.NEW[ FileObject ← Object[ file[ seal: openSeal, useCount: 0, link: fileList, fh: fFile, fs:, cachePages: 0, options: options, altoFile: TRUE, openCount: 1]]]; fileList ← vmFile; newlyEntered ← TRUE; END; DoCloseDestroyOrAbandon: INTERNAL PROCEDURE [ file: FileHandle, op: {close, destroy, abandon}] = -- eliminates 'file' from file list and cleans it up as indicated by 'op'. BEGIN last: BOOLEAN; LockIfLastReference: INTERNAL PROCEDURE RETURNS [last: BOOLEAN] = INLINE BEGIN IF file.seal ~= openSeal OR file.openCount = 0 THEN ERROR InvalidFile; IF (last ← file.openCount = 1) THEN file.seal ← underwaySeal ELSE file.openCount ← file.openCount - 1; END; RemoveFile: INTERNAL PROCEDURE = INLINE BEGIN IF file = fileList THEN fileList ← file.link ELSE BEGIN IF fileList = NIL THEN GO TO Trouble; FOR prev: FileHandle ← fileList, prev.link UNTIL prev.link = NIL DO IF prev.link = file THEN {prev.link ← file.link; EXIT}; REPEAT FINISHED => GO TO Trouble; ENDLOOP; EXITS Trouble => ERROR FileNotInList; END; file.seal ← closedSeal; -- try to catch dangling references BROADCAST ChangeInOpenState; END; RemovePage: INTERNAL PROCEDURE [page: PageHandle] RETURNS [found, unmap: BOOLEAN] = BEGIN -- useCount = 1 in the following because of the enumeration IF op = abandon THEN page.useCount ← 1 -- force unmap to take effect ELSE IF page.useCount ~= 1 OR page.dirty THEN ERROR PageInUse; RETURN[FALSE, TRUE] END; IF op ~= abandon THEN WaitFileInternal[file]; last ← LockIfLastReference[]; SELECT op FROM close => file.fs.ops.close[file.fh]; destroy => IF Writable[file] THEN file.fs.ops.destroy[file.fh] ELSE ERROR CantDestroyReadOnlyFile; ENDCASE => file.fs.ops.abandon[file.fh]; IF ~last THEN RETURN; [] ← EnumerateCachePagesInFile[file, RemovePage]; IF file.useCount ~= 0 THEN ERROR UseCountWrong; RemoveFile[]; Heap.systemZone.FREE[@file]; END; EnsureNoOpenFiles: INTERNAL PROCEDURE [fs: FileSystem] = BEGIN vmFile: FileHandle ← fileList; UNTIL vmFile = NIL DO IF vmFile.fs = fs THEN ERROR LogoutIllegalWithOpenFiles ELSE SELECT vmFile.seal FROM openSeal, underwaySeal => vmFile ← vmFile.link; ENDCASE => ERROR FileListSmashed; ENDLOOP; END; END.