/****************************************************************************
**  File:       pipe-handler.c
**  Program:    pipe-handler - an AmigaDOS handler for named pipes
**  Version:    1.2
**  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 shared locks for individual pipes.
**				PIPEDATA structure modified to include
**				 a FileLock structure.
**		07-Feb-87	Added #if's forautomatic pipe naming "feature"
**				 for pipes specified with empty names.
**		12-Feb-87	Added ParentDir packet handling.
**		12-Feb-87	Fixed bug in OpenPipe() and PipeLock():
**				 they previously ignored the lock passed in
**				 packet.  Bug uncovered when pipes became
**				 lockable, and thus assignable.
**		27-Mar-87	Added the case for PipeDupLock().  This was
**				 missing in the original version!
**		28-Mar-87	Added code to handler() to remove ':' from
**				 end of handler name.  This caused problems
**				 with Examine(); it expects no ending  ':'.
*/

#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



/*---------------------------------------------------------------------------
** pipe-handler.c
** --------------
** This is the main module for the handler.  Handlers are started with
** register D1 containing a BPTR to a startup packet, which in turn contains
** (BCPL) pointers to the name and DeviceNode.  Since the entry, handler(),
** expects a byte address of the startup packet, an assembly language startup
** must be used to convert the BCPL pointer, and pass it on the stack.
**
** Problems arise if a handler tries to do I/O via the DOS functions Open(),
** Close(), Read() and Write().  DOS sends request packets to the handler
** via its DOS port (the one whose address forms the process ID).  This is
** also the port used by the I/O functions.  Therefore, if a request comes,
** and then an Open() call is performed, DOS will send a request packet for
** the open and erroneously pick up the request packet meant for the handler
** as its reply.  A crash ensues.
**
** This is the reason for the I/O functions in pipedebug.c.  They implement
** the regualar I/O calls, but use a different ReplyPort.  With no debugging,
** these functions are unneeded, since all of the handler's normal I/O is
** performed asynchronously, using PutMsg().
**
** An alternate solution is to patch the handler's Task field with a new port
** instead of the handler's DOS port.  This works, except that DOS always
** sends the initial request packets to the DOS port (when the handler is
** first started).  This is probably because DeviceProc(), upon seeing that
** the handler has not yet been loaded, returns the result from its call to
** CreateProc() for the handler process.  Only on subsequent calls to
** DeviceProc() will the patched field be returned.  The upshot of this is
** that an alternate port can be used for handler requests, but there are
** always an unspecified number that may come over the DOS port regardless.
** Note that since not all handlers patch their Task field (because they want
** to be restarted each time), DOS is doing the "right" thing, or at least
** the best it can.
**
** Visible Functions
** -----------------
**	void      handler   (StartPkt)
**	PIPEDATA  *FindPipe (name)
**
** Macros (in pipe-handler.h)
** --------------------------
**	BPTRtoCptr (Bp)
**	CptrtoBPTR (Cp)
**	ReplyPkt   (pkt)
**
** Local Functions
** ---------------
**	struct DosPacket  *GetPkt (port)
*/



/*---------------------------------------------------------------------------
** HandlerName  : passed as a BSTR in startup packet Arg1, our device name.
**		Everything from the ':' and beyond is removed.
**		Used by PipeExamine() for the handler's "directory" name.
**
** DevNode	: passed as a BPTR in startup packet Arg3.  This is a pointer
**		to our DeviceNode entry in the system device list (DevInfo).
**
** Pipeort	: our DOS MsgPort, as well as our process ID.  See above for
**		notes about why we can't let DOS use this.
**
** pipelist	: the list of currently existing pipes.  PIPEDATA nodes are
**		linked into this list.
**
** tapwaitlist	: the list of requests waiting on tap opens/closes/writes.
**		WAITINGDATA nodes are linked into this list.  See pipesched.c
**		and pipecreate.c.
**
** TapReplyPort	: this is the MsgPort to which tap I/O replys are returned.
**
** SysBase,
** DOSBase	: Standard system library pointers.  Since we don't have the
**		usual startup code, we must initialize these ourselves.
**
** PipeDate	: If compiled with PIPEDIR true, the handler responds to some
**		directory-like actions.  This is the date for the entire
**		handler, i.e., the directory date.  The flag UPDATE_PIPEDATE
**		controls whether this date is updated with each pipe access
**		(true) or not (false).  See SetPipeDate() and PipeExamine().
*/

