-- File: AltoFileOpsC.mesa
-- Last edited by Levin:  30-Apr-81 16:22:32

DIRECTORY
  AltoFilePrivate USING [
    FileHandle, EnterPageAndvDA, FindLastKnownPage, LastPageBytes, minUsefulRuns],
  DiskIODefs USING [
    CompletionStatus, DiskError, DiskRequest, eofvDA, fillInvDA, InitiateDiskIO,
    PageCount, RequestID, vDA, VerboseCompletionProcedure, XferSpec],
  VMStorage USING [AllocatePage, FreePage];

AltoFileOpsC: MONITOR LOCKS synch.LOCK USING synch: IOSynch
  IMPORTS AltoFilePrivate, DiskIODefs, VMStorage
  EXPORTS AltoFilePrivate =

  BEGIN OPEN AltoFilePrivate, DiskIODefs;


  -- Types --

  IOSynch: TYPE = POINTER TO SynchRecord;

  SynchRecord: TYPE = MONITORED RECORD [status: CompletionStatus, ioDone: CONDITION];

  -- Miscellaneous Declarations --

  notLastXfer: RequestID = 776B;
  lastXfer: RequestID = 777B;


  -- Procedures Exported to AltoFilePrivate --

  DoDiskRequest: PUBLIC PROCEDURE [
    req: POINTER TO DiskRequest, file: FileHandle ← NIL, signalError: BOOLEAN ← TRUE]
    RETURNS [CompletionStatus, vDA, LastPageBytes] =
    BEGIN
    ioSynch: SynchRecord;
    finalXfer: CARDINAL = LENGTH[req.xfers] - 1;
    tempStatus: CompletionStatus;
    lastBytes: LastPageBytes;
    next: vDA;

    IssueRequestAndWait: ENTRY PROCEDURE [synch: IOSynch] = INLINE
      -- issues disk request and waits for completion.
      BEGIN
      DO
	synch.status ← noStatus;
	tempStatus ← ok;
	InitiateDiskIO[req];
	WHILE synch.status = noStatus DO WAIT synch.ioDone; ENDLOOP;
	IF synch.status ~= neverStarted THEN EXIT;
	ENDLOOP;
      END;

    DoNotify: ENTRY PROCEDURE [synch: IOSynch] = INLINE
      -- issues disk request and waits for completion.
      {synch.status ← tempStatus; NOTIFY synch.ioDone};

    ProcessCompletion: VerboseCompletionProcedure =
      -- invoked by completion of operation to post result.
      BEGIN
      IF tempStatus = ok THEN
	IF (tempStatus ← status) = ok THEN
	  BEGIN
	  next ← label.next;
	  lastBytes ← label.bytes;
	  IF file ~= NIL AND file.nRuns >= minUsefulRuns THEN
	    -- hack, but safe outside file monitor
	    BEGIN
	    EnterPageAndvDA[file, label.page, header.diskAddress];
	    EnterPageAndvDA[file, label.page - 1, label.prev];
	    EnterPageAndvDA[file, label.page + 1, label.next];
	    END;
	  END;
      IF id = lastXfer THEN DoNotify[@ioSynch];
      END;

    req.proc ← [verbose[ProcessCompletion]];
    IF file ~= NIL THEN req.fileID ← file.fileID;
    req.nonXferID ← notLastXfer;
    FOR i: CARDINAL IN [0..finalXfer) DO req.xfers[i].id ← notLastXfer; ENDLOOP;
    req.xfers[finalXfer].id ← lastXfer;
    InitSynch[@ioSynch];
    IssueRequestAndWait[@ioSynch];
    IF ioSynch.status ~= ok AND signalError THEN ERROR DiskError[ioSynch.status];
    RETURN[ioSynch.status, next, lastBytes]
    END;

  FindEOF: PUBLIC PROCEDURE [file: FileHandle] =
    BEGIN
    buffer: POINTER ← VMStorage.AllocatePage[]; -- needed only if file is huge
    xferSpec: ARRAY [0..1) OF XferSpec ← [[buffer, fillInvDA, lastXfer]];
    state, tempState: {none, eofFound, needsRestart, fatalError};
    request: DiskRequest;
    ioSynch: SynchRecord;
    prevNext: vDA;
    finalStatus: CompletionStatus;

    IssueRequestAndWait: ENTRY PROCEDURE [synch: IOSynch] = INLINE
      -- initiates disk request and waits for completion.
      BEGIN
      state ← tempState ← none;
      InitiateDiskIO[@request];
      WHILE state = none DO WAIT synch.ioDone; ENDLOOP;
      END;

    DoNotify: ENTRY PROCEDURE [synch: IOSynch] = INLINE
      -- initiates disk request and waits for completion.
      {state ← tempState; NOTIFY synch.ioDone};

    CheckCompletion: VerboseCompletionProcedure =
      -- processes completions, and notifies the main body of FindEOF when all has
      -- finished.
      BEGIN
      IF tempState = none THEN
	SELECT (finalStatus ← status) FROM
	  ok =>
	    BEGIN
	    file.lastPage ← label.page;
	    file.bytes ← label.bytes;
	    prevNext ← label.next;
	    EnterPageAndvDA[file, label.page, header.diskAddress];
	    IF id = lastXfer THEN tempState ← needsRestart; -- huge file!
	    END;
	  checkError =>
	    tempState ← IF prevNext = eofvDA THEN eofFound ELSE fatalError;
	  neverStarted => tempState ← needsRestart;
	  ENDCASE => tempState ← fatalError;
      IF id = lastXfer THEN DoNotify[@ioSynch];
      END;

    InitSynch[@ioSynch];
    DO
      lastvDA: vDA;
      [file.lastPage, lastvDA] ← FindLastKnownPage[file];
      request ←
	[firstPage: file.lastPage, fileID: file.fileID, firstPagevDA: lastvDA,
	  pagesToSkip: LAST[PageCount] - file.lastPage, nonXferID: notLastXfer,
	  proc: [verbose[CheckCompletion]], xfers: DESCRIPTOR[@xferSpec, 1],
	  noRestore: TRUE, -- expect check error at EOF
	  command: ReadD[]];
      IssueRequestAndWait[@ioSynch];
      SELECT state FROM
	eofFound => {file.lengthKnown ← TRUE; EXIT};
	needsRestart => NULL;
	fatalError => {VMStorage.FreePage[buffer]; ERROR DiskError[finalStatus]};
	ENDCASE;
      ENDLOOP;
    VMStorage.FreePage[buffer];
    END;


  -- Internal Procedures --

  InitSynch: PROCEDURE [synch: IOSynch] = INLINE
    {synch.ioDone.timeout ← 0};



  END.