/*
 *  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"

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

#include <sys/syscall.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <stddef.h>
#include <setjmp.h>

extern int _dos20;

void volatile vfork_longjmp (jmp_buf, int);

#include "gcc:include20/utility/tagitem.h"
#include "gcc:include20/dos/dostags.h"
#define BASE_EXT_DECL
#define BASE_PAR_DECL	
#define BASE_PAR_DECL0	
#define BASE_NAME	ix.ix_dos_base
__inline static struct Process* CreateNewProc(BASE_PAR_DECL struct TagItem* tags)
{
	BASE_EXT_DECL
	register struct Process* res __asm("d0");
	register void *a6 __asm ("a6");
	register struct TagItem* d1 __asm("d1");

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

/* ARP stuff for 1.3 */
struct	ProcessControlBlock {
  u_int 	pcb_StackSize;	/* Stacksize for new process			*/
  char		pcb_Pri;	/* Priority of new task				*/
  u_char	pcb_Control;	/* Control bits, see defines below		*/
  void *	pcb_TrapCode;	/* Optional Trap Code				*/
  BPTR		pcb_Input;
  BPTR		pcb_Output;	/* Optional stdin, stdout			*/
  void *	pcb_Console;	/* really a union */
  void *	pcb_LoadedCode;	/* If not null, will not load/unload code	*/
  struct	ZombieMsg	*pcb_LastGasp;	/* ReplyMsg() to be filled in by exit		*/
  struct	MsgPort		*pcb_WBProcess;	/* Valid only when PRB_NOCLI			*/
};

#define	PRB_SAVEIO	0L	/* Don't free/check file handles on exit	*/
#define	PRB_CLOSESPLAT	1L	/* Close Splat file, must request explicitly	*/
#define	PRB_NOCLI	2L	/* Don't create a CLI process			*/
/*	PRB_INTERACTIVE	3L	   This is now obsolete...			*/
#define	PRB_CODE	4L	/* Dangerous yet enticing			*/
#define	PRB_STDIO	5L	/* Do the stdio thing, splat = CON:Filename 	*/

#define	PRF_SAVEIO	(1L << PRB_SAVEIO)
#define	PRF_CLOSESPLAT	(1L << PRB_CLOSESPLAT)
#define	PRF_NOCLI	(1L << PRB_NOCLI)
#define	PRF_CODE	(1L << PRB_CODE)
#define	PRF_STDIO	(1L << PRB_STDIO)

#undef BASE_NAME
#define BASE_NAME	ix.ix_arp_base
__inline static LONG ASyncRun(BASE_PAR_DECL const char* name, const char* command, struct ProcessControlBlock* pcb)
{
	BASE_EXT_DECL
	register LONG res __asm("d0");
	register void *a6 __asm ("a6");
	register const char* a0 __asm("a0");
	register const char* a1 __asm("a1");
	register struct ProcessControlBlock* a2 __asm("a2");

	a6 = BASE_NAME;
	a0 = name;
	a1 = command;
	a2 = pcb;
	__asm volatile ("
	jsr a6@(-0x222)"
	: "=r" (res)
	: "r" (a6), "r" (a0), "r" (a1), "r" (a2)
	: "d0", "d1", "a0", "a1", "a2");
	*(char *)a2=*(char *)a2;
	return res;
}

