-- Copyright (C) 1981, 1984, 1985  by Xerox Corporation. All rights reserved. 
-- File: IFSFileOpsA.mesa

-- HGM,  6-Feb-85 22:13:21
-- Last edited by Gobbel: 27-Jul-81 19:57:07
-- Last edited by Levin:   6-Mar-81 16:39:17

DIRECTORY
  FileDefs USING [
    bytesPerPage, ComparePositions, Completer, defaultTime, FileTime, Position],
  IFSFilePrivate USING [
    DoRead, DoWrite, FileAddressToPosition, FileHandle, fileTimeAddress,
    FileWatcher, FSInstance, GetIORequestBlock, ifsFiles, IFSTIME, IFSTimes,
    InsertFile, IORequest, PositionToFileAddress, PurgeFile, ReleaseFile,
    ValidateFile],
  Inline USING [LongCOPY],
  Leaf USING [
    Answer, closeOp, deleteOp, FileAddress, IfsError, openOp, paramsOp, ptLeaf,
    Request, RequestObject],
  Mopcodes USING [zEXCH],
  Sequin USING [
    Broken, Buffer, Create, Destroy, Get, GetEmptyBuffer, Put, ReleaseBuffer],
  String USING [AppendString],
  Time USING [Current],
  VMDefs USING [AccessFailure, Error, OpenOptions, Problem],
  VMStorage USING [shortTerm];

