/*
**  Pgmpipe.c --- main module of APipe-Handler
**
**  Copyright (C) 1991 by Per Bojsen.  All Rights Reserved.
**
**  Permission is granted to any individual or institution to use, copy,
**  modify, and distribute this software, provided that this complete
**  copyright and permission notice is maintained, intact, in all copies
**  and supporting documentation.
**
**  This software is provided on an "as is" basis without express or
**  implied warranty.
**
**  NOTE: The code is reentrant.  Be careful when modifying it.
**
**  $Id: pgmpipe.c,v 1.6 92/02/20 23:46:11 bojsen Exp Locker: bojsen $
**
**  $Log:	pgmpipe.c,v $
**  Revision 1.6  92/02/20  23:46:11  bojsen
**  Fixed bug in handling multiple outstanding read packets add EOF.
**  
**  Revision 1.5  91/11/24  17:01:25  bojsen
**  Made extraction of command line from file name more robust; the code now
**  handles the case where the handler name is not prepended.
**
**  Revision 1.4  91/11/24  02:22:39  bojsen
**  First released version.  Changed references to ChildProcess() to
**  _ChildProcess().
**
**  Revision 1.3  91/10/01  02:35:30  bojsen
**  Added code to clone current dir of requester process.  Changed code
**  to obtain pointer to requester process.
**
**  Revision 1.2  91/09/24  23:59:44  bojsen
**  Fixed typo in #include line.
**
**  Revision 1.1  91/09/24  23:54:06  bojsen
**  Initial revision
**
*/

#include <exec/types.h>
#include <exec/memory.h>
#include <utility/tagitem.h>
#include <clib/macros.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>
#include <dos/var.h>
#ifdef __SASC
#include <proto/exec.h>
#include <proto/dos.h>
#endif /* __SASC */
#include <string.h>
#include "APipe-Handler_rev.h"

/*
 *  Version tagging.
 */
STATIC char VersionTag[] = VERSTAG;

/*
 *  Debugging.
 */
#ifdef DEBUG
#define DPrintF(args) do { KPrintF args; } while (0)
#else /* !DEBUG */
#define DPrintF(args)
#endif /* !DEBUG */

#define AbsExecBase 4L
#define LIB_VERSION_SUPPORTED 37L
#define PIPEBUFFER_SIZE 4096

/* defines for read/write status */
#define PPIPE_NOTOPEN   0
#define PPIPE_OPEN      4
#define PPIPE_READ      1
#define PPIPE_READOPEN  (PGMPIPE_READ | PGMPIPE_OPEN)
#define PPIPE_WRITE     2
#define PPIPE_WRITEOPEN (PGMPIPE_WRITE | PGMPIPE_OPEN)
#define PPIPE_RWMASK    3

/* defines for fh_Arg1 field of filehandles */
#define PPIPE_READER  1
#define PPIPE_WRITER  2

/* defines for closing variable */
#define PPIPE_C_OPEN          0
#define PPIPE_C_RDRCLOSING    1
#define PPIPE_C_WRTCLOSING    2
#define PPIPE_C_CLOSINGMASK   3
#define PPIPE_C_RDRCLOSED     4
#define PPIPE_C_WRTCLOSED     8
#define PPIPE_C_CLOSEDMASK  0xC

/*
 *  Parameter packet to child process.
 */
struct ChildMsg
{
  struct Message  ExecMsg;
  char           *CmdLine;   /* the command line to execute */
  LONG            StackSize; /* use this stacksize for child's child */
  BPTR            PathList;  /* path list for child */
  ULONG           Flags;     /* flags for child */
  LONG            RC;        /* return code from child process */
};

/* Defines for ChildMsg.Flags */
#define CHMF_PIPE 1L /* stdin of child is a pipe (of PIPE: type) */
#define CHMF_PATH 2L /* path is passed in in PathList */

/*
 *  Command path element.
 */
struct PathEntry
{
  BPTR pe_NextPathEntry;
  BPTR pe_PathLock;
};

/*
 *  The library bases must be global by AmigaOS programming standards,
 *  and the pragma libcall requires them.
 */
struct ExecBase *SysBase;
struct DosLibrary *DOSBase;


/*
 *  Prototypes for external functions.
 */
extern int _ChildProcess(void);

/*
 *  Prototypes for local functions.
 */