char               HandlerName[30];
struct DeviceNode  *DevNode   =  NULL;
struct MsgPort     *PipePort  =  NULL;

PIPELISTHEADER     pipelist;

PIPELISTHEADER     tapwaitlist;
struct MsgPort     *TapReplyPort  =  NULL;

struct Library     *SysBase  =  NULL;
struct Library     *DOSBase  =  NULL;

#if PIPEDIR
  struct DateStamp   PipeDate;
#endif PIPEDIR



/*---------------------------------------------------------------------------
** Performs initialization, replies to startup packet, and dispatches
** incoming request packets to the apropriate functions.  The TapReplyPort is
** also monitored for returning requests which were sent out by the handler.
** These returned requests are routed to HandleTapReply().
**      Our DeviceNode Task field is patched with our process ID so that this
** process is used for subsequent handler requests.  The function exits only
** if there is some initialization error.
*/

void  handler (StartPkt)

struct DosPacket  *StartPkt;

{ char              *cp;
  struct Task       *Task;
  ULONG             PipeMask, TapReplyMask, WakeupMask, SigMask;
  struct DosPacket  *pkt, *GetPkt();
  void              OpenPipe(), ClosePipe();


  SysBase= AbsExecBase;
  if ((DOSBase= OpenLibrary (DOSNAME, 0)) == NULL)
    goto QUIT;

  BSTRtoCstr (BPTRtoCptr (StartPkt->dp_Arg1), HandlerName, sizeof (HandlerName));
  for (cp= HandlerName; *cp != '\0'; ++cp)
    if (*cp == ':')      /* remainder of handler's first refernece follows */
      { *cp= '\0';
        break;
      }

  Task= FindTask (0);
  PipePort= (struct MsgPort *) ((ULONG) Task + sizeof (struct Task));
  ((struct Process *) Task)->pr_CurrentDir= 0;     /* initial file system root */

  if ((TapReplyPort= CreatePort (NULL, PipePort->mp_Node.ln_Pri)) == NULL)
    goto QUIT;

#ifdef DEBUG
  if (! InitDebugIO (PipePort->mp_Node.ln_Pri))
    goto QUIT;
#endif DEBUG


  PipeMask=     (1L << PipePort->mp_SigBit);
  TapReplyMask= (1L << TapReplyPort->mp_SigBit);
  WakeupMask=   (PipeMask | TapReplyMask);

  DevNode= (struct DeviceNode *) BPTRtoCptr (StartPkt->dp_Arg3);
  DevNode->dn_Task= PipePort;

  InitList (&pipelist);
  InitList (&tapwaitlist);

#if PIPEDIR
  (void) DateStamp (&PipeDate);
#endif PIPEDIR

  ReplyPkt (StartPkt);


LOOP:
  SigMask= Wait (WakeupMask);

  if (SigMask & TapReplyMask)
    while ((pkt= GetPkt (TapReplyPort)) != NULL)
      HandleTapReply (pkt);

