/* 
** $VER: TeeHandler.c 1.39 (21st Oct 1997) 
*/
#include <exec/types.h>
#include <exec/tasks.h>
#include <exec/ports.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/filehandler.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>


/* Program Information */
#define PROG_NAME  "tee-handler"
#define PROG_VER   "1"
#define PROG_REV   "39"
#define PROG_DATE  "21st Oct 1997"

/* Version String */
const char VerStr[] = "\0$VER: "PROG_NAME" "PROG_VER"."PROG_REV" ("PROG_DATE")\r\n";


#ifdef DEBUG

/* Prototypes for debug.lib functions */
extern __stkargs void kputs(STRPTR string);
extern __stkargs void kprintf(STRPTR format, ... );


/* Strings associated with actions known by this handler */
STRPTR Actions[] = {"ACTION_FINDUPDATE",
                    "ACTION_FINDOUTPUT",
                    "ACTION_SEEK",
                    "ACTION_WRITE",
                    "ACTION_END",
                    "ACTION_IS_FILESYSTEM"};

/*
** KPrint a string indicating packet type.
*/
__regargs void KPutPacketName(LONG Type)
{
  int i;
  
  switch (Type) {
    case ACTION_FINDUPDATE:     i = 1; break;
    case ACTION_FINDOUTPUT:     i = 2; break;
    case ACTION_SEEK:           i = 3; break;
    case ACTION_WRITE:          i = 4; break;
    case ACTION_END:            i = 5; break;
    case ACTION_IS_FILESYSTEM:  i = 6; break;
    default:                    i = 0;
  }
  if (i) kprintf("TeeHandler: Got an %s packet.\n\n",Actions[i-1]);
  else   kprintf("TeeHandler: Got an Unknown packet (%ld).\n\n",Type);
}

#endif


/*
** Convert a BString to a CString.
** String must be duplicated if you wish to keep it.
*/
__regargs STRPTR b2cstr(BSTR bstring)
{
#ifdef DEBUG
  kputs  ("            TeeHandler: b2cstr() Called:\n");
  kprintf("                        BString = \"%b\"\n",bstring);
#endif

  static char cstring[255];
  STRPTR realbstr;
  UBYTE len;
  int i;
  
  realbstr = (STRPTR)BADDR(bstring);
  len = realbstr[0];
  
  for (i=0; i<len; i++)
    cstring[i] = realbstr[i+1];
  cstring[i] = '\0';
  
#ifdef DEBUG
  kprintf("                        CString = \"%s\"\n",cstring);
  kputs  ("            TeeHandler: b2cstr() Finished.\n\n");
#endif

  return cstring;
}

/*
** Get a pointer to the Process that sent the packet.
*/
#define FindPktProc(pkt) \
         ((struct Process *)((UBYTE *)(pkt)->dp_Port - sizeof(struct Task)))


struct TeeArgs {
  ULONG  ArgC;
  STRPTR *ArgV;
};