ULONG CopyFromFIFO(UBYTE *FIFO, ULONG FIFOSize, UBYTE **Start, UBYTE *End,
                   ULONG *FIFOFill, UBYTE *Dest, ULONG NumBytes);
ULONG CopyToFIFO(UBYTE *FIFO, ULONG FIFOSize, UBYTE **Start, UBYTE **End,
                 ULONG *FIFOFill, UBYTE *Src, ULONG NumBytes);
STRPTR GetCommandLine(STRPTR FileSpec, BSTR HandlerName);
LONG ExecCommand(char *CmdLine, struct MsgPort **ParentPort,
                 struct Process *Requester, struct FileHandle *ChildIO,
                 LONG IoDirection);
BPTR CloneProcessIO(struct Process *Friend, LONG IoDirection);
BPTR ClonePathList(struct CommandLineInterface *Peer);
void FreePathList(BPTR);

void
PgmPipe()
{
  struct DosLibrary *l_DOSBase;

  SysBase = *((struct ExecBase **) AbsExecBase); /* `open' exec.library */

  if (l_DOSBase =
        (struct DosLibrary *) OpenLibrary("dos.library", LIB_VERSION_SUPPORTED))
  {
    struct Process *ourProc, *theirProc;
    struct DosList *ourDevNode;
    struct DosPacket *packet, *writePkt = NULL, *readPkt = NULL;
    struct DosPacket *writeCls = NULL, *readCls = NULL;
    struct MsgPort *childPort = NULL;
    struct MsgPort *oldConTask = NULL;
    struct ChildMsg *childMsg = NULL;
    struct FileHandle *fhIncoming, *fhOutgoing;
    UBYTE *pipeBuffer;
    UBYTE *pbStart, *pbEnd;
    ULONG bytesReady, bytesR = 0, bytesW = 0;
    UWORD closing = PPIPE_C_RDRCLOSED | PPIPE_C_WRTCLOSED;
    UWORD readwrite = PPIPE_NOTOPEN;
    ULONG waitSigMask;
    struct MinList readQ, writeQ;
#ifdef DEBUG
    ULONG packetNum = 0;
#endif /* DEBUG */

    NewList((struct List *) &readQ);
    NewList((struct List *) &writeQ);

    DOSBase = l_DOSBase; /* patch the global library base */

    ourProc = (struct Process *) FindTask(NULL);
    waitSigMask = 1L << ourProc->pr_MsgPort.mp_SigBit;
    packet = WaitPkt(); /* get parameter packet */

    if ((pipeBuffer = AllocMem(PIPEBUFFER_SIZE, 0)) == NULL)
      ReplyPkt(packet, DOSFALSE, ERROR_NO_FREE_STORE);
    else
    {
      pbStart = pbEnd = pipeBuffer;
      bytesReady = 0;

      /*
       *  Currently we don't use the parameters provided in the parameter
       *  packet.  Since we want a new process for each instance of reference
       *  to our handler we don't patch the DeviceList node, but use the
       *  node to get the handler name.
       */

      ourDevNode = (struct DosList *) BADDR(packet->dp_Arg3);

      ReplyPkt(packet, DOSTRUE, packet->dp_Arg2);

      do
      {
        struct Node *node;
        char *cmdLine;
        LONG ioerr;

        if (readPkt && bytesReady == 0 && writePkt == NULL &&
            closing & PPIPE_C_WRTCLOSED)
        {
          ReplyPkt(readPkt, bytesR, 0);
          while (readPkt = (struct DosPacket *)
                             (node = RemHead((struct List *) &readQ),
                              node ? node->ln_Name : NULL))
            ReplyPkt(readPkt, 0, 0);
        }

#ifdef DEBUG
        if (readPkt || writePkt)
          DPrintF(("Event loop: readPkt == 0x%08lx writePkt == 0x%08lx\n",
                   readPkt, writePkt));
#endif /* DEBUG */

        if (packet = WaitPkt())
        {
          DPrintF(("Packet %ld, port 0x%08lx: ", ++packetNum, packet->dp_Port));

          switch (packet->dp_Type)
          {
            struct TagItem adoTags[2];

            case ACTION_FINDINPUT:
              DPrintF(("ACTION_FINDINPUT 0x%08lx 0x%08lx %s\n",
                       packet->dp_Arg1 << 2, packet->dp_Arg2 << 2,
                       (char *) (packet->dp_Arg3 << 2) + 1));

              if (readwrite != PPIPE_NOTOPEN)
              {
                ReplyPkt(packet, DOSFALSE, ERROR_OBJECT_IN_USE);
                break;
              }
              else
                readwrite = PPIPE_READ;

            case ACTION_FINDOUTPUT:
#ifdef DEBUG
              if (packet->dp_Type == ACTION_FINDOUTPUT)
                DPrintF(("ACTION_FINDOUTPUT 0x%08lx 0x%08lx %s\n",
                         packet->dp_Arg1 << 2, packet->dp_Arg2 << 2,
                         (char *) (packet->dp_Arg3 << 2) + 1));
#endif /* DEBUG */

              if (readwrite & PPIPE_OPEN)
              {
                ReplyPkt(packet, DOSFALSE, ERROR_OBJECT_IN_USE);
                break;
              }
              else if (readwrite != PPIPE_READ)
                readwrite = PPIPE_WRITE;

              fhIncoming = (struct FileHandle *) BADDR(packet->dp_Arg1);

              /*
               *  Obtain command line to execute.
               */
              cmdLine = GetCommandLine((STRPTR) BADDR(packet->dp_Arg3) + 1,
                                       ourDevNode->dol_Name);

              /*
               *  Find out who sent the packet.
               */
              if ((packet->dp_Port->mp_Flags & PF_ACTION) == PA_SIGNAL)
                theirProc = (struct Process *) packet->dp_Port->mp_SigTask;
              else
              {
                ReplyPkt(packet, DOSFALSE, ERROR_BAD_STREAM_NAME);
                readwrite = PPIPE_NOTOPEN;
                break;
              }

              adoTags[0].ti_Tag = ADO_FH_Mode;
              adoTags[0].ti_Data =
                readwrite == PPIPE_READ ? MODE_NEWFILE : MODE_OLDFILE;
              adoTags[1].ti_Tag = TAG_DONE;

              if ((fhOutgoing = AllocDosObject(DOS_FILEHANDLE, adoTags))
                    == NULL)
              {
                ReplyPkt(packet, DOSFALSE, ERROR_NO_FREE_STORE);
                readwrite = PPIPE_NOTOPEN;
                break;
              }

              fhOutgoing->fh_Link = NULL;
              fhOutgoing->fh_Port = fhIncoming->fh_Port = (struct MsgPort *) 0;
              fhOutgoing->fh_Type = &ourProc->pr_MsgPort;

              if (readwrite == PPIPE_READ)
              {
                fhIncoming->fh_Arg1 = PPIPE_READER;
                fhOutgoing->fh_Arg1 = PPIPE_WRITER;
              }
              else
              {
                fhIncoming->fh_Arg1 = PPIPE_WRITER;
                fhOutgoing->fh_Arg1 = PPIPE_READER;
              }

              oldConTask = SetConsoleTask(theirProc->pr_ConsoleTask);

              DPrintF(("Getting ready to spawn child . . .  "));

              if (ioerr = ExecCommand(cmdLine, &childPort, theirProc,
                                      fhOutgoing, readwrite))
              {
                SetConsoleTask(oldConTask);
                ReplyPkt(packet, DOSFALSE, ioerr);
                FreeDosObject(DOS_FILEHANDLE, fhOutgoing);
                readwrite = PPIPE_NOTOPEN;
                break;
              }
              SetConsoleTask(oldConTask);

              DPrintF(("Child spawned.\n"));

              waitSigMask |= 1L << childPort->mp_SigBit;

              closing = PPIPE_C_OPEN;
              readwrite |= PPIPE_OPEN;

              ReplyPkt(packet, DOSTRUE, packet->dp_Res2);

              break;

            case ACTION_READ:
              DPrintF(("ACTION_READ %ld 0x%08lx %ld\n", packet->dp_Arg1,
                       packet->dp_Arg2, packet->dp_Arg3));

              if (packet->dp_Arg2 == NULL || packet->dp_Arg3 < 0)
                ReplyPkt(packet, DOSFALSE, ERROR_BAD_NUMBER);
              else if (closing & PPIPE_C_RDRCLOSED)
                ReplyPkt(packet, DOSFALSE, ERROR_INVALID_LOCK);
              else if (packet->dp_Arg1 == PPIPE_WRITER)
                ReplyPkt(packet, 0, 0);
              else if (readPkt == NULL)
              {
                readPkt = packet;
                bytesR = 0;
              }
              else
                AddTail((struct List *) &readQ,
                        (struct Node *) packet->dp_Link);
              break;

            case ACTION_WRITE:
              DPrintF(("ACTION_WRITE %ld 0x%08lx %ld\n", packet->dp_Arg1,
                       packet->dp_Arg2, packet->dp_Arg3));

              if (packet->dp_Arg2 == NULL || packet->dp_Arg3 < 0)
                ReplyPkt(packet, DOSFALSE, ERROR_BAD_NUMBER);
              else if (closing & PPIPE_C_WRTCLOSED)
                ReplyPkt(packet, DOSFALSE, ERROR_INVALID_LOCK);
              else if (packet->dp_Arg1 == PPIPE_READER)
                ReplyPkt(packet, 0, 0);
              else if (writePkt == NULL)
              {
                writePkt = packet;
                bytesW = 0;
              }
              else
                AddTail((struct List *) &writeQ,
                        (struct Node *) packet->dp_Link);
              break;

            case ACTION_END:
              DPrintF(("ACTION_END %ld\n", packet->dp_Arg1));

              if (packet->dp_Arg1 == PPIPE_READER &&
                  (closing & PPIPE_C_RDRCLOSED) == 0)
              {
                closing |= PPIPE_C_RDRCLOSING;
                readCls = packet;
              }
              else if (packet->dp_Arg1 == PPIPE_WRITER &&
                       (closing & PPIPE_C_WRTCLOSED) == 0)
              {
                closing |= PPIPE_C_WRTCLOSING;
                writeCls = packet;
              }
              else
                ReplyPkt(packet, DOSTRUE, 0);
              break;

            case ACTION_IS_FILESYSTEM:
              DPrintF(("ACTION_IS_FILESYSTEM\n"));

              ReplyPkt(packet, DOSFALSE, 0);
              break;

            default:
              DPrintF(("%ld\n", packet->dp_Type));

              ReplyPkt(packet, DOSFALSE, ERROR_ACTION_NOT_KNOWN);
          }

          DPrintF(("\n"));
        }

        do
        {
          if (readPkt)
          {
            if (bytesReady > 0)
              bytesR += CopyFromFIFO(pipeBuffer, PIPEBUFFER_SIZE, &pbStart,
                                     pbEnd, &bytesReady,
                                     (UBYTE *) readPkt->dp_Arg2 + bytesR,
                                     readPkt->dp_Arg3 - bytesR);
            if (bytesR < readPkt->dp_Arg3 && writePkt)
            {
              ULONG bytes = MIN(writePkt->dp_Arg3 - bytesW,
                                readPkt->dp_Arg3 - bytesR);

              CopyMem((UBYTE *) writePkt->dp_Arg2 + bytesW,
                      (UBYTE *) readPkt->dp_Arg2 + bytesR, bytes);
              bytesR += bytes;
              if ((bytesW += bytes) == writePkt->dp_Arg3)
              {
                ReplyPkt(writePkt, writePkt->dp_Arg3, 0);
                writePkt = NULL;
              }
            }
            if (bytesR == readPkt->dp_Arg3)
            {
              ReplyPkt(readPkt, readPkt->dp_Arg3, 0);
              readPkt = NULL;
            }
          }
          if (readPkt == NULL &&
              (readPkt = (struct DosPacket *)
                           (node = RemHead((struct List *) &readQ),
                            node ? node->ln_Name : NULL)))
          {
            bytesR = 0;
          }
          if (writePkt == NULL &&
              (writePkt = (struct DosPacket *)
                            (node = RemHead((struct List *) &writeQ),
                             node ? node->ln_Name : NULL)))
          {
            bytesW = 0;
          }
        }
        while (readPkt && (writePkt || bytesReady > 0));

        if (readPkt == NULL && closing & PPIPE_C_RDRCLOSING)
        {
          closing &= ~PPIPE_C_RDRCLOSING;
          closing |= PPIPE_C_RDRCLOSED;
        }

        while (writePkt &&
               (closing & PPIPE_C_RDRCLOSED ||
                PIPEBUFFER_SIZE - bytesReady >= writePkt->dp_Arg3 - bytesW))
        {
          if (closing & PPIPE_C_RDRCLOSED)
          {
            ReplyPkt(writePkt, writePkt->dp_Arg3, 0);
            writePkt = NULL;
          }
          else
          {
            ULONG bytes = CopyToFIFO(pipeBuffer, PIPEBUFFER_SIZE, &pbStart,
                                     &pbEnd, &bytesReady,
                                     (UBYTE *) writePkt->dp_Arg2 + bytesW,
                                     writePkt->dp_Arg3 - bytesW);

            if ((bytesW += bytes) == writePkt->dp_Arg3)
            {
              ReplyPkt(writePkt, writePkt->dp_Arg3, 0);
              writePkt = NULL;
            }
          }
          if (writePkt == NULL &&
              (writePkt = (struct DosPacket *)
                            (node = RemHead((struct List *) &writeQ),
                             node ? node->ln_Name : NULL)))
          {
            bytesW = 0;
          }
        }

        if (writePkt == NULL && closing & PPIPE_C_WRTCLOSING)
        {
          closing &= ~PPIPE_C_WRTCLOSING;
          closing |= PPIPE_C_WRTCLOSED;
        }
      }
      while ((closing & PPIPE_C_RDRCLOSED) == 0 ||
             (closing & PPIPE_C_WRTCLOSED) == 0);

      /* clean up from child process */
      if (childPort)
      {
        waitSigMask = 1L << childPort->mp_SigBit;

        while ((childMsg = (struct ChildMsg *) GetMsg(childPort)) == NULL)
          Wait(waitSigMask);

        DeleteMsgPort(childPort);
      }
      if (childMsg) /* means the child process existed */
      {
        if (readCls)
          ReplyPkt(readCls, DOSTRUE, 0);
        if (writeCls)
          ReplyPkt(writeCls, DOSTRUE, 0);
        FreePathList(childMsg->PathList);
        FreeVec(childMsg); /* no use for return code currently */
      }
      FreeMem(pipeBuffer, PIPEBUFFER_SIZE);
    }

    CloseLibrary((struct Library *) l_DOSBase);

    DPrintF(("Handler exiting.\n"));
  }
} /* PgmPipe */


