/****************************************************************************
**  File:       pipesched.c
**  Program:    pipe-handler - an AmigaDOS handler for named pipes
**  Version:    1.1
**  Author:     Ed Puckett      qix@mit-oz
**
**  Copyright 1987 by EpAc Software.  All Rights Reserved.
**
**  History:    05-Jan-87       Original Version (1.0)
**		07-Feb-87	Added "lockct" check in CheckWaiting().
*/

#include   <libraries/dos.h>
#include   <libraries/dosextens.h>
#include   <libraries/filehandler.h>
#include   <exec/exec.h>

#include   "pipelists.h"
#include   "pipename.h"
#include   "pipebuf.h"
#include   "pipecreate.h"
#include   "pipesched.h"
#include   "pipe-handler.h"

#if PIPEDIR
# include   "pipedir.h"
#endif PIPEDIR

#ifdef DEBUG
# include   "pipedebug.h"
#endif DEBUG



/*---------------------------------------------------------------------------
** pipesched.c
** -----------
** This module handles pipe I/O scheduling.
**
** Visible Functions
** -----------------
**	void              StartPipeIO    (pkt, iotype)
**	void              CheckWaiting   (pipe)
**	struct DosPacket  *AllocPacket   (ReplyPort)
**	void              FreePacket     (pkt)
**	void              StartTapIO     (pkt, Type, Arg1, Arg2, Arg3, Handler)
**	void              HandleTapReply (pkt)
**
** Macros (in pipesched.h)
** -----------------------
**	- none -
**
** Local Functions
** ---------------
**	void  EndPipeIO (pipe, wd)
*/



/*---------------------------------------------------------------------------
** A pipe I/O request is begun.  A WAITINGDATA structure is allocated and
** the request is stored in it.  It is then stored in the appropriate list
** (readerlist or writerlist) of the pipe.  Finally, CheckWaiting is called
** to service requests for that pipe.
**      Notice that CheckWaiting() is only called when a new I/O request
** comes in, or when the pipe is closed.  At no other time will the state of
** the pipe change in such a way that more requests for it can be honored.
*/

void  StartPipeIO (pkt, iotype)

struct DosPacket  *pkt;
IOTYPE            iotype;     /* assumed only PIPEREAD or PIPEWRITE */

{ PIPEKEY      *pipekey;
  PIPEDATA     *pipe;
  WAITINGDATA  *wd;


  if ((iotype != PIPEREAD) && (iotype != PIPEWRITE))
    { pkt->dp_Res2= ERROR_ACTION_NOT_KNOWN;
SPIOEXIT:
      pkt->dp_Res1= -1;
      ReplyPkt (pkt);
      return;
    }

      
  pipekey= (PIPEKEY *) pkt->dp_Arg1;
  pipe= pipekey->pipe;

  if ((wd= (WAITINGDATA *) AllocMem (sizeof (WAITINGDATA), ALLOCMEM_FLAGS)) == NULL)
    { pkt->dp_Res2= ERROR_NO_FREE_STORE;
      goto SPIOEXIT;
    }


  pkt->dp_Res2= ERROR_INVALID_LOCK;     /* in case not open for iotype */

  if (iotype == PIPEREAD)
    { if ((pipekey->iotype != PIPEREAD) && (pipekey->iotype != PIPERW))
        goto SPIOEXIT;

      InsertTail (&pipe->readerlist, wd);
    }
  else     /* PIPEWRITE */
    { if ((pipekey->iotype != PIPEWRITE) && (pipekey->iotype != PIPERW))
        goto SPIOEXIT;

      InsertTail (&pipe->writerlist, wd);
    }


  wd->pkt= pkt;
  wd->pktinfo.pipewait.reqtype= iotype;
  wd->pktinfo.pipewait.buf= (BYTE *) pkt->dp_Arg2;     /* buffer */
  wd->pktinfo.pipewait.len= (ULONG)  pkt->dp_Arg3;     /* length */

  CheckWaiting (pipe);
}



/*---------------------------------------------------------------------------
** Read requests for the pipe are satisfied until the pipe is empty or no
** more requests are left.  Then, write requests are satisifed until the pipe
** is full or no more requests are left.  This alternating process is
** repeated until no further changes are possible.
**      Finished requests are sent to EndPipeIO() so that replies may be sent
** to their owners.  Aftereward, if the pipe is empty and is not open for
** either read or write, then it is discarded.  If it is open for read, but
** is empty and has no write requests and is not open for write, then all
** remaining read requests are returned in their current state.  (This
** implements EOF.)  A pipe with a positive "lockct" will not be discarded.
** UnLock() is expected to call here so that a previously locked, empty pipe
** will be discarded.
*/

