/*
 * Is nothing simple?
 *
 * We can't free the stack until we've finished executing,
 * but once we've finished executing, we can't do anything
 * at all, including call free.  So instead we keep a linked list
 * of all stacks for all processes, and every few times we try
 * to allocate a new stack we scan the current stack list for
 * dead processes and reclaim those stacks.
 */

#include <u.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sched.h>
#include <signal.h>
#include <errno.h>
#include <libc.h>
#include "9proc.h"

int fforkstacksize = 16384;

typedef struct Stack Stack;
struct Stack
{
	Stack *next;
	Stack *fnext;
	int pid;
};

static Lock stacklock;
static Stack *freestacks;
static Stack *allstacks;
static int stackmallocs;
static void gc(void);

static void*
mallocstack(void)
{
	Stack *p;

	lock(&stacklock);
top:
	p = freestacks;
	if(p)
		freestacks = p->fnext;
	else{
		if(stackmallocs++%1 == 0)
			gc();
		if(freestacks)
			goto top;
		p = malloc(fforkstacksize);
		p->next = allstacks;
		allstacks = p;
	}
	if(p)
		p->pid = 1;
	unlock(&stacklock);
	return p;
}

static void
gc(void)
{
	Stack *p;

	for(p=allstacks; p; p=p->next){
		if(p->pid > 1)
		if(kill(p->pid, 0) < 0 && errno == ESRCH){
			if(0) fprint(2, "reclaim stack from %d\n", p->pid);
			p->pid = 0;
		}
		if(p->pid == 0){
			p->fnext = freestacks;
			freestacks = p;
		}
	}
}

static void
freestack(void *v)
{
	Stack *p;

	p = v;
	if(p == nil)
		return;
	lock(&stacklock);
	p->fnext = freestacks;
	p->pid = 0;
	freestacks = p;
	unlock(&stacklock);
	return;
}

static int
tramp(void *v)
{
	void (*fn)(void*), *arg;
	void **v2;
	void *p;

	_p9uproc(0);
	v2 = v;
	fn = v2[0];
	arg = v2[1];
	p = v2[2];
	free(v2);
	fn(arg);
	_exit(0);
	return 0;
}
	
static int
trampnowait(void *v)
{
	int pid;
	int cloneflag;
	void **v2;
	int *pidp;
	void *p;

	v2 = v;
	cloneflag = (int)v2[4];
	pidp = v2[3];
	p = v2[2];
	pid = clone(tramp, p+fforkstacksize-512, cloneflag, v);
	*pidp = pid;
	_exit(0);
	return 0;
}

int
ffork(int flags, void (*fn)(void*), void *arg)
{
	void **v;
	char *p;
	int cloneflag, pid, thepid, status, nowait;

	_p9uproc(0);
	p = mallocstack();
	v = malloc(sizeof(void*)*5);
	if(p==nil || v==nil){
		freestack(p);
		free(v);
		return -1;
	}
	cloneflag = 0;
	flags &= ~RFPROC;
	if(flags&RFMEM){
		cloneflag |= CLONE_VM;
		flags &= ~RFMEM;
	}
	if(!(flags&RFFDG))
		cloneflag |= CLONE_FILES;
	else
		flags &= ~RFFDG;
	nowait = flags&RFNOWAIT;
	if(!(flags&RFNOWAIT))
		cloneflag |= SIGCHLD;
	else
		flags &= ~RFNOWAIT;
	if(flags){
		fprint(2, "unknown rfork flags %x\n", flags);
		freestack(p);
		free(v);
		return -1;
	}
	v[0] = fn;
	v[1] = arg;
	v[2] = p;
	v[3] = &thepid;
	v[4] = (void*)cloneflag;
	thepid = -1;
	pid = clone(nowait ? trampnowait : tramp, p+fforkstacksize-16, cloneflag, v);
	if(pid > 0 && nowait){
		if(wait4(pid, &status, __WALL, 0) < 0)
			fprint(2, "ffork wait4: %r\n");
	}else
		thepid = pid;
	if(thepid == -1)
		freestack(p);
	else
		((Stack*)p)->pid = thepid;
	return thepid;
}

int
getfforkid(void)
{
	return getpid();
}

