-- File: IFSFileImplC.mesa
-- Last edited by:
-- Levin - 14-Sep-81 13:51:35

DIRECTORY
  ByteBlt USING [ByteBlt],
  IFSFile USING [Buffer, ByteCount, Completer, CompleterArg, Position, Problem],
  IFSFilePrivate USING [
    FileHandle, FileObject, FileState, FreeIORequestBlock, GetIORequestBlock, IORequest],
  Leaf USING [
    Answer, ByteCount, FileAddress, LeafType, OpSpecificFileAddress, readOp, readAns,
    Request, writeOp, WriteRequest],
  Mopcodes USING [zEXCH],
  Sequin USING [Broken, Buffer, Get, GetEmptyBuffer, Put, ReleaseBuffer];

IFSFileImplC: MONITOR LOCKS file.LOCK USING file: IFSFilePrivate.FileHandle
  IMPORTS ByteBlt, IFSFilePrivate, Sequin
  EXPORTS IFSFile, IFSFilePrivate =

  BEGIN OPEN IFSFilePrivate;


  -- Miscellaneous --

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


  -- Procedures, Types, and Signals Exported to IFSFile --

  FileObject: PUBLIC TYPE = IFSFilePrivate.FileObject;

  Error: PUBLIC ERROR [reason: IFSFile.Problem] = CODE;

  StartRead: PUBLIC PROCEDURE [file: FileHandle, pos: IFSFile.Position, bytes: IFSFile.ByteCount,
    buffer: IFSFile.Buffer, callback: IFSFile.Completer,
    arg: IFSFile.CompleterArg] =
    BEGIN
    request: IORequest;
    ValidateFile[file];
    request ← GetIORequestBlock[];
    request↑ ← [link: , buffer: buffer, address: PositionToFileAddress[pos], op: read,
		bytesToGo: bytes, proc: callback, arg: arg];
    DoRead[file, request];
    END;

  StartWrite: PUBLIC PROCEDURE [file: FileHandle, pos: IFSFile.Position, bytes: IFSFile.ByteCount,
    buffer: IFSFile.Buffer, callback: IFSFile.Completer,
    arg: IFSFile.CompleterArg] =
    BEGIN
    request: IORequest;
    ValidateFile[file];
    request ← GetIORequestBlock[];
    request↑ ← [link: , buffer: buffer,address: PositionToFileAddress[pos], op: write,
		bytesToGo: bytes, proc: callback, arg: arg];
    DoWrite[file, request];
    END;


  -- 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 IFSFile;
    bytesSent: Leaf.ByteCount ← 0;
    bytesToGo: Leaf.ByteCount = request.bytesToGo;
    initialPosition: Position = FileAddressToPosition[request.address];
    positionAfterWrite: Position = initialPosition + request.bytesToGo;
    AcquireIOLock[file];
    IF file.length < positionAfterWrite THEN file.length ← positionAfterWrite;
    EnqueueRequest[file, request];
    DO
      positionThisTime: Position = 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: ]];
      [] ← ByteBlt.ByteBlt[
       to: [blockPointer: @sequinRequest.writeBytes,
            startIndex: 0, stopIndexPlusOne: bytesThisTime],
       from: [blockPointer: request.buffer,
            startIndex: bytesSent, stopIndexPlusOne: bytesSent+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: IFSFile.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: IFSFile.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: IFSFile.Position = FileAddressToPosition[address];
	    IF current.op = answer.op.type AND pos = position THEN
	      BEGIN
	      outcome ← ok;
	      current.bytesToGo ← current.bytesToGo - bytes;
	      position ← 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;
	  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};
		illegalRead, illegalWrite => outcome ← illegalIO;
		IN [unimplementedOp..illegalTruncate] => ERROR LeafGibberish;
		ENDCASE;
	      current.bytesToGo ← 0;
	      END;
	    read =>
	      BEGIN
	      dataBytes: Leaf.ByteCount = ans.op.length - Leaf.readAns.length;
	      ValidateArrival[ans.address, dataBytes];
	      [] ← ByteBlt.ByteBlt[
		to: [blockPointer: current.buffer,
		     startIndex: offset, stopIndexPlusOne: offset+dataBytes],
		from: [blockPointer: @ans.readBytes,
		     startIndex: 0, stopIndexPlusOne: 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 [pos: IFSFile.Position] =
    -- Note:  regards 'fa' as a signed quantity (to allow leader page access).
    BEGIN
    Flip: PROCEDURE [Leaf.FileAddress] RETURNS [IFSFile.Position] =
      MACHINE CODE BEGIN Mopcodes.zEXCH; END;
    fa.opSpecific ← Leaf.OpSpecificFileAddress[open[fill: IF fa.high > 1777B THEN 37B ELSE 0]];
    RETURN[Flip[fa]]
    END;

  PositionToFileAddress: PUBLIC PROCEDURE [
    pos: IFSFile.Position, opSpecific: Leaf.OpSpecificFileAddress ← [read[]]]
    RETURNS [fa: Leaf.FileAddress] =
    -- Note:  regards 'pos' as a signed quantity (to allow leader page access).
    BEGIN
    Flip: PROCEDURE [IFSFile.Position] RETURNS [Leaf.FileAddress] =
      MACHINE CODE BEGIN Mopcodes.zEXCH; END;
    fa ← Flip[pos];
    fa.opSpecific ← opSpecific;
    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.