void  CheckWaiting (pipe)

PIPEDATA  *pipe;

{ BYTE         change;
  WAITINGDATA  *wd;
  ULONG        amt;
  void         EndPipeIO();


#if PIPEDIR
  SetPipeDate (pipe);
#endif PIPEDIR

  for (change= TRUE; change; )
    { change= FALSE;

      while ( (! (PipebufEmpty (pipe->buf))) &&
              ((wd= (WAITINGDATA *) FirstItem (&pipe->readerlist)) != NULL) )
        { amt= MoveFromPipebuf (pipe->buf, wd->pktinfo.pipewait.buf, wd->pktinfo.pipewait.len);

          if (amt)
            { wd->pktinfo.pipewait.buf += amt;
              wd->pktinfo.pipewait.len -= amt;
              change= TRUE;
            }

          if (wd->pktinfo.pipewait.len == 0L)     /* then finished with request */
            EndPipeIO (pipe, wd);
        }     /* end of readerlist loop */


      while ( (! (PipebufFull (pipe->buf))) &&
              ((wd= (WAITINGDATA *) FirstItem (&pipe->writerlist)) != NULL) )
        { amt= MoveToPipebuf (pipe->buf, wd->pktinfo.pipewait.buf, wd->pktinfo.pipewait.len);

          if (amt)
            { wd->pktinfo.pipewait.buf += amt;
              wd->pktinfo.pipewait.len -= amt;
              change= TRUE;
            }

          if (wd->pktinfo.pipewait.len == 0L)     /* then finished with request */
            EndPipeIO (pipe, wd);
        }     /* end of writerlist loop */
    }


  if ( PipebufEmpty (pipe->buf)                &&
       (! (pipe->flags & OPEN_FOR_WRITE))      &&
       (FirstItem (&pipe->writerlist) == NULL)    )     /* then EOF */
    { while ((wd= (WAITINGDATA *) FirstItem (&pipe->readerlist)) != NULL)
        EndPipeIO (pipe, wd);

      if (! (pipe->flags & OPEN_FOR_READ))     /* readerlist is now empty */
#if PIPEDIR
        if (pipe->lockct == 0)
#endif PIPEDIR
          DiscardPipe (pipe);
    }
}



/*---------------------------------------------------------------------------
** This routine returns a finished pipe I/O request.  If it is a write
** request to a pipe with a tap, then the same write request is sent to the
** tap, and the reply is deferred until HandleTapReply() gets the tap request
** reply.  (This lets the user stop a pipe by typing a character into a
** tap window.)
*/

static void  EndPipeIO (pipe, wd)

PIPEDATA     *pipe;
WAITINGDATA  *wd;

{ struct DosPacket   *pkt, *tappkt;
  struct FileHandle  *taphandle;


  pkt= wd->pkt;

  pkt->dp_Res1= pkt->dp_Arg3 - wd->pktinfo.pipewait.len;
  pkt->dp_Res2= 0;

  if (wd->pktinfo.pipewait.reqtype == PIPEREAD)
    { Delete (&pipe->readerlist, wd);

      ReplyPkt (pkt);
      FreeMem (wd, sizeof (WAITINGDATA));
    }
  else     /* must be PIPEWRITE -- reqtype is new PIPERW */
    { Delete (&pipe->writerlist, wd);

      if (pipe->tapfh != 0)     /* then write to the pipe tap */
        { if ((tappkt= AllocPacket (TapReplyPort)) == NULL)
            { ReplyPkt (pkt);
              FreeMem (wd, sizeof (WAITINGDATA));
#ifdef DEBUG
              OS ("!!! ERROR - Could not allocate packet for tap write\n");
#endif DEBUG
            }
          else
            { wd->pkt= tappkt;     /* reuse wd for tap write request */
              wd->pktinfo.tapwait.clientpkt= pkt;
              /* don't need ...tapwait.handle */

              taphandle= (struct FileHandle *) BPTRtoCptr (pipe->tapfh);

              StartTapIO ( tappkt, ACTION_WRITE,
                           taphandle->fh_Arg1, pkt->dp_Arg2, pkt->dp_Arg3,
                           taphandle->fh_Type );

              InsertHead (&tapwaitlist, wd);     /* for HandleTapReply() */
            }
        }
      else     /* otherwise, return finished packet */
        { ReplyPkt (pkt);
          FreeMem (wd, sizeof (WAITINGDATA));
        }
    }
}