  if (SigMask & PipeMask)
    while ((pkt= GetPkt (PipePort)) != NULL)
      switch (pkt->dp_Type)
        { case MODE_READWRITE:
#ifdef DEBUG
  OS ("Open READWRITE packet received\n");
#endif DEBUG
            OpenPipe (pkt, 0);
            break;

          case MODE_READONLY:     /* syn: MODE_OLDFILE, ACTION_FINDINPUT */
#ifdef DEBUG
  OS ("Open READONLY packet received\n");
#endif DEBUG
            OpenPipe (pkt, 0);
            break;

          case MODE_NEWFILE:     /* syn: ACTION_FINDOUTPUT */
#ifdef DEBUG
  OS ("Open NEWFILE packet received\n");
#endif DEBUG
            OpenPipe (pkt, 0);
            break;

          case ACTION_END:
#ifdef DEBUG
  OS ("Close packet received\n");
#endif DEBUG
            ClosePipe (pkt);
            break;

          case ACTION_READ:
#ifdef DEBUG
  OS ("<<< Read packet received\n");
#endif DEBUG
            StartPipeIO (pkt, PIPEREAD);
            break;

          case ACTION_WRITE:
#ifdef DEBUG
  OS (">>> Write packet received\n");
#endif DEBUG
            StartPipeIO (pkt, PIPEWRITE);
            break;

#if PIPEDIR
          case ACTION_LOCATE_OBJECT:
#  ifdef DEBUG
     OS (  "Lock packet received\n");
#  endif DEBUG
            PipeLock (pkt);
            break;

          case ACTION_COPY_DIR:
#  ifdef DEBUG
     OS (  "DupLock packet received\n");
#  endif DEBUG
            PipeDupLock (pkt);
            break;

          case ACTION_FREE_LOCK:
#  ifdef DEBUG
     OS (  "UnLock packet received\n");
#  endif DEBUG
            PipeUnLock (pkt);
            break;

          case ACTION_EXAMINE_OBJECT:
#  ifdef DEBUG
     OS (  "Examine packet received\n");
#  endif DEBUG
            PipeExamine (pkt);
            break;

          case ACTION_EXAMINE_NEXT:
#  ifdef DEBUG
     OS (  "ExNext packet received\n");
#  endif DEBUG
            PipeExNext (pkt);
            break;

          case ACTION_PARENT:
#  ifdef DEBUG
     OS (  "ParentDir packet received\n");
#  endif DEBUG
            PipeParentDir (pkt);
            break;
#endif PIPEDIR

          default:
#ifdef DEBUG
  OS ("BAD packet received, type = "); OL (pkt->dp_Type); NL;
#endif DEBUG
            pkt->dp_Res1= 0;
            pkt->dp_Res2= ERROR_ACTION_NOT_KNOWN;
            ReplyPkt (pkt);
        }

  goto LOOP;


QUIT:
  DevNode->dn_Task= NULL;     /* bad if someone in process of accessing us . . . */

  if (TapReplyPort != NULL)
    FreeMem (TapReplyPort, sizeof (struct MsgPort));     /* signal bit won't matter */

#ifdef DEBUG
  CleanupDebugIO ();
#endif DEBUG

  if (DOSBase != NULL)
    CloseLibrary (DOSBase);
}



/*---------------------------------------------------------------------------
** Returns the DosPacket associated with the next message on "port", or NULL
** if the port is empty.  The message is removed from the port.
** A related macro, ReplyPkt() is provided in pipe-handler.h.
*/

static struct DosPacket  *GetPkt (port)

register struct MsgPort  *port;

{ register struct Message  *msg;

  return  ((msg= GetMsg (port)) == NULL)
            ? NULL
            : (struct DosPacket *) msg->mn_Node.ln_Name;
}



/*---------------------------------------------------------------------------
** Searches "pipelist" for a pipe whose name is "name".  If found, a pointer
** to the pipe returns.  Otherwise, NULL returns.
*/

PIPEDATA  *FindPipe (name)

char  *name;

{ PIPEDATA  *p;
  char      *cp, *strdiff();


  for (p= (PIPEDATA *) FirstItem (&pipelist); p != NULL; p= (PIPEDATA *) NextItem (p))
    { cp= strdiff (name, p->name);

      if ((*cp == '\0') && (p->name[(LONG) cp - (LONG) name] == '\0'))
        return p;     /* same name */
    }

  return NULL;     /* no match found */
}
