-- 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.