/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#define KERNEL
#include "ixemul.h"
#include <ctype.h>
#include <sys/wait.h>

#include <sys/exec.h>

#define alloca __builtin_alloca

#ifdef DEBUG
#define DP(a) kprintf a
#else
#define DP(a)
#endif

extern int _dos20;

/* 2.0 support */
#define BASE_EXT_DECL
#define BASE_PAR_DECL	
#define BASE_PAR_DECL0	
#define BASE_NAME	ix.ix_dos_base
__inline static LONG RunCommand(BASE_PAR_DECL BPTR seg, long int stack, UBYTE* paramptr, long int paramlen)
{
	BASE_EXT_DECL
	register LONG res __asm("d0");
	register void *a6 __asm ("a6");
	register BPTR d1 __asm("d1");
	register long int d2 __asm("d2");
	register UBYTE* d3 __asm("d3");
	register long int d4 __asm("d4");

	a6 = BASE_NAME;
	d1 = seg;
	d2 = stack;
	d3 = paramptr;
	d4 = paramlen;
	__asm volatile ("
	jsr a6@(-0x1f8)"
	: "=r" (res)
	: "r" (a6), "r" (d1), "r" (d2), "r" (d3), "r" (d4)
	: "d0", "d1", "a0", "a1", "d2", "d3", "d4");
	return res;
}
__inline static BPTR SelectInput(BASE_PAR_DECL BPTR fh)
{
	BASE_EXT_DECL
	register BPTR res __asm("d0");
	register void *a6 __asm ("a6");
	register BPTR d1 __asm("d1");

	a6 = BASE_NAME;
	d1 = fh;
	__asm volatile ("
	jsr a6@(-0x126)"
	: "=r" (res)
	: "r" (a6), "r" (d1)
	: "d0", "d1", "a0", "a1");
	return res;
}
__inline static BPTR SelectOutput(BASE_PAR_DECL BPTR fh)
{
	BASE_EXT_DECL
	register BPTR res __asm("d0");
	register void *a6 __asm ("a6");
	register BPTR d1 __asm("d1");

	a6 = BASE_NAME;
	d1 = fh;
	__asm volatile ("
	jsr a6@(-0x12c)"
	: "=r" (res)
	: "r" (a6), "r" (d1)
	: "d0", "d1", "a0", "a1");
	return res;
}

extern void bcopy (void *, void *, int);
extern BPTR __load_seg (const char *name, char **args);
extern void *kmalloc (size_t);
extern void *krealloc (void *, size_t);
extern char *index (const char *, int);
extern char *strpbrk (const char *, const char *);
extern int strlen (const char *);
extern void kfree (void *);
extern char *strcpy (char *, const char *);
extern void kprintf (const char *, ...);
extern void all_free (void);

static int compatible_startup (void *code, int argc, char **argv);
static char *quote (char *orig);
static void volatile on_real_stack (BPTR segs, char **argv, char **environ);

int
execve (char *path, char **argv, char **environ)
{
  BPTR segs;
  u_int omask, err;
  char *extra_args = 0;

  omask = syscall (SYS_sigsetmask, ~0);
  u.u_oldmask = omask;
  
  segs = __load_seg (path, &extra_args);

  /* I'm not interested in any files with a set script bit */
  if (segs == (BPTR)-2)
    segs = 0;

  /* if we got extra arguments, split them into a 2 el argument vector, and join
   * `argv' */
  if (segs && extra_args)
    {
      char **ap, **nargv;
      int size;
      
      for (size = 0, ap = argv; *ap; size++, ap++) ;
      nargv = (char **) alloca ((size + 3) * 4);
      ap = nargv;
      *ap++ = extra_args;
      *ap = index (extra_args, ' ');
      if (*ap)
        {
          **ap = 0;
          ++*ap;
          ++ap;
	}
      while (size--) *ap++ = *argv++;
      argv = nargv;
    }
  
  if (segs)
    {
      /* Now it gets somewhat nasty... since I have to revert to the `real'
         stack (since the parent will want its sp back ;-)), I have to save
         the values of this stack frame into registers, or I'll never be
         able to access them afterwards again.. */
      register BPTR _segs asm ("d2") = segs;
      register char **_argv asm ("d3") = argv;
      register char **_environ asm ("d4") = environ;

      DP(("execve: about to call on_real_stack ()\n"));

      if (u.u_save_sp)
        {
          set_sp ((u_int) u.u_save_sp);
          DP(("execve () restored native sp\n"));
	}
      on_real_stack (_segs, _argv, _environ);
      /* never reached */
    }

  err = ENOENT;

  /* will have to integrate interpreter-expansion here again later */

  syscall (SYS_sigsetmask, omask);

  errno = err;
  return -1;
}


