-- File: IFSFileOpsB.mesa
-- Last edited by Levin:  27-Feb-81 11:30:04
-- edit by Randy:  27-Jul-81 19:58:12
-- edit by Andrew: 11-Feb-81 14:51:07

DIRECTORY
  ByteBlt USING [ByteBlt],
  Environment USING [Block],
  FileDefs USING [
    Buffer, bytesPerPage, ComparePositions, Completer, CompleterArg,
    IncrementPosition, PageNumber, Position, Problem],
  IFSFilePrivate USING [
    FileHandle, FileState, FreeIORequestBlock, GetIORequestBlock, IORequest,
    openSeal],
  Leaf USING [
    Answer, ByteCount, FileAddress, LeafType, OpSpecificFileAddress, readOp,
    readAns, Request, writeOp, WriteRequest],
  Sequin USING [Broken, Buffer, Get, GetEmptyBuffer, Put, ReleaseBuffer];

IFSFileOpsB: MONITOR LOCKS file.LOCK USING file: IFSFilePrivate.FileHandle
  IMPORTS ByteBlt, FileDefs, IFSFilePrivate, Sequin EXPORTS IFSFilePrivate =

  BEGIN OPEN IFSFilePrivate;


  -- Types --

  FAPieces: TYPE = MACHINE DEPENDENT RECORD [
    ugh(0): SELECT OVERLAID * FROM
      fa => [fa(0): Leaf.FileAddress],
      faPieces => [
        opSpecific(0:0..4): Leaf.OpSpecificFileAddress,
        faHigh(0:5..15): [0..3777B],
        faMiddle(1:0..6): [0..177B],
        faLow(1:7..15): [0..777B]],
      posPieces => [
        posHigh(0:0..8): [0..777B],
        posMiddle(0:9..15): [0..177B],
        posLow(1:0..15): [0..777B]],
      position => [pos(0): FileDefs.Position],
      ENDCASE];


  -- Miscellaneous --

  bytesPerPage: CARDINAL = FileDefs.bytesPerPage;

  FileTooBig: ERROR = CODE;
  InvalidFile: ERROR = CODE;
  LeafGibberish: ERROR = CODE;
  TheWellIsDry: ERROR = CODE;

  MoveBytes: PROCEDURE [
    to, from: LONG POINTER, toByte, fromByte, nBytes: CARDINAL] = INLINE
    BEGIN
    toBlock, fromBlock: Environment.Block;
    toBlock ← [
      blockPointer: to, startIndex: toByte, stopIndexPlusOne: toByte + nBytes];
    fromBlock ← [
      blockPointer: from, startIndex: fromByte,
      stopIndexPlusOne: fromByte + nBytes];
    [] ← ByteBlt.ByteBlt[toBlock, fromBlock];
    END;


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

  StartRead: PUBLIC PROCEDURE [
    file: FileHandle, page: FileDefs.PageNumber, buffer: FileDefs.Buffer,
    callback: FileDefs.Completer, arg: FileDefs.CompleterArg] =
    BEGIN
    request: IORequest;
    bytesToRead: CARDINAL;
    ValidateFile[file];
    bytesToRead ←
      IF file.length.page = page THEN file.length.byte ELSE bytesPerPage;
    request ← GetIORequestBlock[];
    request↑ ← [
      link:, buffer: buffer,
      address: PositionToFileAddress[[page: page, byte: 0]], op: read,
      bytesToGo: bytesToRead, proc: callback, arg: arg];
    DoRead[file, request];
    END;

  StartWrite: PUBLIC PROCEDURE [
    file: FileHandle, page: FileDefs.PageNumber, buffer: FileDefs.Buffer,
    callback: FileDefs.Completer, arg: FileDefs.CompleterArg] =
    BEGIN
    request: IORequest;
    ValidateFile[file];
    request ← GetIORequestBlock[];
    request↑ ← [
      link:, buffer: buffer,
      address: PositionToFileAddress[[page: page, byte: 0]], op: write,
      bytesToGo: bytesPerPage, proc: callback, arg: arg];
    DoWrite[file, request];
    END;


  -- Other Procedures exported to IFSFilePrivate --

  DoRead: PUBLIC PROCEDURE [file: FileHandle, request: IORequest] =
    BEGIN
    sequinBuffer: Sequin.Buffer ← Sequin.GetEmptyBuffer[];
    sequinRequest: Leaf.Request ← LOOPHOLE[sequinBuffer.data];
    AcquireIOLock[file];
    EnqueueRequest[file, request];
    sequinRequest↑ ← [
      op: Leaf.readOp,
      opSpecific: read[
      handle: file.leafHandle, address: request.address,
      length: request.bytesToGo]];
    sequinBuffer.nBytes ← Leaf.readOp.length;
    Sequin.Put[file.sequin, sequinBuffer ! Sequin.Broken => CONTINUE];
    ReleaseIOLock[file];
    END;

  DoWrite: PUBLIC PROCEDURE [file: FileHandle, request: IORequest] =
    BEGIN OPEN FileDefs;
    bytesSent: Leaf.ByteCount ← 0;
    bytesToGo: Leaf.ByteCount = request.bytesToGo;
    initialPosition: Position = FileAddressToPosition[request.address];
    positionAfterWrite: Position = IncrementPosition[
      initialPosition, request.bytesToGo];
    AcquireIOLock[file];
    IF ComparePositions[file.length, positionAfterWrite] = less THEN
      file.length ← positionAfterWrite;
    EnqueueRequest[file, request];
    DO
      positionThisTime: Position = IncrementPosition[initialPosition, bytesSent];
      buffer: Sequin.Buffer ← Sequin.GetEmptyBuffer[];
      sequinRequest: Leaf.WriteRequest ← LOOPHOLE[buffer.data];
      bytesThisTime: Leaf.ByteCount;
      buffer.nBytes ← Leaf.writeOp.length;
      bytesThisTime ← MIN[buffer.maxBytes - buffer.nBytes, bytesToGo - bytesSent];
      sequinRequest↑ ← [
        op: Leaf.writeOp,
        opSpecific: write[
        handle: file.leafHandle,
        address: PositionToFileAddress[
        positionThisTime, request.address.opSpecific], length: bytesThisTime,
        writeBody:]];
      MoveBytes[
        to: @sequinRequest.writeBytes, from: request.buffer, toByte: 0,
        fromByte: bytesSent, nBytes: bytesThisTime];
      sequinRequest.op.length ← buffer.nBytes ← buffer.nBytes + bytesThisTime;
      Sequin.Put[file.sequin, buffer ! Sequin.Broken => EXIT];
      bytesSent ← bytesSent + bytesThisTime;
      IF bytesSent = bytesToGo THEN EXIT;
      ENDLOOP;
    ReleaseIOLock[file];
    END;

  FileWatcher: PUBLIC PROCEDURE [file: FileHandle] RETURNS [BOOLEAN] =
    BEGIN
    current: IORequest ← NIL;
    fatalError: BOOLEAN ← FALSE;
    offset: CARDINAL;
    DO
      buffer: Sequin.Buffer;
      outcome: FileDefs.Problem;

      CheckState: ENTRY PROCEDURE [file: FileHandle] RETURNS [FileState] =  -- INLINE --
        BEGIN
        DO
          SELECT file.state FROM
            alive => IF fatalError THEN file.state ← flush;
            flush => NULL;
            closing =>
              BEGIN
              IF file.ioPending = NIL THEN {
                file.state ← closed; BROADCAST file.ioSynch};
              EXIT
              END;
            ENDCASE;
          IF file.ioPending ~= NIL THEN EXIT;
          WAIT file.ioSynch;
          ENDLOOP;
        RETURN[file.state]
        END;

      CompleteRequest: PROCEDURE =
        BEGIN
        request: IORequest = DequeueRequest[file];
        request.proc[request.arg, outcome];
        FreeIORequestBlock[request];
        current ← NIL;
        END;

      SELECT CheckState[file] FROM
        alive =>
          BEGIN
          answer: Leaf.Answer;
          position: FileDefs.Position;

          EnsureCurrent: ENTRY PROCEDURE [file: FileHandle] =  -- INLINE --
            BEGIN
            IF current ~= NIL THEN RETURN;
            IF file.ioPending = NIL THEN ERROR TheWellIsDry;
            current ← file.ioPending.link;
            position ← FileAddressToPosition[current.address];
            offset ← 0;
            END;

          ValidateArrival: PROCEDURE [
            address: Leaf.FileAddress, bytes: Leaf.ByteCount] =
            BEGIN
            pos: FileDefs.Position = FileAddressToPosition[address];
            IF current.op = answer.op.type AND pos = position THEN
              BEGIN
              outcome ← ok;
              current.bytesToGo ← current.bytesToGo - bytes;
              position ← FileDefs.IncrementPosition[position, bytes];
              END
            ELSE fatalError ← TRUE;
            END;

          EnsureCurrent[file];
          outcome ← other;
          buffer ← Sequin.Get[
            file.sequin !
            Sequin.Broken => {outcome ← io; fatalError ← TRUE; LOOP}];
          answer ← LOOPHOLE[buffer.data];
          IF answer.op.sense ~= reply THEN GO TO protocolViolation
          ELSE
            WITH ans: answer SELECT answer.op.type FROM
              error =>
                BEGIN
                IF current.op ~= ans.errorOp.type THEN GO TO protocolViolation;
                SELECT ans.error FROM
                  accessDenied, fileBusy, IN [userName..connectPassword] =>
                    outcome ← credentials;
                  allocExceeded, fileSystemFull => outcome ← resources;
                  brokenLeaf => {outcome ← io; GO TO broken};
                  IN [unimplementedOp..illegalWrite] => ERROR LeafGibberish;
                  ENDCASE;
                current.bytesToGo ← 0;
                END;
              read =>
                BEGIN
                dataBytes: Leaf.ByteCount = ans.op.length - Leaf.readAns.length;
                ValidateArrival[ans.address, dataBytes];
                MoveBytes[
                  to: current.buffer, from: @ans.readBytes, toByte: offset,
                  fromByte: 0, nBytes: dataBytes];
                offset ← offset + dataBytes;
                END;
              write => ValidateArrival[ans.address, ans.length];
              ENDCASE => GO TO protocolViolation;
          IF current.bytesToGo = 0 THEN CompleteRequest[];
          Sequin.ReleaseBuffer[buffer];
          EXITS
            protocolViolation,
            broken => {fatalError ← TRUE; Sequin.ReleaseBuffer[buffer]};
          END;
        flush => CompleteRequest[];
        closing => {outcome ← other; CompleteRequest[]};
        closed => EXIT;
        ENDCASE;
      ENDLOOP;
    RETURN[~fatalError]
    END;

  ValidateFile: PUBLIC PROCEDURE [file: FileHandle] = {
    IF file.seal ~= openSeal THEN ERROR InvalidFile};

  FileAddressToPosition: PUBLIC PROCEDURE [fa: Leaf.FileAddress]
    RETURNS [FileDefs.Position] =
    -- Note:  regards 'fa' as a signed quantity (to allow leader page access).
    BEGIN
    p1, p2: FAPieces;
    p1.fa ← fa;
    IF ~(p1.faHigh <= 777B OR p1.faHigh IN [3400B..3777B]) THEN ERROR FileTooBig;
    p2 ← [
      posPieces[posHigh: p1.faHigh, posMiddle: p1.faMiddle, posLow: p1.faLow]];
    RETURN[p2.pos]
    END;

  PositionToFileAddress: PUBLIC PROCEDURE [
    pos: FileDefs.Position, opSpecific: Leaf.OpSpecificFileAddress ← [read[]]]
    RETURNS [Leaf.FileAddress] =
    -- Note:  regards 'pos' as a signed quantity (to allow leader page access).
    BEGIN
    p1, p2: FAPieces;
    p1.pos ← pos;
    p2 ← [
      faPieces[
      opSpecific: opSpecific, faHigh: p1.posHigh, faMiddle: p1.posMiddle,
      faLow: p1.posLow]];
    IF p1.posHigh >= 400B THEN p2.faHigh ← p1.posHigh + 3000B;
    RETURN[p2.fa]
    END;


  -- Internal Procedures --

  AcquireIOLock: ENTRY PROCEDURE [file: FileHandle] =
    BEGIN WHILE file.locked DO WAIT file.ioSynch ENDLOOP; file.locked ← TRUE; END;

  ReleaseIOLock: ENTRY PROCEDURE [file: FileHandle] =
    BEGIN file.locked ← FALSE; BROADCAST file.ioSynch; END;

  EnqueueRequest: ENTRY PROCEDURE [file: FileHandle, request: IORequest] =
    BEGIN
    IF file.ioPending = NIL THEN request.link ← request
    ELSE {request.link ← file.ioPending.link; file.ioPending.link ← request};
    file.ioPending ← request;
    BROADCAST file.ioSynch;
    END;

  DequeueRequest: ENTRY PROCEDURE [file: FileHandle]
    RETURNS [request: IORequest] =
    BEGIN
    IF file.ioPending = NIL THEN ERROR TheWellIsDry;
    request ← file.ioPending.link;
    file.ioPending.link ← request.link;
    IF request = file.ioPending THEN file.ioPending ← NIL;
    END;

  END.