-- File: VMFile.mesa -- Last edited by Levin: 24-Aug-82 14:26:39 DIRECTORY AltoFile USING [ CloseDirectory, CloseFile, Delete, DirHandle, Enter, FileHandle, fileNameChars, FP, 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]]}; -- 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.