/*
** PPCRun
**
** Run an ELF object under AmigaOS as a new PowerPC task.
**
** V0.9a 24-Dec-97 vb
**       Modified to use inline library calls
** V0.9  23-Dec-97 phx
**       To be compatible with elfrun and SAS/C-ppc, pass the command
**       line in GPR3, instead argc/argv in GPR3/4.
**       PPCRun is compiled with vbcc's minimal startup code to get
**       direct access to the command line.
**       Removed all debugging output.
**       Successfully tested with ppc.library V45.2. Removing a crashed
**       task with CTRL-D works perfectly now. But I'm not sure, if I
**       fixed a bug or
** V0.4  18-Oct-97 phx
**       Close(errfh) was missing. Included a help text.
** V0.3  11-Oct-97 phx
**       Define DEBUG for debugging output.
** V0.2  07-Oct-97 phx
**       fixed some infinite loops :)
** V0.1  04-Oct-97 phx
**       created
*/

#include <stdio.h>
#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <exec/libraries.h>
#include <exec/ports.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <utility/tagitem.h>
#include <ppclib/tasks.h>
#include <ppclib/message.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/ppc.h>
/*
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <powerup/clib/ppc_protos.h>
*/
#define VERSION "PPCRun 0.9a (24.12.97)"
#define PPCLIBVER 44
#define PPCDEFPRI 0

#define MSGID_EXIT 0x44584954


struct StartupData {
  APTR M68kPort;  /* the PowerPC task can send messages to this port */
  BPTR std_in;    /* standard input handle */
  BPTR std_out;   /* standard output handle */
  BPTR std_err;   /* standard error handle */
  LONG retCode;   /* here we will find the return code from the PPC task */
  ULONG flags;    /* additional flags (currently unused) */
};


extern struct ExecBase *SysBase;

struct DOSBase *DOSBase = NULL;
struct Library *PPCLibBase = NULL;

static const char *ppcrun = "PPCRun";
static const char *version = "$VER: " VERSION "\r\n";
static const char *ppclibname = "ppc.library";



static char *nextarg(char *p)
/* get pointer to next argument in command line */
{
  while (*p<=' ' && *p!=0)
    p++;
  if (*p == 0)
    return (NULL);
  return (p);
}


static char *skiparg(char *p)
/* find end of argument */
{
  char c;

  if (*p != '\"') {
    while (*p > ' ')
      p++;
  }
  else {
    p++;
    do
      c = *p++;
    while (c!='\"' && c!=0);
    if (c==0)
      p--;
  }
  return (p);
}


