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