static char **
dupvec (char **vec)
{
  int n;
  char **vp, **res;
  
  if (! vec)
    return vec;

  for (n = 0, vp = vec; *vp; n++, vp++) ;

  /* contrary to `real' vfork(), malloc() works in the child on its own
     data, that is it won't clobber anything in the parent  */
  
  res = (char **) syscall (SYS_malloc, (n + 1) * 4);
  if (res)
    {
      for (vp = res; *vec; )
        *vp++ = (char *) syscall (SYS_strdup, *vec++);

      *vp = 0;
    }

  return res;
}


static void volatile 
on_real_stack (BPTR segs, char **argv, char **environ)
{
  int private_startup;
  u_int *code;
  int (*entry) (struct ixemul_base *, int, char **, char **);
  struct exec *hdr;
  int f;
  jmp_buf old_exit;

  /* first make sure that we're later passing on `safe' data to our child, ie.
     copy it from wherever the data is currently stored into child malloc space */
  argv = dupvec (argv);
  environ = dupvec (environ);
    
  code = BTOCPTR (segs);
  code ++;	/* code starts at offset 4 */
  
  /* check whether this program has our magic header */
  private_startup = ((code[0] & 0xffff0000) == 0x4efa0000  /* jmp pc@(amiga-dos-entry) */
	  	     && (code [1] & 0xffff) == OMAGIC);

DP(("execve (%s, )\n", argv[0]));

  if (private_startup)
    {
      hdr = (struct exec *) &code[1];
      entry = (void *) hdr->a_entry;
      
      if (! entry) private_startup = 0;
    }
      
  /* okay, get ready to turn us into a new process, as much as
     we can actually.. */

  /* close all files with the close-on-exec flag set */
  for (f = 0; f < NOFILE; f++)
    {
      if (u.u_ofile[f] && (u.u_pofile[f] & UF_EXCLOSE))
      syscall (SYS_close, f);
    }
	      
  /* `ignored signals remain ignored across an execve, but
      signals that are caught are reset to their default values.
      Blocked signals remain blocked regardless of changes to
      the signal action. The signal stack is reset to be
      undefined'. */

  u.u_sigonstack = 0;	/* default = on normal stack */
  u.u_sigintr    = 0;	/* default = don't interrupt syscalls */
  u.p_sigcatch   = 0;	/* no signals caught by user -> SIG_DFL */

  /* what happens when we execute execve() from a signal handler
     that executes on the signal stack? Better don't do this... */
	      
  /* save the original exit-jmpbuf, as ix_exec_entry() will destroy
   * it later */
  bcopy (u.u_jmp_buf, old_exit, sizeof (old_exit));
	      
  /* count the arguments */
  for (f = 0; argv[f]; f++) ;

DP(("execve() having parent resume\n"));
  if (u.p_vfork_msg)
    {
      /* make the parent runable again */
      ReplyMsg ((struct Message *) u.p_vfork_msg);
      u.p_vfork_msg = 0;
    }

DP(("execve() calling entry(%ld, $%lx, $%lx)\n", f, argv, environ));
  {
    char *orig, **name;
    struct Process *me = (struct Process *) FindTask (0);
    struct CommandLineInterface *CLI = BTOCPTR (me->pr_CLI);
    char *bcpl_argv0;
	
    bcpl_argv0 = alloca (strlen (argv[0]) + 4);
    bcpl_argv0 = LONG_ALIGN (bcpl_argv0);
	
    if (CLI)
      {
	name = (char **) & CLI->cli_CommandName;
	orig = *name;
	bcpl_argv0[0] = strlen (argv[0]);
	bcopy (argv[0], &bcpl_argv0[1], bcpl_argv0[0] + 1);
	*name = (char *) CTOBPTR (bcpl_argv0);
      }
    else
      {
	name = (char **) & me->pr_Task.tc_Node.ln_Name;
	orig = *name;
	*name = argv[0];
      }

    if (private_startup)
      f = entry (ixemulbase, f, argv, environ);
    else
      f = compatible_startup (code, f, argv);

    *name = orig;
  }

  UnLoadSeg (segs);

DP(("old program doing _exit(%ld)\n", f));
  /* and fake an _exit */
  _longjmp (old_exit, f + 1);
}  