STATIC ULONG
CopyFromFIFO(UBYTE *FIFO, ULONG FIFOSize, UBYTE **Start, UBYTE *End,
             ULONG *FIFOFill, UBYTE *Dest, ULONG NumBytes)
{
  ULONG bytesCopied = 0, bytes;

  DPrintF(("CopyFromFIFO: 0x%08lx %ld", Dest, NumBytes));

  if (*FIFOFill > 0 && NumBytes > 0)
  {
    bytes = *Start >= End ? FIFO + FIFOSize - *Start : *FIFOFill;

    CopyMem(*Start, Dest, bytesCopied = MIN(bytes, NumBytes));
    NumBytes -= bytesCopied;
    *Start += bytesCopied;
    *FIFOFill -= bytesCopied;
  }
  if (*FIFOFill > 0 && NumBytes > 0)
  {
    bytes = End - FIFO; /* invariant: *Start == FIFO + FIFOSize */

    CopyMem(FIFO, Dest + bytesCopied, bytes = MIN(bytes, NumBytes));
    *Start = FIFO + bytes;
    *FIFOFill -= bytes;
    bytesCopied += bytes;
  }

  DPrintF((" %ld\n\n", bytesCopied));

  return bytesCopied;
} /* CopyFromFIFO */


STATIC ULONG
CopyToFIFO(UBYTE *FIFO, ULONG FIFOSize, UBYTE **Start, UBYTE **End,
           ULONG *FIFOFill, UBYTE *Src, ULONG NumBytes)
{
  ULONG bytesCopied = 0, bytes;

  DPrintF(("CopyToFIFO: 0x%08lx %ld", Src, NumBytes));

  if (*FIFOFill == 0)
    *Start = *End = FIFO; /* normalize for efficiency */

  if (*FIFOFill < FIFOSize && NumBytes > 0)
  {
    bytes = *End <= *Start ? FIFOSize - *FIFOFill : FIFO + FIFOSize - *End;

    CopyMem(Src, *End, bytesCopied = MIN(bytes, NumBytes));
    NumBytes -= bytesCopied;
    *End += bytesCopied;
    *FIFOFill += bytesCopied;
  }
  if (*FIFOFill < FIFOSize && NumBytes > 0)
  {
    bytes = *Start - FIFO; /* invariant: *End == FIFO + FIFOSize */

    CopyMem(Src + bytesCopied, FIFO, bytes = MIN(bytes, NumBytes));
    *End = FIFO + bytes;
    *FIFOFill += bytes;
    bytesCopied += bytes;
  }

  DPrintF((" %ld\n\n", bytesCopied));

  return bytesCopied;
} /* CopyFromFIFO */