int main(char *cmdline)
{
  APTR elfobject,startupmsg,ppctask,ppcmsg;
  BPTR errfh=0;
  char *taskname=NULL,*args=NULL;
  struct MsgPort *m68kport; /* not documented as real MsgPort, but I hope.. */
  int rc = 10;
  struct StartupData *sd;  /* data delivered with startup message */
  char c,*elfname,*p;
  ULONG argsize,sigmask=0;
  ULONG sigppc;  /* signal mask: PPC message coming in */
  struct Process *pr;

  static struct TagItem porttags[] = {
    TAG_END
  };
  static struct TagItem tasktags[] = {
    PPCTASKTAG_NAME,0,
    PPCTASKTAG_ARG1,0,
    PPCTASKTAG_MSGPORT,TRUE,
    PPCTASKTAG_STARTUP_MSG,0,
    PPCTASKTAG_STARTUP_MSGDATA,0,
    PPCTASKTAG_STARTUP_MSGLENGTH,0,
    PPCTASKTAG_STARTUP_MSGID,0,
    PPCTASKTAG_STACKSIZE,0,
    PPCTASKTAG_PRIORITY,PPCDEFPRI,
    TAG_END
  };


  if (SysBase->LibNode.lib_Version < 36)  /* We need OS2.0! */
    exit (15);
  if (!(DOSBase = (struct DOSBase *)OpenLibrary("dos.library",36)))
    exit (15);

  if (*cmdline==0 || *cmdline=='?') {
    Printf(VERSION "\n(c)1997 by Frank Wille\n\nUsage: "
           "%s <ELF object> [<aguments> ...]\n",ppcrun);
    rc = 5;
  }

  else {
    if (PPCLibBase = OpenLibrary((STRPTR)ppclibname,PPCLIBVER)) {
      if (sd = (struct StartupData *)
          PPCAllocVec(sizeof(struct StartupData),MEMF_CLEAR|MEMF_PUBLIC)) {
        sd->std_in = Input();
        sd->std_out = Output();
        if (errfh = Open("CONSOLE:",MODE_NEWFILE))
          sd->std_err = errfh;
        else
          sd->std_err = sd->std_out;

        /* parse options and determine name of ELF object */
        while (cmdline = nextarg(cmdline)) {
          if (*cmdline != '-')
            break;
          cmdline++;
          switch (*cmdline++) {
            /* here we could check for options - currently unused... */
            default:
              Printf("%s: Unrecognized option -%lc!\n",
                     ppcrun,(ULONG)*(--cmdline));
              cmdline = skiparg(cmdline);
              break;
          }
        }
        if (cmdline) {
          elfname = cmdline;
          p = skiparg(cmdline);
          c = *p;
          if (*elfname == '\"') {  /* file name in quotes? */
            elfname++;
            if (*(p-1) == '\"') {
              c = '\"';
              p--;
            }
          }
          *p = 0;

          if (elfobject = PPCLoadObject(elfname)) {
            /* copy command line for PPC task */
            argsize = (p - elfname) + 1;
            if (taskname = (char *)AllocVec(argsize,MEMF_ANY))
              CopyMem(elfname,taskname,argsize);
            *p = c;
            p = cmdline;
            argsize = 0;
            do
              argsize++;
            while (*p++);
            if (args = (char *)PPCAllocVec(argsize,MEMF_ANY))
              CopyMem(cmdline,args,argsize);

            /* create M68k message port and startup message */
            if (m68kport = (struct MsgPort *)PPCCreatePort(porttags)) {
              sigppc = 1 << m68kport->mp_SigBit;

              if (startupmsg =
                  PPCCreateMessage(m68kport,sizeof(struct StartupData))) {
                sd->M68kPort = m68kport;

                /* start PowerPC task */
                tasktags[0].ti_Data = (ULONG)taskname;  /* task name */
                tasktags[1].ti_Data = (ULONG)args;      /* arg1 - cmdline */
                tasktags[3].ti_Data = (ULONG)startupmsg;
                tasktags[4].ti_Data = (ULONG)sd;
                tasktags[5].ti_Data = sizeof(struct StartupData);
                tasktags[6].ti_Data = MSGID_EXIT;
                /* determine stack size for PowerPC */
                pr = (struct Process *)FindTask(NULL);
                if (pr->pr_Task.tc_Node.ln_Type == NT_PROCESS && pr->pr_CLI)
                  tasktags[7].ti_Data = *(ULONG *)pr->pr_ReturnAddr;
                else
                  tasktags[7].ti_Data = (ULONG)((char *)pr->pr_Task.tc_SPUpper
                                        - (char *)pr->pr_Task.tc_SPLower);
                CacheClearU();
                if (ppctask = PPCCreateTask(elfobject,tasktags)) {

                  for (;;) {
                    ULONG mid;
                    BOOL quit;
                    struct StartupData *ppcsd;

                    sigmask = Wait(sigppc |
                                   SIGBREAKF_CTRL_C |
                                   SIGBREAKF_CTRL_D);

                    /* Message from PPC task */
                    if (sigmask & sigppc) {
                      quit = FALSE;
                      while (ppcmsg = PPCGetMessage(m68kport)) {
                        if (mid = PPCGetMessageAttr(ppcmsg,PPCMSGTAG_MSGID)
                            == MSGID_EXIT) {
                          ppcsd = (struct StartupData *)
                                  PPCGetMessageAttr(ppcmsg,PPCMSGTAG_DATA);
                          rc = ppcsd->retCode;  /* set return code */
                          quit = TRUE;
                          break;
                        }
                        else {  /* received unknown message from PPC task */
                          Printf("PPC task sent unknown message "
                                 "with id = %ld.\n",mid);
                          PPCReplyMessage(ppcmsg);
                        }
                      }
                      if (quit)  /* PPC task has finished */
                        break;
                    }

                    /* CTRL-D immediately kills the PPC task */
                    if (sigmask & SIGBREAKF_CTRL_D) {
                      PPCDeleteTask(ppctask);
                      break;
                    }

                    /* CTRL-C is passed on to the PPC task */
                    if (sigmask & SIGBREAKF_CTRL_C) {
                      SetSignal(0,SIGBREAKF_CTRL_C);
                      PPCSignalTask(ppctask,SIGBREAKF_CTRL_C);
                    }
                  }

                }
                else
                  Printf("Unable to create PPC task.\n");
                PPCDeleteMessage(startupmsg);
              }
              else
                Printf("Can't create startup message for PPC task.\n");
              PPCDeletePort(m68kport);
            }
            else
              Printf("Can't create MsgPort for PPC-messages.\n");
            PPCFreeVec(args);
            FreeVec(taskname);
            PPCUnLoadObject(elfobject);
          }
          else
            Printf("%s: Unknown command.\n",elfname);
        }
        else
          Printf("Missing argument <ELF object>!\n");
        if (errfh)
          Close(errfh);
        PPCFreeVec(sd);
      }
      else
        Printf("Not enough memory for startup data.\n");
      CloseLibrary(PPCLibBase);
    }
    else
      Printf("Can't open %s V%ld.\n",ppclibname,PPCLIBVER);
  }
  CloseLibrary((struct Library *)DOSBase);
  exit(rc);
}