IFSFileOpsA: MONITOR LOCKS file.LOCK USING file: IFSFilePrivate.FileHandle
  IMPORTS
    FileDefs, IFSFilePrivate, Inline, Sequin, String, Time, VMDefs, VMStorage
  EXPORTS IFSFilePrivate =

  BEGIN OPEN IFSFilePrivate;


  -- Types --

  IOSynchRecord: TYPE = RECORD [
    done: BOOLEAN,
    outcome: VMDefs.Problem,
    file: FileHandle,
    finished: CONDITION ← [timeout: 0]];

  IOSynch: TYPE = POINTER TO IOSynchRecord;


  -- Miscellaneous --

  IllegalExtend: ERROR = CODE;
  IllegalTruncate: ERROR = CODE;
  PacketTooSmall: ERROR = CODE;


  -- Operations (exported to IFSFilePrivate on behalf of FileDefs) --

  Open: PUBLIC PROCEDURE [
    instance: FSInstance, name: STRING, options: VMDefs.OpenOptions ← oldReadOnly]
    RETURNS [file: FileHandle] =
    BEGIN

    DoOpen: PROCEDURE [file: FileHandle] RETURNS [problem: VMDefs.AccessFailure] =
      BEGIN
      buffer: Sequin.Buffer ← Sequin.GetEmptyBuffer[];
      request: Leaf.Request ← LOOPHOLE[buffer.data];
      openOpOffset: CARDINAL;

      Flush: PROCEDURE =
        BEGIN
        Sequin.Put[file.sequin, buffer];
        buffer ← Sequin.GetEmptyBuffer[];
        request ← LOOPHOLE[buffer.data];
        openOpOffset ← 0;
        END;

      MapError: PROCEDURE [leafError: Leaf.IfsError]
        RETURNS [VMDefs.AccessFailure] = {
        RETURN[
          SELECT leafError FROM
            accessDenied, fileBusy, illegalDIFAccess, IN
              [userName..connectPassword] => accessDenied,
            fileNotFound, dirNotFound => notFound,
            fileAlreadyExists => alreadyExists,
            IN [nameMalformed..nameTooLong] => illegalFileName,
            ENDCASE => other]};

      file.sequin ← Sequin.Create[
        dest: instance.serverAddr, pupType: Leaf.ptLeaf];
      request↑ ← [
        Leaf.paramsOp, params[
        packetDataBytes: buffer.maxBytes, fileLockTimeout: 2 * 60 / 5,
        connectionTimeout: 10 * 60 / 5]];
      buffer.nBytes ← openOpOffset ← Leaf.paramsOp.length;
      IF buffer.maxBytes - buffer.nBytes <= Leaf.openOp.length THEN
        Flush[ ! Sequin.Broken => GO TO serverDead]
      ELSE
        request ← LOOPHOLE[@buffer.data.words[SIZE[params Leaf.RequestObject]]];
      request↑ ← [Leaf.openOp, open[]];
      BEGIN
      ENABLE
        PacketTooSmall => {
          IF openOpOffset # 0 THEN {
            Flush[ ! Sequin.Broken => GO TO serverDead]; RETRY}};
      fs: FSInstance = file.fs;
      WITH openReq: request SELECT open FROM
        open =>
          BEGIN
          IF options # oldReadOnly THEN openReq.write ← openReq.extend ← TRUE;
          SELECT options FROM
            oldOrNew => openReq.create ← TRUE;
            new => {openReq.vDefault ← next; openReq.create ← TRUE};
            ENDCASE;
          END;
        ENDCASE;
      buffer.nBytes ← buffer.nBytes + Leaf.openOp.length;
      AddStringToBuffer[@buffer, fs.primaryName];
      AddStringToBuffer[@buffer, fs.primaryPassword];
      AddStringToBuffer[@buffer, fs.secondaryName];
      AddStringToBuffer[@buffer, fs.secondaryPassword];
      AddStringToBuffer[@buffer, name];
      END;
      request.op.length ← buffer.nBytes - openOpOffset;
      Sequin.Put[file.sequin, buffer ! Sequin.Broken => GO TO serverDead];
      buffer ← Sequin.Get[file.sequin ! Sequin.Broken => GO TO serverDead];
      BEGIN
      answer: Leaf.Answer ← LOOPHOLE[buffer.data];
      IF answer.op.sense # reply THEN {problem ← other; GO TO cantOpen};
      WITH ans: answer SELECT answer.op.type FROM
        error => {problem ← MapError[ans.error]; GO TO cantOpen};
        params => NULL;
        ENDCASE => {problem ← other; GO TO cantOpen};
      IF answer.op.length # buffer.nBytes THEN
        answer ← answer + answer.op.length / 2
      ELSE
        BEGIN
        Sequin.ReleaseBuffer[buffer];
        buffer ← Sequin.Get[file.sequin ! Sequin.Broken => GO TO serverDead];
        answer ← LOOPHOLE[buffer.data];
        END;
      IF answer.op.sense # reply THEN {problem ← other; GO TO cantOpen};
      WITH ans: answer SELECT answer.op.type FROM
        error => {problem ← MapError[ans.error]; GO TO cantOpen};
        open =>
          BEGIN
          problem ← ok;
          file.leafHandle ← ans.handle;
          file.length ← FileAddressToPosition[ans.eofAddress];
          ifsFiles ← ifsFiles + 1;
          END;
        ENDCASE => {problem ← other; GO TO cantOpen};
      EXITS cantOpen => Sequin.Destroy[file.sequin];
      END;
      Sequin.ReleaseBuffer[buffer];
      EXITS serverDead => {
        problem ← io;
        Sequin.Destroy[file.sequin]; };
      END;

    file ← InsertFile[instance, name, DoOpen];
    file.watcher ← FORK FileWatcher[file];
    END;

  Close: PUBLIC PROCEDURE [file: FileHandle] =
    BEGIN
    DoClose: PROCEDURE [file: FileHandle] = {CleanupFile[file, close]};

    ValidateFile[file];
    ReleaseFile[file, DoClose];
    END;

  Abandon: PUBLIC PROCEDURE [file: FileHandle] =
    BEGIN
    DoAbandon: PROCEDURE [file: FileHandle] = {CleanupFile[file, abandon]};

    ValidateFile[file];
    ReleaseFile[file, DoAbandon];
    END;

  Destroy: PUBLIC PROCEDURE [file: FileHandle] =
    BEGIN
    DoDestroy: PROCEDURE [file: FileHandle] = {CleanupFile[file, destroy]};

    ValidateFile[file];
    PurgeFile[file, DoDestroy];
    END;

  GetLength: PUBLIC ENTRY PROCEDURE [file: FileHandle]
    RETURNS [FileDefs.Position] = BEGIN RETURN[file.length] END;

  SetLength: PUBLIC PROCEDURE [file: FileHandle, length: FileDefs.Position] =
    BEGIN
    oldLength: FileDefs.Position;
    ValidateFile[file];
    oldLength ← GetLength[file];
    SELECT FileDefs.ComparePositions[oldLength, length] FROM
      less => DoIO[file, PrepareSetLength[file, length]];
      equal => NULL;
      greater => Truncate[file, length];
      ENDCASE;
    END;

  Extend: PUBLIC PROCEDURE [
    file: FileHandle, length: FileDefs.Position, buffer: POINTER] =
    BEGIN
    PrepareExtend: ENTRY PROCEDURE [file: FileHandle]
      RETURNS [request: IORequest] = INLINE
      BEGIN
      request ← GetIORequestBlock[];
      ValidateFile[file];
      request↑ ← [
        buffer: buffer,
        address: PositionToFileAddress[
        [file.length.page, 0], [write[newEOF: TRUE]]], op: write, bytesToGo:];
      IF FileDefs.ComparePositions[length, file.length] # greater THEN
        GO TO bogus;
      IF length.page = file.length.page THEN {request.bytesToGo ← length.byte}
      ELSE
        BEGIN
        IF ~(length.page = file.length.page + 1 AND length.byte = 0) THEN
          GO TO bogus;
        request.bytesToGo ← FileDefs.bytesPerPage;
        END;
      file.length ← length;
      EXITS bogus => ERROR IllegalExtend;
      END;
    DoIO[file, PrepareExtend[file]];
    END;

  Truncate: PUBLIC PROCEDURE [file: FileHandle, length: FileDefs.Position] =
    BEGIN
    ValidateFile[file];
    SELECT FileDefs.ComparePositions[file.length, length] FROM
      less => ERROR IllegalTruncate;
      equal => NULL;
      greater => DoIO[file, PrepareSetLength[file, length]];
      ENDCASE;
    END;

  GetTimes: PUBLIC PROCEDURE [file: FileHandle]
    RETURNS [read, write, create: FileDefs.FileTime] =
    BEGIN
    times: IFSTimes;
    request: IORequest ← GetIORequestBlock[];
    request↑ ← [
      buffer: @times, address: fileTimeAddress, op: read,
      bytesToGo: 2 * SIZE[IFSTimes]];
    DoIO[file, request];
    RETURN[
      IFSToMesaTime[times.read], IFSToMesaTime[times.write], IFSToMesaTime[
        times.create]]
    END;

  SetCreationTime: PUBLIC PROCEDURE [
    file: FileHandle, create: FileDefs.FileTime ← FileDefs.defaultTime] =
    BEGIN
    creation: IFSTIME;
    request: IORequest ← GetIORequestBlock[];
    IF create = FileDefs.defaultTime THEN create ← Time.Current[];
    creation ← MesaToIFSTime[create];
    request↑ ← [
      buffer: @creation, address: fileTimeAddress, op: write,
      bytesToGo: 2 * SIZE[IFSTIME]];
    DoIO[file, request];
    END;


  -- Other Procedures exported to IFSFilePrivate --

  CopyString: PUBLIC PROCEDURE [s: STRING] RETURNS [ns: STRING] =
    BEGIN
    IF s = NIL THEN RETURN[NIL];
    ns ← VMStorage.shortTerm.NEW[StringBody [s.length]];
    String.AppendString[ns, s];
    END;

  AddStringToBuffer: PUBLIC PROCEDURE [
    buffer: POINTER TO Sequin.Buffer, s: STRING] =
    BEGIN
    startWord: CARDINAL = (buffer.nBytes + 1) / 2;
    nWords: CARDINAL;
    IF s = NIL THEN s ← ""L;
    nWords ← (s.length + 1) / 2;
    IF buffer.maxBytes < 2 * (startWord + nWords) THEN ERROR PacketTooSmall;
    buffer.data.words[startWord] ← s.length;
    Inline.LongCOPY[
      from: @s.text, to: @buffer.data.words[startWord + 1], nwords: nWords];
    buffer.nBytes ← buffer.nBytes + 2 * (nWords + 1);
    END;


  -- Internal Procedures --

  PrepareSetLength: ENTRY PROCEDURE [file: FileHandle, length: FileDefs.Position]
    RETURNS [request: IORequest] =
    BEGIN
    request ← GetIORequestBlock[];
    request↑ ← [
      buffer: NIL, address: PositionToFileAddress[length, [write[newEOF: TRUE]]],
      op: write, bytesToGo: 0];
    file.length ← length;
    END;

  CleanupFile: ENTRY PROCEDURE [file: FileHandle, op: {close, destroy, abandon}] =
    BEGIN
    file.state ← closing;
    NOTIFY file.ioSynch;
    UNTIL file.state = closed DO WAIT file.ioSynch ENDLOOP;
    IF (JOIN file.watcher) AND op # abandon THEN
      BEGIN OPEN Sequin;
      buffer: Buffer ← GetEmptyBuffer[];
      request: Leaf.Request ← LOOPHOLE[buffer.data];
      IF op = close THEN
        BEGIN
        request↑ ← [Leaf.closeOp, close[handle: file.leafHandle]];
        buffer.nBytes ← Leaf.closeOp.length;
        END
      ELSE  -- op = destroy
        BEGIN
        request↑ ← [Leaf.deleteOp, delete[handle: file.leafHandle]];
        buffer.nBytes ← Leaf.deleteOp.length;
        END;
      Put[file.sequin, buffer ! Broken => GO TO ignore];
      buffer ← Get[file.sequin ! Broken => GO TO ignore];
      ReleaseBuffer[buffer];
      EXITS ignore => NULL;
      END;
    Sequin.Destroy[file.sequin];
    ifsFiles ← ifsFiles - 1;
    END;

  DoIO: PROCEDURE [file: FileHandle, request: IORequest] =
    BEGIN
    ioSynch: IOSynchRecord ← [done: FALSE, outcome: ok, file: file];
    WaitForCompletion: ENTRY PROCEDURE [file: FileHandle] = INLINE {
      UNTIL ioSynch.done DO WAIT ioSynch.finished; ENDLOOP};
    request.proc ← NoteCompletion;
    request.arg ← @ioSynch;
    SELECT request.op FROM
      read => DoRead[file, request];
      write => DoWrite[file, request];
      ENDCASE;
    WaitForCompletion[file];
    IF ioSynch.outcome # ok THEN ERROR VMDefs.Error[ioSynch.outcome];
    END;

  NoteCompletion: FileDefs.Completer =
    BEGIN
    ioSynch: IOSynch = LOOPHOLE[arg];
    DoNotify: ENTRY PROCEDURE [file: FileHandle] = INLINE
      BEGIN
      ioSynch.done ← TRUE;
      ioSynch.outcome ← outcome;
      NOTIFY ioSynch.finished;
      END;
    DoNotify[ioSynch.file];
    END;

  IFSToMesaTime: PROCEDURE [IFSTIME] RETURNS [FileDefs.FileTime] = MACHINE CODE
    BEGIN Mopcodes.zEXCH; END;

  MesaToIFSTime: PROCEDURE [FileDefs.FileTime] RETURNS [IFSTIME] = MACHINE CODE
    BEGIN Mopcodes.zEXCH; END;


  END.