/*---------------------------------------------------------------------------
** An exec Message and a DosPacket are allocated, and they are initialized.
** A pointer to the packet is returned, or NULL if it could not be allocated.
*/

struct DosPacket  *AllocPacket (ReplyPort)

struct MsgPort  *ReplyPort;

{ struct Message    *msg;
  struct DosPacket  *pkt;


  if ((msg = (struct Message *) AllocMem (sizeof (struct Message), (ALLOCMEM_FLAGS | MEMF_CLEAR))) == NULL)
    return NULL;

  if ((pkt = (struct DosPacket *) AllocMem (sizeof (struct DosPacket), (ALLOCMEM_FLAGS | MEMF_CLEAR))) == NULL)
    { FreeMem (msg, sizeof (struct Message));
      return NULL;
    }

  msg->mn_Node.ln_Type= NT_MESSAGE;
  msg->mn_Node.ln_Name= (char *) pkt;

  msg->mn_ReplyPort= ReplyPort;

  pkt->dp_Link= msg;
  pkt->dp_Port= ReplyPort;

  return pkt;
}



/*---------------------------------------------------------------------------
** A DosPacket/exec Message pair is freed.
*/

void  FreePacket (pkt)

struct DosPacket  *pkt;

{ if (pkt != NULL)
    { if (pkt->dp_Link != NULL)
        FreeMem (pkt->dp_Link, sizeof (struct Message));

      FreeMem (pkt, sizeof (struct DosPacket));
    }
}



/*---------------------------------------------------------------------------
** The indicated fields are filled into the packet and it is sent.
*/

void  StartTapIO (pkt, Type, Arg1, Arg2, Arg3, Handler)

struct DosPacket  *pkt;
LONG              Type;
LONG              Arg1;
LONG              Arg2;
LONG              Arg3;
struct MsgPort    *Handler;

{ pkt->dp_Type= Type;
  pkt->dp_Arg1= Arg1;
  pkt->dp_Arg2= Arg2;
  pkt->dp_Arg3= Arg3;

  PutMsg (Handler, pkt->dp_Link);
}



/*---------------------------------------------------------------------------
** Handle replies from tap I/O requests.  These were initiated by OpenTap(),
** CloseTap() and EndPipeIO().
*/

void  HandleTapReply (pkt)

struct DosPacket  *pkt;

{ WAITINGDATA  *wd;


  for (wd= (WAITINGDATA *) FirstItem (&tapwaitlist); wd != NULL; wd= (WAITINGDATA *) NextItem (wd))
    if (wd->pkt == pkt)
      { Delete (&tapwaitlist, wd);
        break;
      }

  if (wd == NULL)
    {
#ifdef DEBUG
      OS ("!!! ERROR - WAITINGDATA not found in HandleTapReply()\n");
#endif DEBUG
      FreePacket (pkt);
      return;     /* not found - this should never happen */
    }

  switch (pkt->dp_Type)
    { case MODE_READWRITE:
      case MODE_READONLY:
      case MODE_NEWFILE:     /* for a tap open request */
             if (pkt->dp_Res1)     /* then successful */
               OpenPipe (wd->pktinfo.tapwait.clientpkt, pkt->dp_Arg1);
             else     /* couldn't open tap */
               { FreeMem (wd->pktinfo.tapwait.handle, sizeof (struct FileHandle));
                 pkt->dp_Res1= 0;
                 pkt->dp_Res2= ERROR_INVALID_COMPONENT_NAME;
                 ReplyPkt (wd->pktinfo.tapwait.clientpkt);
               }

             FreeMem (BPTRtoCptr (pkt->dp_Arg3), OPENTAP_STRSIZE);
             break;

      case ACTION_END:     /* for a tap close request */
             FreeMem (wd->pktinfo.tapwait.handle, sizeof (struct FileHandle));
             break;

      case ACTION_WRITE:     /* for a tap write request */
             ReplyPkt (wd->pktinfo.tapwait.clientpkt);     /* return to client */
             break;

#ifdef DEBUG
      default:     /* should never happen */
             OS ("!!! ERROR - bad packet type in HandleTapReply(), type ="); OL (pkt->dp_Type); NL;
#endif DEBUG
    }

  FreePacket (pkt);
  FreeMem (wd, sizeof (WAITINGDATA));
}