__inline static struct	Process* FindCLI(BASE_PAR_DECL LONG clinum)
{
	BASE_EXT_DECL
	register struct	Process* res __asm("d0");
	register void *a6 __asm ("a6");
	register LONG d0 __asm("d0");

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


/* having it in a struct makes parameter passing easier */

struct reg_parms {
  jmp_buf jb;
};

struct vfork_msg {
  struct Message 	vm_msg;
  struct Process 	*vm_pptr;
  struct reg_parms 	*vm_regs;	/* parents context to restore */
  int		 	vm_rc;		/* 0 if the child started normally, else errno */
};

struct death_msg {
  struct MinNode	dm_node;
  struct Process	*dm_child;
  int			dm_pgrp;
  int			dm_status;
  struct rusage		dm_rusage;
};

/* this is the new process generated by vfork () ! */
static void
launcher ()
{
  void *ixb = OpenLibrary ("ixemul.library", IX_VERSION);
  struct Process *me = (struct Process *) FindTask (0);
  struct vfork_msg *vm;
  int omask;

  WaitPort (& me->pr_MsgPort);
  vm = (struct vfork_msg *) GetMsg (& me->pr_MsgPort);

  if (ixb)
    {
      /* get parents user area */
      struct user *pu = (struct user *) (vm->vm_pptr->pr_Task.tc_TrapData);
      /* `my' user area. This way we don't have to recalculate it too often */
      struct user *mu = &u;
      int fd, rc;

      /* link ourselves into the parents process lists. Guarantee single
       * threaded access to those lists by locking out any other users of
       * the library (nicer than to just call Forbid()) */
      ix_lock_base ();
      
      /* our older sybling is the last recently created child of the parent */
      mu->p_osptr = pu->p_cptr;
      /* we have no younger sybling */
      mu->p_ysptr = 0;
      /* if we have an older sybling, point its `younger sybling' field at us */
      if (mu->p_osptr)
        {
          struct user *ou = (struct user *) (mu->p_osptr->pr_Task.tc_TrapData);
	  ou->p_ysptr = me;
	}
      /* set the parents `last recently created child' field at us */
      pu->p_cptr = me;

      /* inherit the process group of our parent */
      mu->p_pgrp = pu->p_pgrp;
      mu->p_pptr = vm->vm_pptr;
      
      /* inherit these global variables. */
      mu->u_environ = pu->u_environ;
      mu->u_errno   = pu->u_errno;
      
      /* and inherit several other things as well, upto not including u_md */
      bcopy (& pu->u_signal[0], & mu->u_signal[0],
	     offsetof (struct user, u_md) - offsetof (struct user, u_signal[0]));

      /* some things have been copied that should be reset */      
      bzero (& mu->u_ru, sizeof (struct rusage));
      bzero (& mu->u_cru, sizeof (struct rusage));
      bzero (& mu->u_timer[0], sizeof (struct itimerval)); /* just the REAL timer! */
      syscall (SYS_gettimeofday, & mu->u_start, 0);
      omask = vm->vm_rc;	/* signal mask to restore at the end */

      /* and adjust the open count of each of the copied filedescriptors */
      for (fd = 0; fd < NOFILE; fd++)
        if (mu->u_ofile[fd])
          mu->u_ofile[fd]->f_count++;

      /* copying finished, allow other processes to vfork() as well ;-)) */
      ix_unlock_base ();
      
      /* remember the message we have to reply when either _exit() or 
       * execve() is called */
      mu->p_vfork_msg = vm;
      
      vm->vm_rc = 0;

      mu->u_save_sp = get_sp ();

      /* we get here when the user does an _exit() 
       * (so as well after execve() terminates !) */
      if (rc = _setjmp (mu->u_jmp_buf))
        {
	  struct death_msg *dm;
	  /* reset `mu' in here, _setjmp() might have clobbered it */
	  mu = &u;

	  syscall (SYS_sigsetmask, ~0);
	  ix_lock_base ();

DP(("vforked: _exit in progress, rc = %ld.\n", rc));

	  /* this whole thing only happens if our parent is still alive ! */
	  if (mu->p_pptr && mu->p_pptr != (struct Process *) 1)
	    {
	      rc --;

DP(("vforked: parent alive, zombie-sig = %ld, vfork_msg = $%lx.\n",
    pu->p_zombie_sig, mu->p_vfork_msg));

	      pu = (struct user *) (mu->p_pptr->pr_Task.tc_TrapData);

	      /* send the parent a death message with our return code */
	      dm = (struct death_msg *) kmalloc (sizeof (struct death_msg));
	      if (dm)
		{
		  /* don't need to fill out dm_message, it won't be ReplyMsg'd */
		  dm->dm_status = (rc >= 128) ? W_EXITCODE (0, rc & 0x7f) 
					  : W_EXITCODE (rc, 0);
		  dm->dm_rusage = mu->u_ru;
		  ruadd (&dm->dm_rusage, &mu->u_cru);
		  dm->dm_child = (struct Process *) FindTask (0);
		  dm->dm_pgrp  = mu->p_pgrp;
		  AddTail ((struct List *) &pu->p_zombies, (struct Node *) dm);
		}

	      /* once for Exec */
	      Signal ((struct Task *) mu->p_pptr, 1 << pu->p_zombie_sig);
	      /* and once for my signals ;-) */
	      _psignal (mu->p_pptr, SIGCHLD);

	      Forbid ();
	      if (mu->p_vfork_msg)
	        ReplyMsg ((struct Message *) mu->p_vfork_msg);

DP(("vforked: unlinking from parent process chains\n"));
	      /* unlink us from the parents process chains */

	      if (mu->p_ysptr)
	        {
	          struct user *yu = (struct user *) (mu->p_ysptr->pr_Task.tc_TrapData);
	          yu->p_osptr = mu->p_osptr;
	        }

	      if (mu->p_osptr)
	        {
	         struct user *ou = (struct user *) (mu->p_osptr->pr_Task.tc_TrapData);
	         ou->p_ysptr = mu->p_ysptr;
	        }

	      if (pu->p_cptr == me)
	        pu->p_cptr = mu->p_osptr;
	    }

DP(("vforked: unlocking base and closing library\n"));
	  ix_unlock_base ();

	  CloseLibrary (ixb);

DP(("vforked: falling off the edge of the world.\n"));
	  /* just fall off the edge of the world, this is a process */
	  return;
        }

      syscall (SYS_sigsetmask, omask);

DP(("vforked: jumping back\n"));

      /* jump into nevereverland ;-) */
      vfork_longjmp (vm->vm_regs->jb, 0);
      /* NOTREACHED */
    }

  vm->vm_rc = ENOMEM; /* can't imagine any other reason why the OpenLib should fail */
  ReplyMsg ((struct Message *) vm);
  /* fall off the edge of the world ;-) */
}


/*
 * this is an implementation extension to the `real' vfork(). Normally you
 * can only cause the parent to resume by calling _exit() or execve() from
 * the child. Since I can't provide a real fork() on the Amiga, this function
 * is a third possibility to make the parent resume. You have then two
 * concurrent processes sharing the same frame and global data... Please be
 * EXTREMLY careful what you may do and what not. vfork() itself is a hack,
 * this is an even greater one...
 */
static void
_vfork_resume (u_int jmp_to)
{
  if (u.p_vfork_msg)
    {
      /* make room on the new stack for the return address */
      ((u_int *)u.u_save_sp) --;
      *((u_int *)u.u_save_sp) = jmp_to;

      set_sp ((u_int) u.u_save_sp);

      ReplyMsg ((struct Message *) u.p_vfork_msg);
      u.p_vfork_msg = 0;

      /* optimizers *want* to be fooled, right ? ;-)) */
      asm volatile ("rts" : "=g" (u.p_vfork_msg) : "0" (u.p_vfork_msg));
    }
}


asm ("
	.globl _vfork
	.globl _vfork_resume
_vfork:
	| store a _setjmp () compatible frame on the stack to pass to _vfork ()
	lea	sp@(-18*4),a0		| room for _JBLEN (17) longs
	movel	#0,d0
	movel	d0,a0@			| no old onstack
	movel	d0,a0@(4)		| no old sigmask
	movel	sp,a0@(8)		| save old SP
	movel	a5,a0@(12)		| save old FP
	movel	d0,a0@(16)		| no old AP
	movel	sp@,a0@(20)		| save old PC
	movel	d0,a0@(24)		| clear PS
	moveml	d2-d7/a2-a4/a6,a0@(28)	| save other non-scratch regs 

	movel	a0,sp			| update SP and call _vfork ()
	bsr	__vfork
	lea	sp@(18*4),sp
	rts

	| the following is _longjmp(), with the subtle difference that this
	| thing doesn't insist in returning something non-zero... 
_vfork_longjmp:
	movl	sp@(4),a0	/* save area pointer */
	addql	#8,a0		/* skip onstack/sigmask */
	tstl	a0@		/* ensure non-zero SP */
	jeq	Lbotch		/* oops! */
	movl	sp@(8),d0	/* grab return value */
	movl	a0@+,sp		/* restore SP */
	movl	a0@+,a5		/* restore FP */
	addql	#4,a0		/* skip AP */
	movl	a0@+,sp@	/* restore PC */
	moveml	a0@(4),d2-d7/a2-a4/a6	/* restore non-scratch regs */
	rts

Lbotch:
	jsr	_longjmperror
	stop	#0

_vfork_resume:
	movel	sp@,sp@-	| pass the return address on the stack
	bsr	__vfork_resume
	lea	sp@(4),sp
	rts
");

static int
_vfork (struct reg_parms rp)
{
  struct Process *me = (struct Process *) FindTask(0);
  struct CommandLineInterface *CLI = BTOCPTR (me->pr_CLI);
  u_int stack_size = CLI ? CLI->cli_DefaultStack * 4 : me->pr_StackSize;
  BPTR input, output;
  /* those *have* to be in registers to survive the stack deallocation */
  register struct vfork_msg *vm asm ("a2");
  register int omask asm ("d2");
  register struct Process *child asm ("a3");

  vm = (struct vfork_msg *) kmalloc (sizeof (struct vfork_msg));
  if (! vm)
    {
      errno = ENOMEM;
      return -1;
    }

  vm->vm_msg.mn_ReplyPort = u.u_sync_mp;
  vm->vm_msg.mn_Node.ln_Type = NT_MESSAGE;
  vm->vm_msg.mn_Length = sizeof (struct vfork_msg);
  vm->vm_pptr = me;
  vm->vm_regs = & rp;

  /* we have to block all signals as long as the child uses our resources.
   * but since the child needs to start with the signal mask BEFORE this
   * general blocking, we have to pass it the old signal mask. This is a
   * way to do it */
  vm->vm_rc   = 
  omask      = syscall (SYS_sigsetmask, ~0);

  /* save the passed frame in our user structure, since the child will
     deallocate it from the stack when it `returns' to user code */
  bcopy (&rp, &u.u_vfork_frame, sizeof (rp));

  if (u.u_ofile[0] && u.u_ofile[0]->f_type == DTYPE_FILE)
    input = CTOBPTR (u.u_ofile[0]->f_fh);
  else  
    input = Input();
  
  if (u.u_ofile[1] && u.u_ofile[1]->f_type == DTYPE_FILE)
    output = CTOBPTR (u.u_ofile[1]->f_fh);
  else  
    output = Output();

  if (_dos20)
    {
      struct TagItem tags [] = {
        { NP_Entry, (ULONG) launcher, },
        { NP_Input, (ULONG) input, },
        { NP_Output, (ULONG) output, },
        { NP_CloseInput, 0, },			/* don't close */
        { NP_CloseOutput, 0, },			/* don't close */
        { NP_Cli, (ULONG) (CLI ? -1 : 0), },	/* same thing we are */
        { NP_Name, (ULONG) "vfork()'d process", },	/* to be overridden by execve() */
        { NP_StackSize, stack_size, },		/* same size we use */
        { TAG_END, 0, }
      };
      
      child = CreateNewProc (tags);
    }
  else
    {
      struct ProcessControlBlock pcb = {
	stack_size,				/* pcb_StackSize XXXX FIX ME !!! */
	0,					/* pcb_Pri */
	CLI ? PRF_SAVEIO|PRF_CODE : PRF_SAVEIO|PRF_NOCLI|PRF_CODE,
	0,					/* pcb_TrapCode */
	input, output,				/* pcb_Input, pcb_Output */
	0,					/* pcb_Console */
	launcher,				/* pcb_LoadedCode */
	0,					/* pcb_LastGasp */
	0,					/* pcb_WBProcess */
      };
      int cli_num;	
      
      cli_num = ASyncRun ("vfork()'d process", 0, &pcb);
      if (cli_num > 0)
        child = FindCLI (cli_num);
      else if (cli_num == 0)
        child = (struct Process *)((int)pcb.pcb_WBProcess - 
				   offsetof(struct Process, pr_MsgPort));
      else
	child = 0;
    }

  if (! child)
    {
      errno = EPROCLIM;
      kfree (vm);
      syscall (SYS_sigsetmask, omask);
      return -1;
    }

  /* As soon as this message is dispatched, the child will `return' and 
     deallocate the stack we're running on. So afterwards, *only* use
     register variables and then _longjmp () back.
     Since we don't have a stack until after the _longjmp(), temporarily
     switch to our mini-stack */
  set_sp ((u_int) &u.u_mini_stack[sizeof (u.u_mini_stack) / sizeof (long)]);

  PutMsg (& child->pr_MsgPort, (struct Message *) vm);
  /* wait until the child does execve() or _exit() */
  WaitPort (u.u_sync_mp);
  GetMsg (u.u_sync_mp);
  syscall (SYS_sigsetmask, omask);

  if (vm->vm_rc)
    {
      errno = (int) vm->vm_rc;
      child = (struct Process *) -1;
    }
      
  /* this is the parent return, so we pass the id of the new child */
  kfree (vm);
  /* could use _longjmp() here, but since we already *have* the local one.. */
  vfork_longjmp (u.u_vfork_frame, (int) child);
}

  
ruadd(ru, ru2)
	register struct rusage *ru, *ru2;
{
	register long *ip, *ip2;
	register int i;

	timevaladd(&ru->ru_utime, &ru2->ru_utime);
	timevaladd(&ru->ru_stime, &ru2->ru_stime);
	if (ru->ru_maxrss < ru2->ru_maxrss)
		ru->ru_maxrss = ru2->ru_maxrss;
	ip = &ru->ru_first; ip2 = &ru2->ru_first;
	for (i = &ru->ru_last - &ru->ru_first; i > 0; i--)
		*ip++ += *ip2++;
}

int
wait4 (int pid, int *status, int options, struct rusage *rusage)
{
  struct Process * const me = (struct Process *) FindTask (0);
  struct user * const mu = (struct user *) me->pr_Task.tc_TrapData;

  for (;;)
    {
      int err = 0;
      struct death_msg *dm, *ndm;

      ix_lock_base ();
      
      for (dm  = (struct death_msg *) mu->p_zombies.mlh_Head;
  	   ndm = (struct death_msg *) dm->dm_node.mln_Succ;
  	   dm  = ndm)
  	if (pid == -1 ||
  	    (pid == 0 && dm->dm_pgrp == mu->p_pgrp) ||
	    (pid < -1 && dm->dm_pgrp == - pid) ||
	    (pid == (int) dm->dm_child))
	  {
	    Remove ((struct Node *) dm);
	    break;
	  }

      if (!ndm && !mu->p_cptr)
	err = ECHILD;
      else if (!ndm && !(options & WNOHANG))
	/* it's important to do this while ixbase is locked ! */
        SetSignal (0, 1 << mu->p_zombie_sig);

      ix_unlock_base ();
      
      if (ndm)
        {
	  struct Process *child;

          if (status)
            *status = dm->dm_status;
          if (rusage)
            *rusage = dm->dm_rusage;

	  child = dm->dm_child;

	  kfree (dm);

          return (int) child;
	}

      if (err)
	{
	  errno = err;
	  return -1;
	}

      if (options & WNOHANG)
	return 0;

      Wait ((1 << mu->p_zombie_sig) | (1 << mu->u_sleep_sig) | SIGBREAKF_CTRL_C);
    }
}