/*
** Parse a given Argument String into C-style ArgC/ArgV.
** Format of TeeHandler Arguments:
**    <DOSDEV>:<Arg1>,<Arg2>,...,<ArgN>   ;   "\," ==> ","
*/
__regargs struct TeeArgs *ParseArgs(STRPTR ArgS)
{
#ifdef DEBUG
  kputs  ("            TeeHandler: ParseArgs() Called.\n");
#endif

  struct TeeArgs *teeArgs;
  STRPTR *ArgV;
  STRPTR Strings;
  ULONG ArgC;
  ULONG CharC;
  int i,j,k,l;
  
  
  /* Count the number of Arguments and Characters */
  
  /* First for DOSDEV: ... */
  for (i=0; ArgS[i] != ':'; i++)
    ;
  i++;
  ArgC = 1;
  CharC = i;
  
  /* Then for the rest of the Arguments */
  for (j=i; ArgS[j] != '\0'; j++)
  {
    if (ArgS[j] == ',')
      ArgC++;
    else if (ArgS[j] == '\\')
      j++;
    
    CharC++;
  }
  ArgC++;
  
  /* Add two NULL byte for DeviceName: and last argument (no commas) */
  CharC += 2;
  
  /* Allocate memory for ArgC string pointers   */
  /*  = sizeof(teeArgs) + ArgC*4 + CharC        */
  teeArgs = (struct TeeArgs *)
            AllocVec(sizeof(struct TeeArgs) + (ArgC<<2) + CharC, NULL);
  if (!teeArgs)
    /* Oh dear, no memory ... Shall we try it with individual   */
    /* allocations?  Why bother?  CRASH AND BURN, HA HA HA HA!! */
    return NULL;
  
  /* So a map of our memory block would be:  */
  /*   struct TeeArgs {                      */
  /*     ULONG   ArgC;                       */
  /*     STRPTR *ArgV;                       */
  /*   };                                    */
  /*   STRPTR ArgV[ArgC];                    */
  /*   "DeviceName:",0                       */
  /*   "Arg1",0                              */
  /*   "Arg2",0                              */
  /*     ...                                 */
  /*   "ArgN",0    (where N == ArgC-1)       */
  
  /* Link it all together ... */
  teeArgs->ArgC = ArgC;
  ArgV = (STRPTR *)((UBYTE *)teeArgs + sizeof(struct TeeArgs));
  teeArgs->ArgV = ArgV;
  Strings = (STRPTR)((UBYTE *)ArgV + (ArgC<<2));
  ArgV[0] = Strings;
  
  /*  i indicates the current position in the ArgS string          */
  /*  j incicates the current position in the Strings memory block */
  /*  k indicates the current argument                             */
  /*  l indicates the start of the current argument                */
  
  /* Copy device name */
  for (i=0, j=0; ArgS[i] != ':'; i++, j++)
    Strings[j] = ArgS[i];
  Strings[j++] = ':'; i++;
  Strings[j++] = '\0';
  
  /* Copy arguments */
  for (k=1,l=j; ArgS[i] != '\0'; i++,j++)
  {
    if (ArgS[i] == ',')
    {
      Strings[j] = '\0';
      
      ArgV[k++] = Strings+l;
      l = j+1;
    }
    else if (ArgS[i] == '\\')
      Strings[j] = ArgS[++i];
    else
      Strings[j] = ArgS[i];
  }
  Strings[j] = '\0';
  ArgV[k] = Strings+l;
  
  /* Well, that should do it... */
#ifdef DEBUG
  kputs  ("            TeeHandler: ParseArgs() Finished.\n\n");
#endif
  return teeArgs;
}


/*
** Delete the entire TeeArgs structure and associated fields.
*/
__regargs void DeleteArgs(struct TeeArgs *teeArgs)
{
#ifdef DEBUG
  kputs  ("            TeeHandler: DeleteArgs() Called.\n");
#endif

  /* Because this was allocated in one big block, */
  /* we can release it all with just one call     */
  FreeVec((APTR)teeArgs);

#ifdef DEBUG
  kputs  ("            TeeHandler: DeleteArgs() Finished.\n\n");
#endif
}


