/*
 * _fork(), _wait(): create a new process, and wait for it to finish.
 * the parameter to _fork() controls the way memory is shared with the
 * parent; 0 means share all data and stack, non-zero means just share
 * stack up to the address given. Note that _fork(0) is callable only
 * when malloc's are being done from the heap. If _heapbase is
 * non-zero, _fork(0) fails and errno is set to EACCESS.
 *
 * written by Eric R. Smith and placed in the public domain.
 * use at your own risk.
 */

#include <stdlib.h>
#include <stddef.h>		/* for size_t */
#include <osbind.h>
#include <basepage.h>
#include <errno.h>
#include <setjmp.h>
#include "fork.h"

struct _fork_block *_forks = 0;
extern long _stksize;

static short wait_return;

/* newly forked processes start here */
/* all we do is set up a temporary stack and longjmp() back to _fork() */

static void
in_fork(bp)
long bp;
{
	__asm__ volatile("movl %0,sp"::"r"(bp+500));
	longjmp(_forks->ctxt, 1);
}

/*
 * the guts of fork() and vfork(). _fork(0) is the same as fork();
 * _fork(x) gives a vfork(), and in this case "x" is the top address
 * we should save to. note that the parent is suspended until the child
 * exits (this is unlike Unix). also note that _fork(0) requires that
 * _heapbase be non-zero, i.e. that mallocs be done from the heap.
 * otherwise, we would have to keep track of all memory requests from
 * the system, and somehow put them all into the saved block. this is
 * doable, but would be a pain.
 */

int
_fork(save_to)
char *save_to;
{
	struct _fork_block *new;
	char *cpystart;
	long siz, *to, *from;
	long r;
	BASEPAGE *newbase;
	extern void *_heapbase;

	if (!save_to) {
/* fork() requires stack based memory allocation */
		if (!_heapbase) {
			errno = EACCESS;
			return -1;
		}
		siz = ((char *)_heapbase - _base->p_dbase) + _stksize;
		cpystart = (char *)_base->p_dbase;
	}
	else {
/*
 * for vfork, start copying just below where the stack is now, and
 * copy up to just past save_to, leaving some slush on either
 * side for return addresses, linkages, and saved regs
 */
		cpystart = ((char *)&save_to) - 128;
		siz = (save_to - cpystart) + 72;
	}

/*
 * KLUDGE!
 * make sure that there is enough free memory handled by malloc, so that
 * it doesn't have to get any more from TOS if the "child" process
 * does a malloc() before it does an execve() (otherwise malloc() could
 * later return invalid memory blocks!) ++andreas
 */
	free(malloc((size_t)16*1024)); /* hope this is enough! */

/* now malloc a _fork_block to describe this fork */
	if (!(new = (void *)Malloc(siz+sizeof(struct _fork_block)))) {
		errno = ENOMEM;
		return -1;
	}


/* adjust size, since pointers are (long *) */
	siz = siz/sizeof(long);

/* link the new fork block into the list and fill in some entries */
	new->next = _forks;
	new->start_addr = (long *)cpystart;
	new->data_size = siz;
	new->ppid = _base;
	_forks = new;

/* make a new basepage for the child process */

	newbase = (BASEPAGE *)Pexec(PE_CBASEPAGE, 0L, "", 0L);
	if ((long)newbase < 0) {
		errno =  -((int)newbase);
		_forks = _forks->next;
		Mfree(new);
		return -1;
	}
	new->pid = newbase;

/* save the necessary data */
	to = new->data;
	from = (long *)cpystart;
	r = siz;
	while (r-- > 0)
		*to++ = *from++;

/*
   make the new basepage just like ours, so that the child will be able to
   fork() too
*/
	newbase->p_dbase = _base->p_dbase;
	newbase->p_bbase = _base->p_bbase;
	newbase->p_dlen = _base->p_dlen;
	newbase->p_blen = _base->p_blen;
	newbase->p_tbase = (char *)in_fork;
	newbase->p_tlen = 0;

	if (r = setjmp(new->ctxt)) {
/* the second return is caused by the child, from in_fork() */
		Mshrink(_base, 256L);
		return 0;
	}
	else {
/* If the child stomps on the parent's stack, then Pexec won't return to
 * the right place. So we set to a temporary stack before the Pexec.
 */
		_base = newbase;
		r = (long)&(new->tmp_stack[62]);
		__asm__ volatile
		("
			movel sp, a0;
			movel %0, sp;
			movel a0, sp@- "
		:
		: "r"(r)
		: "a0", "sp"
		);
		wait_return = Pexec(PE_GO, 0L, newbase, 0L);
		__asm__ volatile
		("
			movel sp@+, a0;
			movel a0, sp "
		::: "a0", "sp"
		);

/* we're back in the parent here, so restore everything */
		new = *((struct _fork_block * volatile *)&_forks);
		_base = new->ppid;
		new->return_code = wait_return;
/* restore data */
		to = new->start_addr;
		from = new->data;
		siz = new->data_size;
		while (siz-- > 0)
			*to++ = *from++;
		Mfree(newbase->p_env);
		Mfree(newbase);
/* shrink the fork block to the minimum size (it can't be freed yet,
 * because wait() may need it)
 */
		Mshrink(new, sizeof(struct _fork_block));
		return BP_TO_PID(newbase);
	}
}

/*
 * wait() emulation; goes through the fork block structure and returns
 * the pid of a child we haven't waited for yet. if such a child exists,
 * and exit_code is non-zero, the integer pointed at by exit_code is
 * set to the child's exit status. if no child is found, -1 is returned
 * and errno is set.
 */

int _wait(exit_code)
	int *exit_code;
{
	struct _fork_block *f, *g;
	int r;

	g = 0;
	f = _forks;
	while (f) {
		if (f->ppid == _base) {	/* is this one our child?? */
			r = BP_TO_PID(f->pid);
			if (exit_code)
				*exit_code = f->return_code;
			if (g)
				g->next = f->next;
			else
				_forks = f->next;
			Mfree(f);
			return r;
		}
		g = f;
		f = f->next;
	}
	errno = ENMFILES;	/* FIX_ME:: should be ESRCH */
	return -1;
}
