-- File: VMFile.mesa -- Last edited by Levin: 12-Apr-83 11:39:10 DIRECTORY AltoFile USING [ CloseDirectory, CloseFile, Delete, DirHandle, Enter, FileHandle, fileNameChars, FP, GetDiskSpace, IllegalFileName, LDPtr, Lookup, OpenDirectory, OpenFromFP, ReadLeaderPage, RewriteLeaderPage, sysDirFP, WaitForSpaceCrunch], DiskIODefs USING [DiskRequest], FileDefs USING [ ComparePositions, FileHandle, FinalizeAlto, FinalizeIFS, FinalizeJuniper, FSInstance, InitializeAlto, InitializeIFS, InitializeJuniper, Operations], StringDefs USING [AppendChar, AppendString, MesaToBcplString], VMDefs USING [ AccessFailure, CacheIndex, FileHandle, FileObject, FileSystemType, FileTime, FSAlto, OpenOptions, Page, PageNumber, Percentage, Position, Problem, ReadPage, Release, Start, TransactionProblem, Wait], VMPrivate USING [ closedSeal, EnumerateCachePagesInFile, FileHandle, FileObject, FileSystem, FSInstance, MDSPageToAddress, Object, openSeal, PageHandle, underwaySeal, ValidateFile, ValidatePageNumber, Writable], VMSpecial USING [], VMStorage USING [longTerm, shortTerm]; VMFile: MONITOR IMPORTS AltoFile, FileDefs, StringDefs, VMDefs, VMPrivate, VMStorage EXPORTS VMDefs, VMPrivate, VMSpecial = BEGIN OPEN VMDefs, VMPrivate; -- Global Variables -- fileList: FileHandle; ChangeInOpenState: CONDITION; cacheLimit: CacheIndex; fsOps: PUBLIC ARRAY FileSystemType OF FileDefs.Operations; altoFileSystem: 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; Login: PUBLIC PROCEDURE [system: FileSystemType, server, userName, password, secondaryName, secondaryPassword: STRING _ NIL] RETURNS [FileSystem] = BEGIN instance: FileDefs.FSInstance; IF fsOps[system] = NIL THEN ERROR UnableToLogin[other]; instance _ fsOps[system].login[server, userName, password, secondaryName, secondaryPassword]; RETURN[VMStorage.shortTerm.NEW[FSInstance _ [fsOps[system], instance]]] END; UnableToLogin: PUBLIC ERROR [reason: Problem] = CODE; Logout: PUBLIC PROCEDURE [fs: FileSystem] = BEGIN IF fs = FSAlto THEN RETURN; EnsureNoOpenFiles[fs]; fs.ops.logout[fs.instance]; VMStorage.shortTerm.FREE[@fs]; END; CheckpointTransaction: PUBLIC PROCEDURE [fs: FileSystem] = {fs.ops.checkpoint[fs.instance]}; TransactionError: PUBLIC ERROR [reason: TransactionProblem] = CODE; AbortTransaction: PUBLIC PROCEDURE [fs: FileSystem] = {fs.ops.abort[fs.instance]}; -- File handling -- OpenFile: PUBLIC PROCEDURE [ system: FileSystem _ FSAlto, name: STRING, options: OpenOptions _ oldReadOnly, cacheFraction: Percentage _ 0] RETURNS [vmFile: FileHandle] = BEGIN new: BOOLEAN; IF system = FSAlto THEN system _ altoFileSystem; [new, vmFile] _ AddToList[system.ops.open[system.instance, name, options], options]; IF new THEN BEGIN vmFile.fs _ system; vmFile.cachePages _ (cacheFraction*cacheLimit)/100; vmFile.altoFile _ (system.ops = fsOps[Alto]); END; END; CantOpen: PUBLIC ERROR [reason: AccessFailure] = CODE; CloseFile: PUBLIC PROCEDURE [file: FileHandle] = {StartFile[file]; DoCloseDestroyOrAbandon[file, close]}; GetOpenFileParameters: PUBLIC PROCEDURE [file: FileHandle] RETURNS [system: FileSystem, options: OpenOptions, cacheFraction: Percentage] = {RETURN[ IF file.fs = altoFileSystem THEN FSAlto ELSE file.fs, file.options, (file.cachePages*100)/cacheLimit]}; AbandonFile: PUBLIC PROCEDURE [file: FileHandle] = {DoCloseDestroyOrAbandon[file, abandon]}; DestroyFile: PUBLIC PROCEDURE [file: FileHandle] = -- At present, the StartFile is necessary to avoid a difficult back-out later. -- It may generate unnecessary I/O, but it is otherwise harmless. {StartFile[file]; DoCloseDestroyOrAbandon[file, destroy]}; GetFileLength: PUBLIC PROCEDURE [file: FileHandle] RETURNS [length: Position] = BEGIN ValidateFile[file]; RETURN[file.fs.ops.getLength[file.fh]] END; SetFileLength: PUBLIC PROCEDURE [file: FileHandle, position: Position] = BEGIN oldLength: Position = GetFileLength[file]; 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]; 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 = ReadPage[[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 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[MDSPageToAddress[page.buffer]]; RETURN[FALSE, FALSE]}; ValidateFile[file]; [] _ EnumerateCachePagesInFile[file, StartPage]; END; WaitFile: PUBLIC PROCEDURE [file: FileHandle] = BEGIN WaitPage: PROCEDURE [page: PageHandle] RETURNS [found, unmap: BOOLEAN] = -- waits until 'page' is in a stable state. {Wait[MDSPageToAddress[page.buffer]]; RETURN[FALSE, FALSE]}; ValidateFile[file]; [] _ EnumerateCachePagesInFile[file, WaitPage]; END; GetFileTimes: PUBLIC PROCEDURE [file: FileHandle] RETURNS [read, write, create: VMDefs.FileTime] = {[read, write, create] _ file.fs.ops.getTimes[file.fh]}; SetCreationTime: PUBLIC PROCEDURE [file: FileHandle, create: FileTime _ 0] = {file.fs.ops.setCreation[file.fh, create]}; -- Procedures and Signals Exported to VMSpecial -- OpenAltoFileFromFP: PUBLIC PROCEDURE [ fp: AltoFile.FP, writable: BOOLEAN, cacheFraction: Percentage _ 0] RETURNS [vmFile: FileHandle] = BEGIN new: BOOLEAN; options: OpenOptions = IF writable THEN old ELSE oldReadOnly; [new, vmFile] _ AddToList[AltoFile.OpenFromFP[fp, writable], options]; IF new THEN BEGIN vmFile.fs _ altoFileSystem; vmFile.cachePages _ (cacheFraction*cacheLimit)/100; vmFile.altoFile _ TRUE; END; END; QuickAndDirtyAltoRename: PUBLIC PROCEDURE [old, new: STRING] RETURNS [worked: BOOLEAN] = BEGIN OPEN AltoFile; dir: DirHandle = OpenDirectory[sysDirFP]; fp: FP; ValidRename: PROCEDURE RETURNS [BOOLEAN] = INLINE BEGIN ENABLE IllegalFileName => GO TO failed; RETURN[Lookup[dir, old, @fp].found AND Enter[dir, new, @fp]] EXITS failed => RETURN[FALSE]; END; IF (worked _ ValidRename[]) THEN BEGIN leader: LDPtr; request: DiskIODefs.DiskRequest; fullName: STRING _ [fileNameChars]; file: FileHandle = OpenFromFP[fp: fp, markWritten: FALSE]; leader _ ReadLeaderPage[file, @request]; StringDefs.AppendString[fullName, new]; IF fullName[fullName.length-1] ~= '. THEN StringDefs.AppendChar[fullName, '.]; StringDefs.MesaToBcplString[fullName, LOOPHOLE[@leader.name]]; RewriteLeaderPage[file, @request, leader]; CloseFile[file]; [] _ Delete[dir, old]; END; CloseDirectory[dir]; END; WaitForAltoDiskSpaceCrunch: PUBLIC PROCEDURE [pages: CARDINAL] RETURNS [BOOLEAN] = {RETURN[AltoFile.WaitForSpaceCrunch[pages]]}; GetAltoDiskSpace: PUBLIC PROCEDURE [wait: BOOLEAN] RETURNS [pages: CARDINAL, ok: BOOLEAN] = {[pages, ok] _ AltoFile.GetDiskSpace[wait]}; -- Procedures and Signals Exported to VMPrivate -- InitializeVMFile: PUBLIC PROCEDURE [maxCachePages: CacheIndex] = BEGIN fileList _ NIL; cacheLimit _ maxCachePages; fsOps[Alto] _ FileDefs.InitializeAlto[]; fsOps[IFS] _ FileDefs.InitializeIFS[]; fsOps[Juniper] _ FileDefs.InitializeJuniper[]; altoFileSystem _ VMStorage.longTerm.NEW[FSInstance _ FSInstance[fsOps[Alto], NIL]]; altoFileSystem.instance _ fsOps[Alto].login[]; END; FinalizeVMFile: PUBLIC PROCEDURE = BEGIN fsOps[Alto].logout[altoFileSystem.instance]; VMStorage.longTerm.FREE[@altoFileSystem]; IF fileList ~= NIL THEN ERROR FileInUse; FileDefs.FinalizeJuniper[]; FileDefs.FinalizeIFS[]; FileDefs.FinalizeAlto[]; END; InvalidFile: PUBLIC ERROR = CODE; InvalidPageNumber: PUBLIC ERROR = CODE; -- Internal Procedures -- AddToList: ENTRY PROCEDURE [fFile: FileDefs.FileHandle, options: OpenOptions] RETURNS [newlyEntered: BOOLEAN, vmFile: FileHandle] = -- ensures that only a single entry for file 'fFile' appears in the fileList. 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 _ VMStorage.shortTerm.NEW[FileObject _ Object[file[ seal: openSeal, useCount: 0, link: fileList, fh: fFile, fs: , cachePages: 0, options: options, altoFile: , openCount: 1]]]; fileList _ vmFile; newlyEntered _ TRUE; END; DoCloseDestroyOrAbandon: PROCEDURE [file: FileHandle, op: {close, destroy, abandon}] = -- eliminates 'file' from file list and cleans it up as indicated by 'op'. BEGIN last: BOOLEAN; LockIfLastReference: ENTRY 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: ENTRY 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: 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 WaitFile[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[]; VMStorage.shortTerm.FREE[@file]; END; EnsureNoOpenFiles: ENTRY 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.