-- Copyright (C) 1981, 1982, 1984, 1985 by Xerox Corporation. All rights reserved.
-- PilotFileSystem.mesa
-- HGM, 16-Sep-85 22:01:34
-- Wobber, 3-Nov-82 11:30:48
-- Gobbel, 25-Sep-81 15:24:50
-- MJohnson, 1-Feb-82 15:03:33
-- Hankins 8-Aug-84 9:52:49 (Klamath update - space rework)
-- TO DO:
-- - Remove LOOPHOLEs
DIRECTORY
Environment USING [bytesPerPage],
File USING [File],
FileDefs USING [
Buffer, ComparePositions, Comparison, Completer, CompleterArg, FileTime,
OpenOptions, Operations, OperationsRecord, PageNumber, Position],
Heap USING [systemZone],
Inline USING [HighHalf, LDIVMOD, LowHalf],
MFile USING [
Acquire, DeleteWhenReleased, dontRelease, Error, GetLength, GetTimes, Handle,
Release, SetAccess, SetLength, SetTimes],
Process USING [Abort, EnableAborts, InitializeCondition, SecondsToTicks],
Space USING [CopyIn, CopyOut, Window],
SpecialMFile USING [GetCapaWithAccess],
String USING [AppendString, EquivalentStrings],
VMDefs USING [AccessFailure, CantOpen, Error, PageByteIndex, PageNumber];
PilotFileSystem: MONITOR
IMPORTS
FileDefs, Heap, Inline, MFile, Process, Space, SpecialMFile, String, VMDefs
EXPORTS FileDefs =
BEGIN OPEN FileDefs;
-- Types --
FSInstance: PUBLIC TYPE = LONG POINTER TO FSObject;
FSObject: PUBLIC TYPE = RECORD [
fileList: FileHandle ← NIL, ops: Operations ← NIL, loginCount: CARDINAL ← 0];
FileHandle: PUBLIC TYPE = LONG POINTER TO FileObject;
FileObject: PUBLIC TYPE = RECORD [
pilotFile: File.File,
mfh: MFile.Handle,
link: FileHandle,
name: LONG STRING,
openCount: [0..77777B] ← 1,
lengthKnown: BOOLEAN ← FALSE,
length: Position];
IORequestPtr: TYPE = LONG POINTER TO IORequest;
IORequest: TYPE = RECORD [
direction: Direction,
file: FileHandle,
filePage: PageNumber,
buf: Buffer,
completer: Completer,
arg: CompleterArg,
next: IORequestPtr];
qHead, qFree: IORequestPtr;
Direction: TYPE = {read, write};
-- Constants --
bytesPerPage: CARDINAL = Environment.bytesPerPage;
nRequests: CARDINAL = 5;
ioInProgress: CARDINAL ← 0;
-- Global variables --
ioRequestFreed, ioRequestWaiting: CONDITION;
ioProcess: PROCESS;
goingAway: BOOLEAN ← FALSE;
IllegalDestroy: ERROR = CODE;
IllegalExtend: ERROR = CODE;
InsufficientLogouts: ERROR = CODE;
InvalidFS: ERROR = CODE;
InvalidFileHandle: ERROR = CODE;
TooManyLogouts: ERROR = CODE;
pilotFS: FSInstance;
-- Procedures --
-- FileDefs.Operations implementing procedures --
PilotLogin: PUBLIC PROCEDURE [
server, userName, password, secondaryName, secondaryPassword: LONG STRING ← NIL]
RETURNS [FSInstance] = {
pilotFS.loginCount ← pilotFS.loginCount + 1; RETURN[pilotFS]};
PilotAbort, PilotCheckpoint: PUBLIC PROCEDURE [fs: FSInstance] = {
-- unimplemented -- };
PilotLogout: PUBLIC PROCEDURE [fs: FSInstance] =
BEGIN
ValidateFS[fs];
IF pilotFS.loginCount = 0 THEN ERROR TooManyLogouts;
pilotFS.loginCount ← pilotFS.loginCount - 1;
END;
PilotOpen: PUBLIC ENTRY PROCEDURE [
instance: FSInstance, name: LONG STRING, options: OpenOptions ← oldReadOnly]
RETURNS [file: FileHandle] =
BEGIN
ENABLE UNWIND => NULL;
error: VMDefs.AccessFailure ← ok;
BEGIN
fHandle: MFile.Handle;
exists: BOOLEAN ← TRUE;
ValidateFS[instance];
file ← FindFile[name];
IF file = NIL THEN
fHandle ← MFile.Acquire[
name, anchor, MFile.dontRelease !
MFile.Error =>
SELECT code FROM
noSuchFile => {exists ← FALSE; CONTINUE};
ENDCASE => {error ← other; GOTO OpenFailed}];
SELECT options FROM
new =>
BEGIN
IF exists THEN {
MFile.Release[fHandle]; error ← alreadyExists; GOTO OpenFailed};
fHandle ← MFile.Acquire[
name, readWrite, MFile.dontRelease !
MFile.Error => {error ← other; GOTO OpenFailed}];
END;
old, oldReadOnly =>
BEGIN
IF file # NIL THEN GOTO AlreadyOpen;
IF ~exists THEN {error ← notFound; GOTO OpenFailed};
MFile.SetAccess[fHandle, IF options = old THEN readWrite ELSE readOnly];
END;
oldOrNew =>
BEGIN
IF file # NIL THEN GOTO AlreadyOpen;
IF exists THEN MFile.SetAccess[fHandle, readWrite]
ELSE
fHandle ← MFile.Acquire[
name, readWrite, MFile.dontRelease !
MFile.Error => {error ← other; GOTO OpenFailed}];
END;
ENDCASE;
file ← Heap.systemZone.NEW[FileObject];
file.mfh ← fHandle;
file.name ← Heap.systemZone.NEW[StringBody [name.length]];
file.name.length ← 0;
String.AppendString[file.name, name];
file.pilotFile ← SpecialMFile.GetCapaWithAccess[fHandle];
InsertFile[file];
EXITS
OpenFailed => ERROR VMDefs.CantOpen[error];
AlreadyOpen => file.openCount ← file.openCount + 1;
END; -- 'enabled'
END;
PilotAbandon, PilotClose: PUBLIC ENTRY PROCEDURE [file: FileHandle] =
BEGIN
ENABLE UNWIND => NULL;
ValidateFile[file];
SELECT file.openCount FROM
0 => ERROR VMDefs.Error[other];
1 => RemoveFile[file];
ENDCASE => file.openCount ← file.openCount - 1;
END;
PilotDestroy: PUBLIC ENTRY PROCEDURE [file: FileHandle] =
BEGIN
ENABLE UNWIND => NULL;
ValidateFile[file];
IF file.openCount # 1 THEN ERROR IllegalDestroy;
MFile.DeleteWhenReleased[file.mfh];
RemoveFile[file, TRUE];
END;
PilotGetLength: PUBLIC ENTRY PROCEDURE [file: FileHandle]
RETURNS [pos: Position] =
BEGIN
ENABLE UNWIND => NULL;
ValidateFile[file];
IF file.lengthKnown THEN RETURN[file.length];
file.length ← MakePosition[MFile.GetLength[file.mfh]];
file.lengthKnown ← TRUE;
RETURN[file.length];
END;
PilotSetLength, PilotTruncate: PUBLIC ENTRY PROCEDURE [
file: FileHandle, pos: Position] =
BEGIN
ENABLE UNWIND => NULL;
ValidateFile[file];
MFile.SetLength[file.mfh, LONG[pos.page] * bytesPerPage + pos.byte];
file.length ← pos;
file.lengthKnown ← TRUE;
END;
PilotExtend: PUBLIC ENTRY PROCEDURE [
file: FileHandle, pos: Position, buf: Buffer] =
BEGIN OPEN Space;
ENABLE UNWIND => NULL;
ValidateFile[file];
IF ~file.lengthKnown THEN {
file.length ← MakePosition[MFile.GetLength[file.mfh]];
file.lengthKnown ← TRUE};
IF ComparePositions[pos, file.length] # greater
OR
~(pos.page = file.length.page
OR (pos.page = file.length.page + 1 AND pos.byte = 0)) THEN
ERROR IllegalExtend;
MFile.SetLength[file.mfh, LONG[pos.page] * bytesPerPage + pos.byte];
-- worry about out of space errors?
[] ← Space.CopyOut[pointer: buf, window: [file.pilotFile, pos.page, 1]];
-- why does NSFileSystem do Inline.LongCOPY of buf to a space then CopyOut that space??
file.length ← pos;
END;
PilotStartRead: PUBLIC ENTRY PROCEDURE [
file: FileHandle, page: PageNumber, buf: Buffer, completer: Completer,
arg: CompleterArg] =
BEGIN
ENABLE UNWIND => NULL;
ValidateFile[file];
RequestTransfer[read, file, page, buf, completer, arg];
END;
PilotStartWrite: PUBLIC ENTRY PROCEDURE [
file: FileHandle, page: PageNumber, buf: Buffer, completer: Completer,
arg: CompleterArg] =
BEGIN
ENABLE UNWIND => NULL;
ValidateFile[file];
RequestTransfer[write, file, page, buf, completer, arg];
END;
PilotGetTimes: PUBLIC ENTRY PROCEDURE [file: FileHandle]
RETURNS [read, write, create: FileTime] =
BEGIN
ENABLE UNWIND => NULL;
ValidateFile[file];
[read: read, write: write, create: create] ← MFile.GetTimes[file.mfh];
END;
PilotSetCreationTime: PUBLIC ENTRY PROCEDURE [
file: FileHandle, create: FileTime] =
BEGIN
ENABLE UNWIND => NULL;
ValidateFile[file];
MFile.SetTimes[file: file.mfh, create: LOOPHOLE[create]];
END;
-- Private procedures --
FindFile: INTERNAL PROCEDURE [name: LONG STRING] RETURNS [file: FileHandle] =
BEGIN
FOR file ← pilotFS.fileList, file.link UNTIL file = NIL DO
IF String.EquivalentStrings[name, file.name] THEN RETURN ENDLOOP;
END;
InsertFile: INTERNAL PROCEDURE [file: FileHandle] =
BEGIN file.link ← pilotFS.fileList; pilotFS.fileList ← file; END;
MakePosition: INTERNAL PROCEDURE [byteLength: LONG CARDINAL]
RETURNS [pos: Position] = {
OPEN Inline;
[quotient: pos.page, remainder: pos.byte] ← LDIVMOD[
numlow: LowHalf[byteLength], numhigh: HighHalf[byteLength],
den: bytesPerPage]};
RemoveFile: INTERNAL PROCEDURE [
file: FileHandle, alreadyReleased: BOOLEAN ← FALSE] =
BEGIN
prev: LONG POINTER TO FileHandle ← @pilotFS.fileList;
FOR cur: FileHandle ← pilotFS.fileList, cur.link UNTIL cur = NIL DO
IF cur = file THEN {prev↑ ← file.link; EXIT};
prev ← @cur.link;
REPEAT FINISHED => ERROR InvalidFileHandle;
ENDLOOP;
IF ~alreadyReleased THEN MFile.Release[file.mfh];
Heap.systemZone.FREE[@file];
END;
ValidateFile: INTERNAL PROCEDURE [file: FileHandle] =
BEGIN
FOR f: FileHandle ← pilotFS.fileList, f.link UNTIL f = NIL DO
IF f = file THEN RETURN ENDLOOP;
ERROR InvalidFileHandle;
END;
-- Asynchronous I/O facility
PageIOProcess: PROCEDURE =
BEGIN
GetIORequest: ENTRY PROCEDURE =
BEGIN
ENABLE UNWIND => NULL;
WHILE QueueEmpty[] DO WAIT ioRequestWaiting ENDLOOP;
req ← qHead↑;
FreeIORequest[];
ioInProgress ← ioInProgress + 1;
END;
IOCompleted: ENTRY PROCEDURE = INLINE {
ENABLE UNWIND => NULL;
ioInProgress ← ioInProgress - 1;
NOTIFY ioRequestFreed};
req: IORequest;
w: Space.Window;
UNTIL goingAway DO
GetIORequest[ ! ABORTED => LOOP];
w ← [
file: req.file.pilotFile, base: req.filePage + 1, -- incr to skip over leader page
count: 1];
SELECT req.direction FROM
read => [] ← Space.CopyIn[pointer: req.buf, window: w];
write => [] ← Space.CopyOut[pointer: req.buf, window: w];
ENDCASE => ERROR;
IOCompleted[];
req.completer[req.arg, ok];
ENDLOOP;
END; -- proc. PageIOProcess
RequestTransfer: INTERNAL PROCEDURE [
direction: Direction, file: FileHandle, page: PageNumber, buf: Buffer,
completer: Completer, arg: CompleterArg] =
BEGIN
req: IORequestPtr;
WHILE QueueFull[] DO WAIT ioRequestFreed ENDLOOP;
req ← AllocateIORequest[];
req↑ ← [
direction: direction, file: file, filePage: page, buf: buf,
completer: completer, arg: arg, next: req.next];
NOTIFY ioRequestWaiting;
END;
ValidateFS: PROCEDURE [fs: FSInstance] = {IF fs # pilotFS THEN ERROR InvalidFS};
-- IORequest management
AllocateIORequest: INTERNAL PROC RETURNS [req: IORequestPtr] = INLINE {
req ← qFree; IF (qFree ← qFree.next) = qHead THEN qFree ← NIL};
FreeIORequest: INTERNAL PROC = INLINE
BEGIN
IF QueueFull[] THEN qFree ← qHead;
qHead ← qHead.next;
NOTIFY ioRequestFreed;
END;
QueueFull: INTERNAL PROC RETURNS [BOOLEAN] = INLINE {RETURN[qFree = NIL]};
QueueEmpty: INTERNAL PROC RETURNS [BOOLEAN] = INLINE {RETURN[qHead = qFree]};
-- Initialize and finalize --
InitializeLocal: PUBLIC PROCEDURE RETURNS [ops: FileDefs.Operations] =
BEGIN
req, reqFirst: IORequestPtr;
req ← reqFirst ← Heap.systemZone.NEW[IORequest];
THROUGH (0..nRequests) DO
req ← req.next ← Heap.systemZone.NEW[IORequest] ENDLOOP;
req.next ← reqFirst; -- close the queue (ring) of requests
qHead ← qFree ← req; -- initial state = queue empty
ioProcess ← FORK PageIOProcess[];
pilotFS ← Heap.systemZone.NEW[FSObject];
pilotFS.ops ← Heap.systemZone.NEW[
FileDefs.OperationsRecord ← [
login: LOOPHOLE[PilotLogin], logout: LOOPHOLE[PilotLogout],
checkpoint: LOOPHOLE[PilotCheckpoint], abort: LOOPHOLE[PilotAbort],
open: LOOPHOLE[PilotOpen], close: LOOPHOLE[PilotClose],
abandon: LOOPHOLE[PilotAbandon], destroy: LOOPHOLE[PilotDestroy],
getLength: LOOPHOLE[PilotGetLength], setLength: LOOPHOLE[PilotSetLength],
extend: LOOPHOLE[PilotExtend], truncate: LOOPHOLE[PilotTruncate],
startRead: LOOPHOLE[PilotStartRead], startWrite: LOOPHOLE[PilotStartWrite],
getTimes: LOOPHOLE[PilotGetTimes],
setCreation: LOOPHOLE[PilotSetCreationTime]]];
RETURN[pilotFS.ops];
END;
FinalizeLocal: PUBLIC PROCEDURE =
BEGIN
IF pilotFS.loginCount # 0 THEN ERROR InsufficientLogouts;
goingAway ← TRUE;
Process.Abort[ioProcess];
JOIN ioProcess;
Heap.systemZone.FREE[@pilotFS.ops];
Heap.systemZone.FREE[@pilotFS];
END;
Process.InitializeCondition[@ioRequestWaiting, Process.SecondsToTicks[600]];
Process.InitializeCondition[@ioRequestFreed, Process.SecondsToTicks[600]];
Process.EnableAborts[@ioRequestWaiting];
END...