/* some rather rude support to start programs that don't have a struct exec
 * information at the beginning.
 * NOTE: This will only start plain C programs, nothing that smells like
 *       BCPL. Limited support for ReadArgs() style parsing is here, but not
 *	 everything is set up that would have to be set up for BCPL programs
 *	 to feel at home. Also don't use Exit() in those programs, it wouldn't
 *	 find what it expects on the stack....
 * The reason why I don't use RunCommand here, is that I don't want to spill
 * that stack space this process already has, RunCommand has no option to use
 * the current stack, it insists in allocating a new one, sigh..
 */
static int
compatible_startup (void *code, int argc, char **argv)
{
  char *al;
  int max, res;
  u_int oldsigalloc;
  struct Process *me = (struct Process *) FindTask (0);
  
  /* ignore the command name ;-) */
  argv++;

  max = 1024;
  al = (char *) kmalloc (max);
  res = -1;
  if (al)
    {
      char *cp;
      register int d0 asm ("d0");
      register char *a0 asm ("a0");
      register void *a1 asm ("a1");
      BPTR old_cis, old_cos;
      struct file *f;

      for (cp = al; *argv; )
        {
	  char *newel = quote (*argv);
          int elsize = strlen (newel ? newel : *argv) + 2;
          
          if (cp + elsize >= al + max)
            {
	      char *nal;
              max <<= 1;
              nal = (char *) krealloc (al, max);
              if (! nal) break;
              cp = nal + (cp-al);
              al = nal;
	    }

	  strcpy (cp, newel ? newel : *argv);
	  cp += elsize - 2;
	  *cp++ = ' ';
	  *cp = 0;
	  if (newel) kfree (newel);
	  ++argv;
        }
      
      *cp++ = '\n';
      *cp = 0;

DP(("compatible_startup (%s)\n", al));

      /* problem with RunCommand: the allocated signal mask is not reset
	 for the new process, thus if several RunCommands are chained, a
	 late started process might run out of signals. This behavior makes
	 no sense, since the starting process is *suspended* while the `child'
	 is running, thus it doesn't need its signals in the meantime ! */

      oldsigalloc = me->pr_Task.tc_SigAlloc & 0xffff0000;	/* hacky...*/
      me->pr_Task.tc_SigAlloc &= 0xffff;

      /* cleanup as much of ixemul.library as possible, so that the started
         process can take over */
      me->pr_Task.tc_Flags  = u.u_otask_flags;
      me->pr_Task.tc_Launch = u.u_olaunch; /* restoring this disables our signals */
      me->pr_Task.tc_Switch = u.u_oswitch;
      
      /* free the task private malloc'd data */
      all_free ();

      /* limited support (part 2 ;-)) for I/O redirection on old programs */
      if (! _dos20)
	{
	  old_cis = me->pr_CIS;
	  old_cos = me->pr_COS;
	  if ((f = u.u_ofile[0]) && f->f_type == DTYPE_FILE)
	    me->pr_CIS = CTOBPTR (f->f_fh);
	  if ((f = u.u_ofile[1]) && f->f_type == DTYPE_FILE)
	    me->pr_COS = CTOBPTR (f->f_fh);
	}
      else
	{
	  old_cis = ((f = u.u_ofile[0]) && f->f_type == DTYPE_FILE) ?
		    SelectInput (CTOBPTR (f->f_fh)) : 0;
	  old_cos = ((f = u.u_ofile[1]) && f->f_type == DTYPE_FILE) ?
		    SelectOutput (CTOBPTR (f->f_fh)) : 0;
	}

      if (! _dos20)
        /* this is a hack, since some programs expect pr_ReturnAddr to
         * be valid... */
        {
          void *old_return_addr = me->pr_ReturnAddr;
	  u_int *sp = (u_int *) get_sp ();
	  struct FileHandle *fh = BTOCPTR (Input ());
	  u_int obuf=fh->fh_Buf, oend=fh->fh_End, opos=fh->fh_Pos;

	  *--sp = 0;
	  *--sp = 0;
	  me->pr_ReturnAddr = sp;
	  asm volatile ("movel %0,sp" : : "a" (sp));
	  me->pr_Result2 = 0;
	  fh->fh_Buf = CTOBPTR (al);
	  fh->fh_Pos = 0;
	  fh->fh_End = cp-al;
	
          /* start the baby */
          d0 = cp - al;
          a0 = al;
          a1 = code;
          asm volatile ("jsr a1@" : "=d" (d0) : "0" (d0), "a" (a0), "a" (a1));
          /* save the result before it's destroyed.. */
          res = d0;
          kfree (al);

          me->pr_ReturnAddr = old_return_addr;
	  fh->fh_Buf = obuf;
	  fh->fh_Pos = opos;
	  fh->fh_End = oend;
	  sp += 2;
	  asm volatile ("movel %0,sp" : : "a" (sp));
        }
      else
	{
	  struct CommandLineInterface *CLI = BTOCPTR (me->pr_CLI);
	  u_int stack_size = CLI ? CLI->cli_DefaultStack * 4 : me->pr_StackSize;

	  /* perhaps someone really uses so small stacks......... */
	  /* if (stack_size <= 4096) stack_size = 250000; */

	  /* the above approach has too many incompatibilities, sigh.

	     Note: The use of RunCommand() here means, that we *waste* the
	           entire stack space allocated for this process! If someone
	           comes up with a clever trick (probably involving StackSwap ())
	           where the stack of this process can be freed before calling
	           RunCommand (), lots of users with memory problems would be
	           thankful! */

	  res = RunCommand (CTOBPTR (code) - 1, stack_size, al, cp - al);
	}

      if (! _dos20)
	{
	  me->pr_CIS = old_cis;
	  me->pr_COS = old_cos;
	}
      else
	{
	  if (old_cis)
	    SelectInput (old_cis);
	  if (old_cos)
	    SelectOutput (old_cos);
	}

      me->pr_Task.tc_SigAlloc |= oldsigalloc;
    }

  return res;
}

static char *
quote (char *orig)
{
  int i;
  char *new, *cp;
  
  i = strlen (orig);
  
  if (strpbrk (orig, "\"\'\\ \t\n"))
    {
      /* worst case, each character needs quoting plus starting and ending " */
      new = (char *) kmalloc (i * 2 + 3);
      if (! new) return 0;

      cp = new;
      *cp++ = '"';
      while (*orig)
        {
          if (index ("\"\'\\ \t\n", *orig))
            *cp++ = '\\';
	  *cp++ = *orig++;
	}
      *cp++ = '"';
      *cp = 0;
      
      return new;
    }
  else
    return 0;	/* means `just use the original string' */
}   
