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