-- File: IFSFileImplB.mesa
-- Last edited by:
-- Levin - 13-Jan-82 10:03:26

DIRECTORY
  IFSFile USING [
    AccessFailure, Completer, defaultTime, Error, FileTime, OpenOptions, Position, Problem],
  IFSFilePrivate USING [
    connectionTimeout, DoRead, DoWrite, FileAddressToPosition, FileHandle, fileLockTimeout,
    FileObject, fileTimeAddress, FileWatcher, FreeSequinForFS, FSInstance, FSObject,
    GetIORequestBlock, GetSequinForFS, IFSTIME, IFSTimes, InsertFile, IORequest,
    PositionToFileAddress, PurgeFile, ReleaseFile, ValidateFile, zone],
  Inline USING [LongCOPY],
  Leaf USING [
    Answer, closeOp, deleteOp, FileAddress, IfsError, openOp, paramsOp,
    Request, RequestObject],
  Mopcodes USING [zEXCH],
  Sequin USING [
    Broken, Buffer, Get, GetEmptyBuffer, Put, ReleaseBuffer],
  Time USING [Current];

IFSFileImplB: MONITOR LOCKS file.LOCK USING file: IFSFilePrivate.FileHandle
  IMPORTS IFSFile, IFSFilePrivate, Inline, Sequin, Time
  EXPORTS IFSFile, IFSFilePrivate =

  BEGIN OPEN IFSFilePrivate;
  

  -- Types --

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

  IOSynch: TYPE = LONG POINTER TO IOSynchRecord;


  -- Miscellaneous --

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

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

  FileObject: PUBLIC TYPE = IFSFilePrivate.FileObject;
  FSObject: PUBLIC TYPE = IFSFilePrivate.FSObject;

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

    DoOpen: PROCEDURE [file: FileHandle] RETURNS [problem: IFSFile.AccessFailure] =
      BEGIN
      buffer: Sequin.Buffer;
      request: Leaf.Request;
      openOpOffset: CARDINAL ← 0;

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

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

      DO
        fromCache: BOOLEAN;
        BEGIN -- for EXITS serverDead
	[file.sequin, fromCache] ← GetSequinForFS[instance];
        buffer ← Sequin.GetEmptyBuffer[];
        request ← LOOPHOLE[buffer.data];
        IF ~fromCache THEN
	  BEGIN
	  -- send paramsOp only on fresh sequin
	  request↑ ← [Leaf.paramsOp,
			params[packetDataBytes: buffer.maxBytes,
			fileLockTimeout: fileLockTimeout/5,
			connectionTimeout: connectionTimeout/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]]];
	  END;
        request↑ ← [Leaf.openOp, open[]];
        BEGIN  -- for handling PacketTooSmall
        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;  -- of handling PacketTooSmall
        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  -- for cantOpen
        answer: Leaf.Answer ← LOOPHOLE[buffer.data];
        IF ~fromCache THEN
	  BEGIN
	  -- process params response
	  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;
	  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];
	    END;
	  ENDCASE => {problem ← other; GO TO cantOpen};
        EXITS
	  cantOpen => FreeSequinForFS[instance, file.sequin, problem ~= other];
        END;  -- for cantOpen
        Sequin.ReleaseBuffer[buffer];
	EXIT
        EXITS
	  serverDead =>
	    BEGIN
	    FreeSequinForFS[instance, file.sequin, FALSE];
	    IF fromCache THEN LOOP  -- perhaps sequin had timed out
	    ELSE {problem ← io; EXIT};
	    END;
        END;
	ENDLOOP;
      END;

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

  CantOpen: PUBLIC ERROR [reason: IFSFile.AccessFailure] = CODE;

  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 [IFSFile.Position] =
    BEGIN
    RETURN[file.length]
    END;

  SetLength: PUBLIC PROCEDURE [file: FileHandle, length: IFSFile.Position] =
    BEGIN
    oldLength: IFSFile.Position;
    ValidateFile[file];
    oldLength ← GetLength[file];
    IF oldLength ~= length THEN DoIO[file, PrepareSetLength[file, length]];
    END;

  GetTimes: PUBLIC PROCEDURE [file: FileHandle]
    RETURNS [read, write, create: IFSFile.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;

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


  -- Procedures Exported to IFSFilePrivate --

  CopyString: PUBLIC PROCEDURE [s: LONG STRING] RETURNS [ns: LONG STRING] =
    BEGIN
    IF s = NIL THEN RETURN[NIL];
    ns ← zone.NEW[StringBody[s.length]];
    Inline.LongCOPY[from: @s.text, to: @ns.text, nwords: (s.length+1)/2];
    ns.length ← s.length;
    END;

  AddStringToBuffer: PUBLIC PROCEDURE [buffer: POINTER TO Sequin.Buffer, s: LONG 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: IFSFile.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
    toCache: BOOLEAN ← FALSE;
    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];
      toCache ← TRUE;
      EXITS
	ignore => NULL;
      END;
    FreeSequinForFS[file.fs, file.sequin, toCache];
    zone.FREE[@file.name];
    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 ← LONG[@ioSynch]; 
    SELECT request.op FROM
      read => DoRead[file, request];
      write => DoWrite[file, request];
      ENDCASE;
    WaitForCompletion[file];
    IF ioSynch.outcome ~= ok THEN ERROR IFSFile.Error[ioSynch.outcome];
    END;

  NoteCompletion: IFSFile.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 [IFSFile.FileTime] = MACHINE CODE
    BEGIN Mopcodes.zEXCH; END;

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


  END.