-- File: IFSFileOpsB.mesa
-- Last edited by Levin:   4-Feb-82 14:52:09
-- edit by Andrew: 11-Feb-81 14:51:07

DIRECTORY
  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],
  MiscDefs USING [ByteBlt],
  Sequin USING [Broken, Buffer, Get, GetEmptyBuffer, Put, ReleaseBuffer];

IFSFileOpsB: MONITOR LOCKS file.LOCK USING file: IFSFilePrivate.FileHandle
  IMPORTS FileDefs, IFSFilePrivate, MiscDefs, 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;


  -- 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;
    bytesToWrite: CARDINAL;
    ValidateFile[file];
    bytesToWrite ← IF file.length.page = page THEN file.length.byte ELSE bytesPerPage;
    request ← GetIORequestBlock[];
    request↑ ← [link: , buffer: buffer,
		address: PositionToFileAddress[[page: page, byte: 0]], op: write,
		bytesToGo: bytesToWrite, 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: ]];
      MiscDefs.ByteBlt[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];
		MiscDefs.ByteBlt[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.