#define GetCICh(String, Char) \
  ((Char) = *(String), (Char) += (Char) >= 'A' && (Char) <= 'Z' ? 0x20 : 0)

STATIC STRPTR
GetCommandLine(STRPTR FileSpec, BSTR HandlerName)
{
  UBYTE *cp1 = FileSpec, *cp2 = BADDR(HandlerName);
  LONG hNmLen = *cp2++;
  LONG ch1, ch2;

  while (hNmLen-- > 0 && GetCICh(cp1, ch1) == GetCICh(cp2, ch2))
    cp1++, cp2++;

  return hNmLen < 0 && *cp1 == ':' ? cp1 + 1 : FileSpec;
} /* GetCommandLine */


#define FAILATCMD        "FailAt 2147483647\n"
#define STRLEN_FAILATCMD 18

/*
 *  Run a command asynchronously.
 */

STATIC LONG
ExecCommand(char *CmdLine, struct MsgPort **ParentPort,
            struct Process *Requester, struct FileHandle *ChildIO,
            LONG IoDirection)
{
  char *commandLine;
  struct ChildMsg *childMsg;
  struct TagItem processTags[9];
  struct Process *child, *thisProcess = (struct Process *) FindTask(NULL);
  struct CommandLineInterface *cli = BADDR(Requester->pr_CLI);
  BPTR childOI;
  struct LocalVar *pVar;
  BPTR pathList, homeDir;
  LONG ioErr;

  /*
   *  Copy shell variables, aliases, and path from requester process.
   *  NOTE: This is not completely safe!
   */
  Forbid();
  for (pVar = (struct LocalVar *) Requester->pr_LocalVars.mlh_TailPred;
       pVar->lv_Node.ln_Pred;
       pVar = (struct LocalVar *) pVar->lv_Node.ln_Pred)
  {
    if (FindVar(pVar->lv_Node.ln_Name, pVar->lv_Node.ln_Type) == NULL)
      SetVar(pVar->lv_Node.ln_Name, pVar->lv_Value, pVar->lv_Len,
             pVar->lv_Flags | pVar->lv_Node.ln_Type & ~LVF_IGNORE);
  }

  pathList = ClonePathList(cli);

  Permit();

  if ((childOI = CloneProcessIO(Requester, IoDirection)) == NULL)
  {
    ioErr = IoErr();
    FreePathList(pathList);

    return ioErr;
  }

  if ((homeDir = DupLock(Requester->pr_CurrentDir)) == NULL)
  {
    ioErr = IoErr();
    Close(childOI);
    FreePathList(pathList);

    return ioErr;
  }

  /*
   *  Create parent's port.
   */
  if ((*ParentPort = CreateMsgPort()) == NULL)
  {
    UnLock(homeDir);
    Close(childOI);
    FreePathList(pathList);

    return ERROR_NO_FREE_STORE;
  }

  /*
   *  Build command line from argument vector.  First count the length.
   *
   *  We insert a `FailAt 2147483647' (0x7fffffff) so the shell doesn't
   *  print `Command foo failed' when we call System() to execute the
   *  command.
   */
  if ((childMsg = AllocVec(sizeof(struct ChildMsg) + strlen(CmdLine) +
                           STRLEN_FAILATCMD + 1, MEMF_PUBLIC)) == NULL)
  {
    DeleteMsgPort(*ParentPort);
    *ParentPort = NULL;
    UnLock(homeDir);
    Close(childOI);
    FreePathList(pathList);

    return ERROR_NO_FREE_STORE;
  }

  childMsg->ExecMsg.mn_Node.ln_Type = NT_MESSAGE;
  childMsg->ExecMsg.mn_Node.ln_Pri = 0;
  childMsg->ExecMsg.mn_ReplyPort = *ParentPort;
  childMsg->ExecMsg.mn_Length = sizeof(struct ChildMsg);
  childMsg->CmdLine = commandLine = (char *) (childMsg + 1);

  /* Setup the grandchild's [sic] stack */
  childMsg->StackSize = cli ? cli->cli_DefaultStack << 2 :
                              Requester->pr_StackSize;
  childMsg->PathList = pathList;
  childMsg->Flags = pathList ? CHMF_PATH : 0;
  childMsg->RC = 0;

  strcpy(commandLine, FAILATCMD);
  strcpy(commandLine + STRLEN_FAILATCMD, CmdLine);

  processTags[0].ti_Tag = NP_Entry;
  processTags[0].ti_Data = (Tag) _ChildProcess;
  processTags[1].ti_Tag = NP_Input;
  processTags[2].ti_Tag = NP_Output;
  processTags[3].ti_Tag = NP_StackSize;
  processTags[3].ti_Data = thisProcess->pr_StackSize;
  processTags[4].ti_Tag = NP_Cli;
  processTags[4].ti_Data = TRUE;
  processTags[5].ti_Tag = NP_Name;
  processTags[5].ti_Data = (Tag) "Kicker Process";
  processTags[6].ti_Tag = NP_Priority;
  processTags[6].ti_Data = Requester->pr_Task.tc_Node.ln_Pri;
  processTags[7].ti_Tag = NP_CurrentDir;
  processTags[7].ti_Data = homeDir;
  processTags[8].ti_Tag = TAG_DONE;

  if (IoDirection == PPIPE_READ)
  {
    processTags[1].ti_Data = childOI;
    processTags[2].ti_Data = MKBADDR(ChildIO);
  }
  else /* IoDirection == PPIPE_WRITE */
  {
    processTags[1].ti_Data = MKBADDR(ChildIO);
    processTags[2].ti_Data = childOI;
  }

  /*
   *  Start our `kicker' process.  This process consists of the _ChildProcess()
   *  function above.  The _ChildProcess() function then executes the command.
   */
  if ((child = CreateNewProc(processTags)) == NULL)
  {
    FreeVec(childMsg);
    DeleteMsgPort(*ParentPort);
    *ParentPort = NULL;
    UnLock(homeDir);
    Close(childOI);
    FreePathList(pathList);

    return ERROR_NO_FREE_STORE;
  }

  /* now pass the child the startup message */
  PutMsg(&child->pr_MsgPort, (struct Message *) childMsg);

  return 0;
} /* ExecCommand */


