/*
 *  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.
 *
 *  22-jan-92	-mw-	restore sigmask before calling system()
 */

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

/* 2.0 support */
#include <utility/tagitem.h>
#include <dos/dostags.h>
#include <hardware/intbits.h>

#if __GNUC__ != 2
#define alloca __builtin_alloca
#endif

extern BPTR *__load_seg (char *name, char **args);

int
ssystem(char *argline)
{
  int rc, err = 0;
  UBYTE *arg, *index();
  UBYTE *tmp;
  int stack_size;
  struct CommandLineInterface *CLI;
  struct Process *me;
  int omask;
  void *old_trapdata, *old_trapcode;
  int old_flags;
  void *old_launch, *old_switch;

  omask = syscall (SYS_sigsetmask, ~0);
  me = (struct Process *)FindTask(0);
  CLI = BTOCPTR (me->pr_CLI);
  stack_size = CLI ? CLI->cli_DefaultStack * 4 : me->pr_StackSize;
  if (stack_size <= 4096) stack_size = 250000;
 
  /* the +1 is to get a cheap way to transform this into a BSTR */
  tmp = alloca (strlen (argline) + 6);
  tmp = LONG_ALIGN (tmp);
  tmp++;

  strcpy (tmp, argline);

  while (*tmp == ' ' || *tmp == '\t') ++tmp;
  /* not needed with custom load-seg function */

  if (arg = index(tmp, ' '))  *arg++ = 0;

    {
      BPTR *segs;
      char *args;
      BPTR old_cis, old_cos, old_ces;
      BPTR dup_cis, dup_cos, dup_ces;
      struct file *f;

      segs = __load_seg (tmp, &args);

      /* check for special cookie */
      if (segs == (BPTR *) -2)
	{
	  syscall (SYS_sigsetmask, omask);

	  /* let the shell do the dirty work ;-)) */
	  return system (argline);
	}

      if (segs)
	{
	  char **name;
	  char *orig;
	  char *all_args;
	  u_int old_signals;
	  
	  /* if __load_seg() set args to something, we have to rebuild our
	   * command line, but only just in that case ;-))
	   */
	  if (args)
	    {
	      int force_quotes = 0;

	      /* now this IS a horrible kludge.. but again, I *NEED* sksh
	       * working, and it only works, if the argument to the -c 
	       * switch is passed quoted... So if the __load_seg code
	       * decided, that this was such a special sksh-script, it 
	       * negates the *arg parameter... shudder... 
	       *
	       * NOTE: This only works for command lines that contain no
	       *       quotes themselves... I don't escape the argument
	       *	   line, I just put a pair of quotes around it!
	       *	   The starting quote is already included in the args
	       *	   string from __load_seg()...
	       */
	      if (((int)args) < 0)
	        {
	          force_quotes = 1;
	          args = (char *) ((-(int)args));
	        }

	      /* make handling easier */
	      if (! arg) arg = "";
	      
	      /* the command we build looks like:
	       * <seg'd command> args arg
	       */
	      
	      all_args = alloca (strlen (args) + 1 + strlen (arg) + 4);
	      strcpy (all_args, args);
	      if (*arg)
	        {
	          strcat (all_args, " ");
	          strcat (all_args, arg);
	        }

	      if (force_quotes)
		strcat (all_args, "\"");  /* no comment... */
	      strcat (all_args, "\n");	  /* neither, this insn't my kludge though.. */

	      /* and finally reassign the commandline to arg */
	      arg = all_args;
	      /* if args was not "", we have to free it, it's from strdup() */
	      if (*args) syscall (SYS_free, args);
	    }
	  else
	    {	
	      /* even if we didn't get any arguments from the expander, we still
	       * need to protect the original arguments ala BCPL ..
	       * Remember that `arg' is a (large enough) alloca() string ;-) */
	      if (arg)
		strcat (arg, "\n");
	      else
		arg = "\n";
	    }

	  /*
	   * Hack to always get the name of the currently executing program
	   * to show up in Xoper
	   */
	  if (CLI)
	    {
	      name = (char **) & CLI->cli_CommandName;
	      orig = *name;
	      /* that's why we incremented tmp before ;-)) */
	      ((unsigned char *)tmp)[-1] = strlen (tmp);
	      /* this is always odd (stack=even + 1), so will chop fine to BPTR */
	      *name = (char *) ((long)tmp >> 2);
	    }
	  else
	    {
	      name = (char **) & me->pr_Task.tc_Node.ln_Name;
	      orig = *name;
	      *name = tmp;
	    }

	  KPRINTF (("RunCommand (stack_size=%ld, arg = >%s<, len = %ld)\n", stack_size, arg, strlen (arg)));
	  /* perform I/O redirection... (copied from execve.c) */

	  if ((f = u.u_ofile[0]) && f->f_type == DTYPE_FILE)
	    {
	      dup_cis = 0;
	      old_cis = SelectInput (CTOBPTR (f->f_fh));
	      readargs_kludge (CTOBPTR (f->f_fh));
	      KPRINTF (("redir 0, old_cis = $%lx\n",old_cis));
	    }
	  else
	    {
	      if (!f)
	        {
	          int fd = open ("/dev/null", 0);
	          dup_cis = dup2_BPTR (fd);
	          close (fd);
	        }
	      else
		dup_cis = dup2_BPTR (0);
	      old_cis = 0;
	      if (dup_cis)
	        {
	          old_cis = SelectInput (dup_cis);
	          readargs_kludge (dup_cis);
		  KPRINTF (("redir 0, old_cis = $%lx, dup_cis = $%lx\n", old_cis, dup_cis));
	        }
	      else
		KPRINTF (("redir 0, dup2_BPTR failed\n"));
	    }

	  if ((f = u.u_ofile[1]) && f->f_type == DTYPE_FILE)
	    {
	      dup_cos = 0;
	      old_cos = SelectOutput (CTOBPTR (f->f_fh));
	      KPRINTF (("redir 1, old_cos = $%lx\n", old_cos));
	    }
	  else
	    {
	      if (!f)
	        {
	          int fd = open ("/dev/null", 1);
	          dup_cos = dup2_BPTR (fd);
	          close (fd);
	        }
	      else
	        dup_cos = dup2_BPTR (1);
	      old_cos = 0;
	      if (dup_cos)
	        {
	          old_cos = SelectOutput (dup_cos);
	          KPRINTF (("redir 1, old_cos = $%lx, dup_cos = $%lx\n", old_cos, dup_cos));
	        }
	      else
	        KPRINTF (("redir 1, dup2_BPTR failed\n"));
	    }
	  old_ces = me->pr_CES;
	  if ((f = u.u_ofile[2]) && f->f_type == DTYPE_FILE)
	    {
	      dup_ces = 0;
	      me->pr_CES = CTOBPTR (f->f_fh);
	    }
          else
	    {
	      if (!f)
	        {
	          int fd = open ("/dev/null", 2);
	          dup_ces = dup2_BPTR (fd);
	          close (fd);
	        }
	      else
	        dup_ces = dup2_BPTR (2);
	      me->pr_CES = dup_ces ? : old_ces;
	    }

	  /* cleanup as much of ixemul.library as possible, so that the started
	     process can take over */
	  old_flags             = me->pr_Task.tc_Flags;
	  me->pr_Task.tc_Flags  = u_save.u_otask_flags;
	  old_launch            = me->pr_Task.tc_Launch;
	  me->pr_Task.tc_Launch = u_save.u_olaunch; /* restoring this disables our signals */
	  old_switch            = me->pr_Task.tc_Switch;
	  me->pr_Task.tc_Switch = u_save.u_oswitch;
	  RemIntServer (INTB_VERTB, & u_save.u_itimerint);

	  /* BEWARE that after this reset no library functions can be
	     called any longer until the moment where trapdata is 
	     reinstalled !! */
	  old_trapdata = me->pr_Task.tc_TrapData;
	  me->pr_Task.tc_TrapData = u_save.u_otrap_data;
	  old_trapcode = me->pr_Task.tc_TrapCode;
	  me->pr_Task.tc_TrapCode = u_save.u_otrap_code;

	  /* RunCommand should provide the started program with a fresh set of
	   * signals, it doesn't. So we do this by hand here... */
	  old_signals = me->pr_Task.tc_SigAlloc;
	  me->pr_Task.tc_SigAlloc &= 0xffff;

	  rc = RunCommand (*segs, stack_size, arg, strlen (arg));

	  me->pr_Task.tc_TrapData = old_trapdata;
	  me->pr_Task.tc_TrapCode = old_trapcode;

	  KPRINTF (("RunCommand back, stack_size = %ld, rc = %ld, sp = $%lx\n", stack_size, rc, get_sp()));

	  me->pr_Task.tc_SigAlloc = old_signals;
          err = __ioerr_to_errno (IoErr ());
	  *name = orig;

	  AddIntServer (INTB_VERTB, & u_save.u_itimerint);
	  me->pr_Task.tc_Launch = old_launch;
	  me->pr_Task.tc_Switch = old_switch;
	  me->pr_Task.tc_Flags  = old_flags;

	  if (old_cis)
	    SelectInput (old_cis);
	  if (old_cos)
	    SelectOutput (old_cos);
	  me->pr_CES = old_ces;

	  /* reset I/O */
	  if (dup_cis)
	    Close (dup_cis);
          if (dup_cos)
            Close (dup_cos);
          if (dup_ces)
            Close (dup_ces);

	  __free_seg (segs);
	}
      else
	{
	  rc = 20;

          err = __ioerr_to_errno (IoErr ());
        }
    }

  syscall (SYS_sigsetmask, omask);

  if (rc > 128)
    errno = EINTR;
  else
    errno = err;
  KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));

  return (rc >= 128) ? W_EXITCODE (0, rc & 0x7f) : W_EXITCODE (rc, 0);
}