/*
** Perform WaitPkt() with a port other than pr_MsgPort.
*/
__regargs struct DosPacket *WaitPktPort(struct MsgPort *port)
{
  struct Message *msg;

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

/*
** Perform ReplyPkt() with a port other then pr_MsgPort.
*/
__regargs void ReplyPktPort(struct MsgPort *port, 
                            struct DosPacket *dp, LONG Res1, LONG Res2)
{
  struct MsgPort *rport = dp->dp_Port;
  
  dp->dp_Res1 = Res1;
  dp->dp_Res2 = Res2;
  dp->dp_Port = port;
  
  PutMsg(rport,dp->dp_Link);
}


struct DOSLibrary *DOSBase;

/*
** Entry Point
*/
__stkargs int _main(int len, char *args)
{
  struct TeeArgs *teeArgs;
  ULONG  ArgC;
  STRPTR *ArgV;
  BPTR   *OutputFiles;    /* [0] = StdOutFile, [1..n] = OutputFiles */
  
  struct DosPacket *packet;
  struct MsgPort *teePort;
  struct DeviceNode *teeDevNode;
  struct Process *teeProc;
  struct Process *callProc;
  struct FileHandle *teeFileHandle;
  
  ULONG  running = TRUE;
  ULONG  opencnt = 0;
  LONG   rc = DOSTRUE, rc2 = 0;
  int i;
  
  
#ifdef DEBUG
  kputs  ("TeeHandler Started.\n\n");
#endif
  
  /* Get this process and attempt to open DOS Library v36+ */
  teeProc = (struct Process *)FindTask(NULL);
  DOSBase = (struct DOSLibrary *)OpenLibrary("dos.library",36);
  if (!DOSBase)
    /* Maybe an alert here to inform the good user that s/he's using */
    /* an obsolete operating system?                                 */
    return RETURN_FAIL;
  
  /* Wait for the startup packet */
#ifdef DEBUG
  kputs("TeeHandler: Waiting for startup packet...\n\n");
#endif
  packet = WaitPktPort(&teeProc->pr_MsgPort);
  
  /* Got the startup packet, now initialise it and reply */
  /*  dp_Arg1 ==> (BSTR) Mount Name (ie Mount TEE:)      */
  /*  dp_Arg3 ==> (BPTR) Device Node                     */
  
  /* Create a new MsgPort so we don't confuse the dos.library */
  teePort = CreateMsgPort();
  if (!teePort)
  {
    /* No memory? No signal? */
    CloseLibrary((struct Library *)DOSBase);
    rc = FALSE;
    rc2 = ERROR_OBJECT_NOT_FOUND;
  }
  
  teeDevNode = (struct DeviceNode *)BADDR(packet->dp_Arg3);
  teeDevNode->dn_Task = teePort;
  ReplyPktPort(teePort,packet,rc,rc2);
  if (rc2)
    return RETURN_FAIL;
  
  /* Main Loop */
#ifdef DEBUG
  kputs("TeeHandler: Starting Main Loop\n");
#endif
  while (running)
  {
    /* Wait for an action packet */
#ifdef DEBUG
    kputs("TeeHandler: Waiting for action packet...\n");
#endif
    packet = WaitPktPort(teePort);
#ifdef DEBUG
    KPutPacketName(packet->dp_Type);
#endif

    /* Got an action packet. */
    switch (packet->dp_Type)
    {
    /*******************************************************************/
    
    case ACTION_FINDUPDATE:
    case ACTION_FINDOUTPUT:
      /* Setup for output (eg Open("TEE:",MODE_OLDFILE))   */
      /*  dp_Arg1 = (BPTR) File Handle                     */
      /*  dp_Arg3 = (BSTR) Access Name (eg Dir >TEE:T:bla) */
      
      opencnt++;                          /* We have another user! */
      
#ifdef DEBUG
      kprintf("            Argument String = \"%b\"\n\n",packet->dp_Arg3);
#endif
      teeArgs = ParseArgs(b2cstr((BSTR)packet->dp_Arg3));
      if (!teeArgs)
      {
        /* Aww, no memory */
        if (!(--opencnt)) running = FALSE;
        rc = FALSE;
        rc2 = ERROR_NO_FREE_STORE;
        
        break;
      }
      ArgC = teeArgs->ArgC;
      ArgV = teeArgs->ArgV;
      
      /* Now Allocate some memory for the Output Files Array */
      /*  = ArgC + 2 (StdOut and -1 End Marker)              */
      OutputFiles = (BPTR *)AllocVec( (ArgC+2)<<2 ,MEMF_CLEAR);
      if (!OutputFiles)
      {
        /* No Memory! */
        DeleteArgs(teeArgs);
        if (!(--opencnt)) running = FALSE;
        rc = FALSE;
        rc2 = ERROR_NO_FREE_STORE;
        
        break;
      }
      
      /* Save the caller's Output and change to it's CurrentDir */
      callProc = FindPktProc(packet);
      OutputFiles[0] = callProc->pr_COS;
      CurrentDir(callProc->pr_CurrentDir);
#ifdef DEBUG
      struct CommandLineInterface *CLI = (struct CommandLineInterface *)
                                          BADDR(callProc->pr_CLI);

      kprintf("            Calling Process: 0x%08lx\n",callProc);
      kprintf("            Task Name:       %s\n",callProc->pr_Task.tc_Node.ln_Name);
      if (CLI)
        kprintf("            CommandName:     %b\n",CLI->cli_CommandName);
      kprintf("            Output Stream:   0x%08lx\n\n",OutputFiles[0]);
#endif
      
      /* Open each of our Output files */
      for (i=1; i < ArgC; i++)
      {
#ifdef DEBUG
        kprintf("            OutputName = \"%s\"\n",ArgV[i]);
#endif
        if (ArgV[i][0] != '\0')
          OutputFiles[i] = Open(ArgV[i],packet->dp_Type);
        
#ifdef DEBUG
        kprintf("            OutputFile Handle = 0x%08lx\n\n",OutputFiles[i]);
#endif
      }
      /* Set the last field to -1 */
      OutputFiles[ArgC] = -1;
      
      /* Set the file handle's relevent items */
      teeFileHandle = (struct FileHandle *)BADDR(packet->dp_Arg1);
      teeFileHandle->fh_Port = NULL;
      teeFileHandle->fh_Args = (LONG)teeFileHandle;
      teeFileHandle->fh_Arg2 = (LONG)OutputFiles;
      
      /* Get rid of the Arguments */
      DeleteArgs(teeArgs);
      
      break;
    
    /*******************************************************************/
    
    case ACTION_SEEK:
      /* Perform Seek Action (eg Seek(TeeFile, Position, Offset))      */
      /*  dp_Arg1 = File Handle's fh_Args (set by ACTION_FIND... )     */
      /*  dp_Arg2 = (LONG) Position to move to, based on offset.       */
      /*  dp_Arg3 = (LONG) Offset: Start or End of file or current pos */
      
#ifdef DEBUG
      int t = packet->dp_Arg3;
      
      kprintf("            Offset Position: %s+(%ld).\n\n",
                                 (t ? (t < 0 ? "Start" : "End") : "Current"),
                                 packet->dp_Arg2);
#endif
      
      teeFileHandle = (struct FileHandle *)packet->dp_Arg1;
      OutputFiles = (BPTR *)teeFileHandle->fh_Arg2;
      
      for (i=1; OutputFiles[i] != -1; i++)
        if (OutputFiles[i])
          Seek(OutputFiles[i],packet->dp_Arg2,packet->dp_Arg3);
      
      break;
    
    /*******************************************************************/
    
    case ACTION_WRITE:
      /* Perform write actions (eg Write(TeeFile, buffer, bufsize)) */
      /*  dp_Arg1 = File Handle's fh_Args (set by ACTION_FIND... )  */
      /*  dp_Arg2 = (APTR) Pointer to buffer to write               */
      /*  dp_Arg3 = (ULONG) Number of bytes to write                */
      
#ifdef DEBUG
      kprintf("            Writing %ld bytes.\n\n",packet->dp_Arg3);
#endif
      
      teeFileHandle = (struct FileHandle *)packet->dp_Arg1;
      OutputFiles = (BPTR *)teeFileHandle->fh_Arg2;
      rc = packet->dp_Arg3;
      
      for (i=0; OutputFiles[i] != -1; i++)
        if (OutputFiles[i])
          Write(OutputFiles[i],(APTR)packet->dp_Arg2,rc);
      
      break;
    
    /*******************************************************************/
    
    case ACTION_END:
      /* Reverse actions taken by ACTION_FIND* ...                  */
      /*  dp_Arg1 = File Handle's fh_Args (set by ACTION_FIND... )  */
      
      teeFileHandle = (struct FileHandle *)packet->dp_Arg1;
      OutputFiles = (BPTR *)teeFileHandle->fh_Arg2;
      
      for (i=1; OutputFiles[i] != -1; i++)
        if (OutputFiles[i])
          Close(OutputFiles[i]);
      
      FreeVec((APTR)OutputFiles);
      
      if (!(--opencnt)) running = FALSE;
      break;
    
    /*******************************************************************/
    
    case ACTION_IS_FILESYSTEM:
      /* Is this a FileSystem?  Erm ... Nope.  Sorry. */
      
      rc = FALSE;
      /* rc2 = 0 */
      
      break;
    
    /*******************************************************************/
    
    default:
      /* All other packets dock here */
      
      rc = FALSE;
      rc2 = ERROR_ACTION_NOT_KNOWN;
      
      /* break; */
    
    /*******************************************************************/
    }
    
    /* Reply back to the calling Process */
    ReplyPktPort(teePort,packet,rc,rc2);
  }
  
  /* Now we close up and exit */
#ifdef DEBUG
  kputs("TeeHandler Shutting Down.\n\n\n");
#endif
  teeDevNode->dn_Task = NULL;
  DeleteMsgPort(teePort);
  CloseLibrary((struct Library *)DOSBase);
  
  return RETURN_OK;
}