STATIC BPTR
CloneProcessIO(struct Process *Friend, LONG IoDirection)
{
  BPTR origFH;
  BPTR lock;
  BPTR fh;

  if (IoDirection == PPIPE_READ)
    origFH = Friend->pr_CIS;
  else
    origFH = Friend->pr_COS;

  if ((lock = DupLockFromFH(origFH)) == NULL ||
      (fh = OpenFromLock(lock)) == NULL)
  {
    if (lock)
      UnLock(lock);
    fh = Open("CONSOLE:", MODE_OLDFILE);
  }

  return fh;
} /* CloneProcessIO */


/*
 *  NOTE: This routine should be called from within a Forbid();
 *        but it is still unsafe due to the call to DupLock().
 */
STATIC BPTR
ClonePathList(struct CommandLineInterface *Peer)
{
  BPTR pathList = NULL;
  struct PathEntry *pathEntry, *pathET;

  if (Peer)
  {
    for (pathEntry = BADDR(Peer->cli_CommandDir),
         pathET = (struct PathEntry *) &pathList;
         pathEntry;
         pathEntry = BADDR(pathEntry->pe_NextPathEntry))
    {
      struct PathEntry *newPE;

      if (newPE = AllocMem(sizeof(struct PathEntry), MEMF_PUBLIC))
      {
        newPE->pe_NextPathEntry = NULL;
        newPE->pe_PathLock = DupLock(pathEntry->pe_PathLock);
        pathET->pe_NextPathEntry = MKBADDR(newPE);
        pathET = newPE;
      }
      else
        break;
    }
  }

  return pathList;
} /* ClonePathList */


STATIC void
FreePathList(BPTR PathList)
{
  struct PathEntry *pathEntry;

  while (pathEntry = BADDR(PathList))
  {
    PathList = pathEntry->pe_NextPathEntry;
    UnLock(pathEntry->pe_PathLock);
    FreeMem(pathEntry, sizeof(struct PathEntry));
  }
} /* FreePathList */
