#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/thread.h>
#include "assert.h"

static void		initproc(void (*)(ulong, int, char*[]), int, char*[], uint);
static void		garbagethread(Thread *t);
static Thread*	getqbytag(Tqueue *, ulong);
static void		putq(Tqueue *, Thread *);
static Thread*	getq(Tqueue *);
static void		launcher(ulong, void (*)(void *), void *);
static void		mainlauncher(ulong, int argc, char *argv[]);
static void		garbageproc(Proc *);
static Proc*	prepproc(Newproc *);
static long	_times(long *t);

static int		notefd;
static int		passerpid;
static long	totalmalloc;

static Thread*	dontkill;

static Tqueue	rendez;
static Lock	rendezlock;
int			mainstacksize;
Channel*		thewaitchan;

struct Pqueue	pq;
Proc			**procp;	/* Pointer to pointer to proc's Proc structure */

#define STACKOFF 2
#ifdef M386
	#define FIX1
	#define FIX2 *--tos = 0
#endif
#ifdef Mmips
	#define FIX1
	#define FIX2
#endif
#ifdef Mpower
	/* This could be wrong ... */
	#define FIX1
	#define FIX2 *--tos = 0
#endif
#ifdef Malpha
	#define FIX1 *--tos = 0
	#define FIX2 *--tos = 0
#endif
#ifdef Marm
	#define FIX1
	#define FIX2
#endif

void
threadsysfatal(char *fmt, va_list arg)
{
	char buf[1024];

	doprint(buf, buf+sizeof(buf), fmt, arg);
	if(argv0)
		threadprint(2, "%s: %s\n", argv0, buf);
	else
		threadprint(2, "%s\n", buf);
	threadexitsall(buf);
}

static int
nextID(void)
{
	static Lock l;
	static id;
	int i;

	lock(&l);
	i = ++id;
	unlock(&l);
	return i;
}
	
static int
notehandler(void *, char *s) {
	Proc *p;
	Thread *t;
	int id;

	if (DBGNOTE & _threaddebuglevel)
		threadprint(2, "Got note %s\n", s);
	if (getpid() == passerpid) {
		if (DBGNOTE & _threaddebuglevel)
			threadprint(2, "Notepasser %d got note %s\n", passerpid, s);
		if (strcmp(s, "kilpasser") == 0)
			exits(nil);
		write(notefd, s, strlen(s));
		if (strncmp(s, "kilthr", 6) == 0 || strncmp(s, "kilgrp", 6) == 0)
			return 1;
	} else {
		if (strncmp(s, "kilthr", 6) == 0) {
			p = *procp;
			id = strtoul(s+6, nil, 10);
			if (DBGNOTE & _threaddebuglevel)
				threadprint(2, "Thread id %d\n", id);
			for (t = p->threads.head; t; t = t->nextt) {
				if (t->id == id) {
					break;
				}
			}
			if (t == nil) {
				if (DBGNOTE & _threaddebuglevel)
					threadprint(2, "Thread id %d not found\n", id);
				return 1;
			}
			if (t == p->curthread) {
				if (DBGNOTE & _threaddebuglevel)
					threadprint(2, "Killing current thread\n");
				errstr("exiting", 8);
			}
			threadassert(t->state != Rendezvous);
			t->exiting = 1;
			return 1;
		}
		if (strncmp(s, "kilgrp", 6) == 0) {
			p = *procp;
			id = strtoul(s+6, nil, 10);
			if (DBGNOTE & _threaddebuglevel)
				threadprint(2, "Thread grp %d", id);
			for (t = p->threads.head; t; t = t->nextt) {
				if (t->grp == id) {
					if (t != p->curthread)
						t->exiting = 1;
				}
			}
			return 1;
		}
	}
	if (strncmp(s, "kilall", 6) == 0)
		exits(s+6);
	return 0;
}

void
threadexits(char *exitstr) {
	Thread *t, *new;
	Proc *p;
	Channel *c;

	_threaddebug(DBGTHRD|DBGKILL, "Exitthread()");
	p = *procp;
	t = p->curthread;
	if (p->nthreads > 1) {
		t->exiting = 1;
		p->nthreads--;
		lock(&rendezlock);
		while ((new = getq(&p->runnable)) == nil) {
			_threaddebug(DBGTHRD, "Nothing left to run");
			/* called with rendezlock held */
			p->blocked = 1;
			unlock(&rendezlock);
			if ((new = (Thread *)rendezvous((ulong)p, 0)) != (Thread *)~0) {
				threadassert(!p->blocked);
				threadassert(new->proc == p);
				p->curthread = new;
				new->state = Running;
				/* switch to new thread, pass it `t' for cleanup */
				new->garbage = t;
				longjmp(new->env, (int)t);
				/* No return */
			}
			_threaddebug(DBGNOTE|DBGTHRD, "interrupted");
			lock(&rendezlock);
		}
		unlock(&rendezlock);
		_threaddebug(DBGTHRD, "Yield atexit to %d.%d", p->pid, new->id);
		p->curthread = new;
		new->state = Running;
		if (new->exiting)
			threadexits(nil);
		/* switch to new thread, pass it `t' for cleanup */
		new->garbage = t;
		longjmp(new->env, (int)t);
		/* no return */
	}
	/*
	 * thewaitchan confounds exiting the entire program, so handle it
	 * carefully; store exposed global in local variable c.
	 */
	if ((c = thewaitchan) != nil) {
		Waitmsg *w;
		long t[4];

		/* create a Waitmsg out of whole cloth; this proc was created RFNOWAIT */

		w = _threadmalloc(sizeof(Waitmsg)+(exitstr==nil? 0 : strlen(exitstr)+1), 0);
		w->pid = p->pid;
		w->time[2] = _times(t);	/* real */
		w->time[0] = t[0]+t[2];	/* user + child user */
		w->time[1] = t[1]+t[3];	/* sys + child sys */
		w->msg = "";
			
		if(exitstr != nil){
			w->msg = (char*)(&w[1]);
			strcpy(w->msg, exitstr);
		}
		_threaddebug(DBGCHLD, "In thread %s: sending exit status %s for %d\n",
			p->curthread->cmdname, exitstr, p->pid);
		sendp(c, w);
	}
	_threaddebug(DBGPROC, "Exiting\n");
	t->exiting = 1;
	if(exitstr == nil)
		p->str[0] = '\0';
	else
		strncpy(p->str, exitstr, sizeof(p->str));
	p->nthreads--;
	/* Clean up and exit */
	longjmp(p->oldenv, DOEXIT);
}

static Thread *
threadof(int id)
{
	Proc *pp;
	Thread *t;

	lock(&pq.lock);
	for (pp = pq.head; pp->next; pp = pp->next)
		for (t = pp->threads.head; t; t = t->nextt)
			if (t->id == id) {
				unlock(&pq.lock);
				return t;
			}
	unlock(&pq.lock);
	return nil;
}

int
threadid(void)
{
	return (*procp)->curthread->id;
}

static char*
skip(char *p)
{

	while(*p == ' ')
		p++;
	while(*p != ' ' && *p != 0)
		p++;
	return p;
}

static long
_times(long *t)
{
	char b[200], *p;
	int f;
	ulong r;

	memset(b, 0, sizeof(b));
	f = open("/dev/cputime", OREAD|OCEXEC);
	if(f < 0)
		return 0;
	if(read(f, b, sizeof(b)) <= 0){
		close(f);
		return 0;
	}
	p = b;
	if(t)
		t[0] = atol(p);
	p = skip(p);
	if(t)
		t[1] = atol(p);
	p = skip(p);
	r = atol(p);
	if(t){
		p = skip(p);
		t[2] = atol(p);
		p = skip(p);
		t[3] = atol(p);
	}
	return r;
}

int
threadpid(int id)
{
	Proc *pp;
	Thread *t;

	if (id < 0)
		return id;
	if (id == 0)
		return (*procp)->pid;
	lock(&pq.lock);
	for (pp = pq.head; pp->next; pp = pp->next)
		for (t = pp->threads.head; t; t = t->nextt)
			if (t->id == id) {
				unlock(&pq.lock);
				return pp->pid;
			}
	unlock(&pq.lock);
	return -1;
}

void
threadkillgrp(int grp) {
	int fd;
	char buf[128];

	sprint(buf, "#p/%d/notepg", getpid());
	fd = open(buf, OWRITE|OCEXEC);
	sprint(buf, "kilgrp%d", grp);
	write(fd, buf, strlen(buf));
	close(fd);
}

void
threadkill(int id) {
	char buf[16];
	Thread *t;
	int pid;
	Proc *p;

	if ((t = threadof(id)) == nil) {
		if (_threaddebuglevel & DBGNOTE)
			threadprint(2, "Can't find thread to kill\n");
		return;
	}
	sprint(buf, "kilthr%d", id);
	if ((pid = t->proc->pid) == 0) {
		if (_threaddebuglevel & DBGNOTE)
			threadprint(2, "Thread has zero pid\n");
		if (postnote(PNGROUP, getpid(), buf) < 0)
			threadprint(2, "postnote failed: %r\n");
		return;
	}
	p = *procp;
	if (pid == p->pid) {
		if (_threaddebuglevel & DBGNOTE)
			threadprint(2, "Thread in same proc\n");
		assert(pid == getpid());
		if (t == p->curthread) {
			threadprint(2, "Threadkill:  killing self\n");
			threadexits("threadicide");
		}
		lock(&rendezlock);
		if (t->state == Rendezvous) {
			if (_threaddebuglevel & DBGNOTE)
				threadprint(2, "Thread was in rendezvous\n");
			assert(getqbytag(&rendez, t->tag) == t);
		}
		t->exiting = 1;
		unlock(&rendezlock);
		garbagethread(t);
		return;
	}
	if (postnote(PNPROC, pid, buf) < 0)
		threadprint(2, "postnote failed: %r\n");
}

void
threadexitsall(char *status) {
	int fd;
	Channel *c;
	char buf[128];
	Waitmsg *w;

	if ((c = thewaitchan) != nil) {
		thewaitchan = nil;
		while ((w = nbrecvp(c)) != nil){
			/* Unblock threads previously exited,
			* sending on thewaitchan, not yet waited for
			*/
			free(w);
		}
	}
	sprint(buf, "#p/%d/note", passerpid);
	if ((fd = open(buf, OWRITE)) < 0) {
		/* passer proc died, send note to notepg directly */
		sprint(buf, "#p/%d/notepg", getpid());
		if ((fd = open(buf, OWRITE)) < 0) {
			threadprint(2, "threadexitsall: can't open %s: %r\n", buf);
			exits("panic");
		}
		snprint(buf, sizeof buf, "kilall%s", status?status:"");
		if (DBGNOTE & _threaddebuglevel)
			fprint(2, "Sending note %s to process group of %d\n", buf, getpid());
	} else {
		snprint(buf, sizeof buf, "kilall%s", status?status:"");
		if (DBGNOTE & _threaddebuglevel)
			fprint(2, "Sending note %s to passer proc %d\n", buf, passerpid);
	}
	write(fd, buf, strlen(buf));
	exits(status);
}

void
yield(void) {
	Thread *new, *t;
	Proc *p;
	ulong thr;

	p = *procp;
	t = p->curthread;

	if (t->exiting) {
		_threaddebug(DBGTHRD|DBGKILL, "Exiting in yield()");
		threadexits(nil);	/* no return */
	}
	if ((new = getq(&p->runnable)) == nil) {
		_threaddebug(DBGTHRD, "Nothing to yield for");
		return;	/* Nothing to yield for */
	}
	if ((thr = setjmp(p->curthread->env))) {
		if (thr != ~0)
			garbagethread((Thread *)thr);
		if ((*procp)->curthread->exiting)
			threadexits(nil);
		return;	/* Return from yielding */
	}
	putq(&p->runnable, p->curthread);
	p->curthread->state = Runnable;
	_threaddebug(DBGTHRD, "Yielding to %d.%d", p->pid, new->id);
	p->curthread = new;
	new->state = Running;
	longjmp(new->env, ~0);
	/* no return */
}

ulong
_threadrendezvous(ulong tag, ulong value) {
	Proc *p;
	Thread *this, *that, *new;
	ulong v, t;

	p = *procp;
	this = p->curthread;

	lock(&rendezlock);
	_threaddebug(DBGREND, "rendezvous tag %lux", tag);
	/* find a thread waiting in a rendezvous on tag */
	that = getqbytag(&rendez, tag);
	/* if a thread in same or another proc waiting, rendezvous */
	if (that) {
		_threaddebug(DBGREND, "waiting proc is %lud.%d", that->proc->pid, that->id);
		threadassert(that->state == Rendezvous);
		/* exchange values */
		v = that->value;
		that->value = value;
		/* remove from rendez-vous queue */
		that->state = Runnable;
		if (that->proc->blocked) {
			threadassert(that->proc != p);
			that->proc->blocked = 0;
			_threaddebug(DBGREND, "unblocking rendezvous, tag = %lux", (ulong)that->proc);
			unlock(&rendezlock);
			/* `Non-blocking' rendez-vous */
			while (rendezvous((ulong)that->proc, (ulong)that) == ~0) {
				_threaddebug(DBGNOTE|DBGTHRD, "interrupted");
				if (this->exiting) {
					_threaddebug(DBGNOTE|DBGTHRD, "and committing suicide");
					threadexits(nil);
				}
			}
		} else {
			putq(&that->proc->runnable, that);
			unlock(&rendezlock);
		}
		yield();
		return v;
	}
	_threaddebug(DBGREND, "blocking");
	/* Mark this thread waiting */
	this->value = value;
	this->state = Rendezvous;
	this->tag = tag;
	putq(&rendez, this);

	/* Look for runnable threads */
	new = getq(&p->runnable);
	if (new == nil) {
		/* No other thread runnable, rendezvous */
		p->blocked = 1;
		_threaddebug(DBGREND, "blocking rendezvous, tag = %lux", (ulong)p);
		unlock(&rendezlock);
		while ((new = (Thread *)rendezvous((ulong)p, 0)) == (Thread *)~0) {
			_threaddebug(DBGNOTE|DBGTHRD, "interrupted");
			if (this->exiting) {
				_threaddebug(DBGNOTE|DBGTHRD, "and committing suicide");
				threadexits(nil);
			}
		}
		threadassert(!p->blocked);
		threadassert(new->proc == p);
		if (new == this) {
			this->state = Running;
			if (this->exiting)
				threadexits(nil);
			return this->value;
		}
		_threaddebug(DBGREND, "after rendezvous, new thread is %lux, id %d", new, new->id);
	} else {
		unlock(&rendezlock);
	}
	if ((t = setjmp(p->curthread->env))) {
		if (t != ~0)
			garbagethread((Thread *)t);
		_threaddebug(DBGREND, "unblocking");
		threadassert(p->curthread->next == (Thread *)~0);
		this->state = Running;
		if (p->curthread->exiting) {
			_threaddebug(DBGKILL, "Exiting after rendezvous");
			threadexits(nil);
		}
		return this->value;
	}
	_threaddebug(DBGREND|DBGTHRD, "Scheduling %lud.%d", new->proc->pid, new->id);
	p->curthread = new;
	new->state = Running;
	longjmp(new->env, ~0);
	/* no return */
	return ~0;	/* Not called */
}

extern void (*_sysfatal)(char *fmt, va_list arg);

/*
 * The main process runs threadmain() but sits in a different note group
 * so the library can use notes internally without disturbing other processes.
 * A child process is created, the "passer", and left in the
 * original group to pass interrupts, etc. on to the main group.  Synchronization
 * between the two processes across the fork guarantees that the original
 * note group survives, in case anyone has its notepg file open.  There is
 * a race, though, as to whether someone using the main process's pid
 * as the route to the notepg file will find the original group or the new one.
 */

void
main(int argc, char *argv[])
{
	char buf[40], err[32];
	int fd, mainpid;

	_qlockinit(_threadrendezvous);
	_sysfatal = threadsysfatal;
	mainpid = getpid();
	rfork(RFREND);
	atnotify(notehandler, 1);
	switch(rfork(RFPROC|RFMEM|RFNAMEG|RFCFDG)){
	case -1:
		threadassert(0);
	case 0:
		/* handle notes */
		if (_threaddebuglevel) {
			fd = open("/dev/cons", OWRITE);
			dup(fd, 2);
		}
		rendezvous(0, 0);	/* wait for parent to switch note groups */
		passerpid = getpid();
		sprint(buf, "#p/%d/notepg", mainpid);	/* notepg of main process's note group */
		notefd = open(buf, OWRITE|OCEXEC);
		if (_threaddebuglevel & DBGNOTE)
			threadprint(2, "Passer is %d\n", passerpid);
		rendezvous(1, 0);	/* tell parent we've opened notepg, can continue */
		for(;;){
			if(sleep(120*1000) < 0){
				errstr(err, sizeof(err));
				if (strcmp(err, "interrupted") != 0)
					break;
			}
		}
		if (_threaddebuglevel & DBGNOTE)
			threadprint(2, "Passer %d exits\n", passerpid);
		exits("interrupted");

	default:
		/* put main process in a different note group */
		rfork(RFNOTEG);
		rendezvous(0, 0);	/* tell child we switched note groups */
		rendezvous(1, 0);	/* wait for child to open notepg */
		quotefmtinstall();	/* for chanprint */
		if(mainstacksize == 0)
			mainstacksize = 512*1024;
		initproc(mainlauncher, argc, argv, mainstacksize);
		/* never returns */
	}
}

void
threadsetname(char *name)
{
	Thread *t = (*procp)->curthread;

	if (t->cmdname)
		free(t->cmdname);
	t->cmdname = strdup(name);
}

char *threadgetname(void) {
	return (*procp)->curthread->cmdname;
}

ulong*
procdata(void) {
	return &(*procp)->udata;
}

ulong*
threaddata(void) {
	return &(*procp)->curthread->udata;
}

int
procrfork(void (*f)(void *), void *arg, uint stacksize, int rforkflag) {
	Newproc *np;
	Proc *p;
	int id;
	ulong *tos;

	p = *procp;
	/* Save old stack position */
	if ((id = setjmp(p->curthread->env))) {
		_threaddebug(DBGPROC, "newproc, return\n");
		return id;	/* Return with pid of new proc */
	}
	np = _threadmalloc(sizeof(Newproc), 0);
	threadassert(np != nil);

	_threaddebug(DBGPROC, "newproc, creating stack\n");
	/* Create a stack */
	np->stack = _threadmalloc(stacksize, 0);
	threadassert(np->stack != nil);
	memset(np->stack, 0xFE, stacksize);
	tos = (ulong *)(&np->stack[stacksize&(~7)]);
	FIX1;
	*--tos = (ulong)arg;
	*--tos = (ulong)f;
	FIX2;
	np->stacksize = stacksize;
	np->stackptr = tos;
	np->grp = p->curthread->grp;
	np->launcher = (long)launcher;
	np->rforkflag = rforkflag;

	_threaddebug(DBGPROC, "newproc, switch stacks\n");
	/* Goto unshared stack and fire up new process */
	p->arg = np;
	longjmp(p->oldenv, DOPROC);
	/* no return */
	return -1;
}

int
proccreate(void (*f)(void*), void *arg, uint stacksize) {
	return procrfork(f, arg, stacksize, 0);
}

int
threadcreate(void (*f)(void *arg), void *arg, uint stacksize) {
	Thread *child;
	ulong *tos;
	Proc *p;

	p = *procp;
	if (stacksize < 32) {
		werrstr("%s", "stacksize");
		return -1;
	}
	if ((child = _threadmalloc(sizeof(Thread), 0)) == nil ||
		(child->stk = _threadmalloc(child->stksize = stacksize, 0)) == nil) {
			if (child) free(child);
			werrstr("%s", "_threadmalloc");
			return -1;
	}
	memset(child->stk, 0xFE, stacksize);
	p->nthreads++;
	child->cmdname = nil;
	child->id = nextID();
	child->proc = p;
	tos = (ulong *)(&child->stk[stacksize&~7]);
	FIX1;
	*--tos = (ulong)arg;
	*--tos = (ulong)f;
	FIX2;	/* Insert a dummy argument on 386 */
	child->env[JMPBUFPC] = ((ulong)launcher+JMPBUFDPC);
	/* -STACKOFF leaves room for old pc and new pc in frame */
	child->env[JMPBUFSP] = (ulong)(tos - STACKOFF);
	child->state = Runnable;
	child->exiting = 0;
	child->nextt = nil;
	if (p->threads.head == nil) {
		p->threads.head = p->threads.tail = child;
	} else {
		p->threads.tail->nextt = child;
		p->threads.tail = child;
	}
	child->next = (Thread *)~0;
	child->garbage = nil;
	putq(&p->runnable, child);
	return child->id;
}

void
procexecl(Channel *pidc, char *f, ...) {

	procexec(pidc, f, &f+1);
}

void
procexec(Channel *pidc, char *f, char *args[]) {
	Proc *p;
	Dir *d;
	int n, pid;
	Execproc *ep;
	Channel *c;
	char *q;
	int s, l;

	/* make sure exec is likely to succeed before tossing thread state */
	d = dirstat(f);
	if(d == nil || (d->mode & DMDIR) || access(f, AEXEC) < 0) {
		free(d);
bad:	if (pidc) sendul(pidc, ~0);
		return;
	}
	free(d);
	p = *procp;
	if(p->threads.head != p->curthread || p->threads.head->nextt != nil)
		goto bad;

	n = 0;
	while (args[n++])
		;
	ep = (Execproc*)procp;
	q = ep->data;
	s = sizeof(ep->data);
	if ((l = n*sizeof(char *)) < s) {
		_threaddebug(DBGPROC, "args on stack, %d pointers\n", n);
		ep->arg = (char **)q;
		q += l;
		s -= l;
	} else
		ep->arg = _threadmalloc(l, 0);	/* will be leaked */
	if ((l = strlen(f) + 1) < s) {
		ep->file = q;
		strcpy(q, f);
		_threaddebug(DBGPROC, "file on stack, %s\n", q);
		q += l;
		s -= l;
	} else
		ep->file = strdup(f);	/* will be leaked */
	ep->arg[--n] = nil;
	while (--n >= 0)
		if ((l = strlen(args[n]) + 1) < s) {
			ep->arg[n] = q;
			strcpy(q, args[n]);
			_threaddebug(DBGPROC, "arg on stack, %s\n", q);
			q += l;
			s -= l;
		} else
			ep->arg[n] = strdup(args[n]);	/* will be leaked */
	/* committed to exec-ing */
	if ((pid = setjmp(p->curthread->env))) {
		int i;
		Waitmsg *w;

		rfork(RFFDG);
		close(0);
		close(1);
		for (i = 3; i < 100; i++)
			close(i);
		if(pidc != nil)
			send(pidc, &pid);
		/* wait for exec-ed child */
		_threaddebug(DBGCHLD, "Proc %d waiting for exec-ed child %d\n", getpid(), pid);
		for(;;){
			w = wait();
			threadassert(w != nil);
			_threaddebug(DBGCHLD, "Child %d exited\n", w->pid);
			if(w->pid == pid)
				break;
			free(w);
		}
		if ((c = thewaitchan) != nil) {	/* global is exposed */
			_threaddebug(DBGCHLD, "Sending exit status for exec: real pid %d, fake pid %d, status %s\n", pid, getpid(), w->msg);
			sendp(c, w);
		}else
			free(w);
		_threaddebug(DBGPROC, "Exiting (exec)\n");
		threadexits("libthread procexec");
	}
	p->arg = ep;
	longjmp(p->oldenv, DOEXEC);
	/* No return; */
}

Channel *
threadwaitchan(void) {
	static void *arg[1];

	thewaitchan = chancreate(sizeof(Waitmsg*), 0);
	return thewaitchan;
}

int
threadgetgrp(void) {
	return (*procp)->curthread->grp;
}

int
threadsetgrp(int ng) {
	int og;

	og = (*procp)->curthread->grp;
	(*procp)->curthread->grp = ng;
	return og;
}

void *
_threadmalloc(long size, int z) {
	void *m;

	m = malloc(size);
	if (m == nil)
		sysfatal("Malloc of size %ld failed: %r\n", size);
	setmalloctag(m, getcallerpc(&size));
	totalmalloc += size;
	if (size > 1000000) {
		fprint(2, "Malloc of size %ld, total %ld\n", size, totalmalloc);
		abort();
	}
	if (z)
		memset(m, 0, size);
	return m;
}

void
threadnonotes(void)
{
	char buf[30];
	int fd;

	sprint(buf, "#p/%d/note", passerpid);
	if((fd = open(buf, OWRITE)) >= 0)
		write(fd, "kilpasser", 9);
	close(fd);
}


static void
runproc(Proc *p)
{
	int action, pid, rforkflag;
	Proc *pp;
	long r;
	int id;
	char str[32];

	r = ~0;
runp:
	/* Leave a proc manager */
	while ((action = setjmp(p->oldenv))) {
		Newproc *np;
		Execproc *ne;

		p = *procp;
		switch(action) {
		case DOEXEC:
			ne = (Execproc *)p->arg;
			if ((pid = rfork(RFPROC|RFMEM|RFREND|RFNOTEG|RFFDG)) < 0) {
				exits("doexecproc: fork: %r");
			}
			if (pid == 0) {
				exec(ne->file, ne->arg);
				if (_threaddebuglevel & DBGPROC) {
					char **a;
					fprint(2, "%s: ", ne->file);
					a = ne->arg;
					while(*a)
						fprint(2, "%s, ", *a++);
					fprint(2, "0\n");
				}
				exits("Can't exec");
				/* No return */
			}
			longjmp(p->curthread->env, pid);
			/* No return */
		case DOEXIT:
			_threaddebug(DBGPROC, "at doexit\n");
			lock(&pq.lock);
			if (pq.head == p) {
				pq.head = p->next;
				if (pq.tail == p) {
					pq.tail = nil;
					postnote(PNPROC, passerpid, "kilpasser");
				}
			} else {
				for (pp = pq.head; pp->next; pp = pp->next) {
					if(pp->next == p) {
						pp->next = p->next;
						if (pq.tail == p)
							pq.tail = pp;
						break;
					}
				}
			}
			unlock(&pq.lock);
			strncpy(str, p->str, sizeof(str));
			garbageproc(p);
			exits(str);
		case DOPROC:
			_threaddebug(DBGPROC, "at doproc\n");
			np = (Newproc *)p->arg;
			pp = prepproc(np);
			id = pp->curthread->id;	// get id before fork() to avoid race
			rforkflag = np->rforkflag;
			free(np);
			if ((pid = rfork(rforkflag|RFPROC|RFMEM|RFNOWAIT)) < 0) {
				exits("donewproc: fork: %r");
			}
			if (pid == 0) {
				/* Child is the new proc; touch old proc struct no more */
				p = pp;
				p->pid = getpid();
				goto runp;
				/* No return */
			}
			/* Parent, return to caller */
			r = id;			// better than returning pid
			_threaddebug(DBGPROC, "newproc, unswitch stacks\n");
			break;
		default:
			/* `Can't happen' */
			threadprint(2, "runproc, can't happen: %d\n", action);
			threadassert(0);
		}
	}
	if (_threaddebuglevel & DBGPROC)
		threadprint(2, "runproc, longjmp\n");
	/* Jump into proc */
	*procp = p;
	longjmp(p->curthread->env, r);
	/* No return */
}

static void
initproc(void (*f)(ulong, int argc, char *argv[]), int argc, char *argv[], uint stacksize) {
	Proc *p;
	Newproc *np;
	ulong *tos;
	ulong *av;
	int i;
	Execproc	ex;

	procp = (Proc **)&ex;	/* address of the execproc struct */

	if (_threaddebuglevel & DBGPROC)
		threadprint(2, "Initproc, f = 0x%p, argc = %d, argv = 0x%p\n",
			f, argc, argv);
	/* Create a stack and fill it */
	np = _threadmalloc(sizeof(Newproc), 0);
	threadassert(np != nil);
	np->stack = _threadmalloc(stacksize, 0);
	threadassert(np->stack != nil);
	memset(np->stack, 0xFE, stacksize);
	tos = (ulong *)(&np->stack[stacksize&(~7)]);
	np->stacksize = stacksize;
	np->rforkflag = 0;
	np->grp = 0;
	for (i = 0; i < argc; i++){
		char *nargv;
	
		nargv = (char *)tos - (strlen(argv[i]) + 1);
		strcpy(nargv, argv[i]);
		argv[i] = nargv;
		tos = (ulong *)nargv;
	}
	/* round down to address of char* */
	tos = (ulong *)((ulong)tos & ~0x3);
	tos -= argc + 1;
	/* round down to address of vlong (for the alpha): */
	tos = (ulong *)((ulong)tos & ~0x7);
	av = tos;
	memmove(av, argv, (argc+1)*sizeof(char *));
	FIX1;
	*--tos = (ulong)av;
	*--tos = (ulong)argc;
	FIX2;
	np->stackptr = tos;
	np->launcher = (long)f;
	p = prepproc(np);
	p->pid = getpid();
	free(np);
	if (_threaddebuglevel & DBGPROC)
		threadprint(2, "calling runproc\n");
	runproc(p);
	/* no return; */
}

static void
garbageproc(Proc *p) {
	Thread *t, *nextt;

	for (t = p->threads.head; t; t = nextt) {
		if (t->cmdname)
			free(t->cmdname);
		threadassert(t->stk != nil);
		free(t->stk);
		nextt = t->nextt;
		free(t);
	}
	free(p);
}

static void
garbagethread(Thread *t) {
	Proc *p;
	Thread *r, *pr;

	p = t->proc;
	threadassert(*procp == p);
	pr = nil;
	for (r = p->threads.head; r; r = r->nextt) {
		if (r == t)
			break;
		pr = r;
	}
	threadassert (r != nil);
	if (pr)
		pr->nextt = r->nextt;
	else
		p->threads.head = r->nextt;
	if (p->threads.tail == r)
		p->threads.tail = pr;
	if (t->cmdname)
		free(t->cmdname);
	threadassert(t->stk != nil);
	free(t->stk);
	free(t);
}

static void
putq(Tqueue *q, Thread *t) {
	lock(&q->lock);
	_threaddebug(DBGQUE, "Putq 0x%lux on 0x%lux, next == 0x%lux",
		(ulong)t, (ulong)q, (ulong)t->next);
	threadassert((ulong)(t->next) == (ulong)~0);
	t->next = nil;
	if (q->head == nil) {
		q->head = t;
		q->tail = t;
	} else {
		threadassert(q->tail->next == nil);
		q->tail->next = t;
		q->tail = t;
	}
	unlock(&q->lock);
}

static Thread *
getq(Tqueue *q) {
	Thread *t;

	lock(&q->lock);
	if ((t = q->head)) {
		q->head = q->head->next;
		t->next = (Thread *)~0;
	}
	unlock(&q->lock);
	_threaddebug(DBGQUE, "Getq 0x%lux from 0x%lux", (ulong)t, (ulong)q);
	return t;
}

static Thread *
getqbytag(Tqueue *q, ulong tag) {
	Thread *r, *pr, *w, *pw;

	w = pr = pw = nil;
	_threaddebug(DBGQUE, "Getqbytag 0x%lux", q);
	lock(&q->lock);
	for (r = q->head; r; r = r->next) {
		if (r->tag == tag) {
			w = r;
			pw = pr;
			if (r->proc == *procp) {
				/* Locals or blocked remotes are best */
				break;
			}
		}
		pr = r;
	}
	if (w) {
		if (pw)
			pw->next = w->next;
		else
			q->head = w->next;
		if (q->tail == w){
			q->tail = pw;
			threadassert(pw == nil || pw->next == nil);
		}
		w->next = (Thread *)~0;
	}
	unlock(&q->lock);
	_threaddebug(DBGQUE, "Getqbytag 0x%lux from 0x%lux", w, q);
	return w;
}

static void
launcher(ulong, void (*f)(void *arg), void *arg) {
	Proc *p;
	Thread *t;

	p = *procp;
	t = p->curthread;
	if (t->garbage) {
		garbagethread(t->garbage);
		t->garbage = nil;
	}

	(*f)(arg);
	threadexits(nil);
	/* no return */
}

static void
mainlauncher(ulong, int argc, char *argv[]) {
/*	ulong *p; */

/*	p = (ulong *)&argc; */
/*	fprint(2, "p[-2..2]: %lux %lux %lux %lux %lux\n", */
/*		p[-2], p[-1], p[0], p[1], p[2]); */

	threadmain(argc, argv);
	threadexits(nil);
	/* no return */
}

static Proc *
prepproc(Newproc *np) {
	Proc *p;
	Thread *t;

	/* Create proc and thread structs */
	p = _threadmalloc(sizeof(Proc), 1);
	t = _threadmalloc(sizeof(Thread), 1);
	if (p == nil || t == nil) {
		char err[32] = "";
		errstr(err, 32);
		write(2, err, strlen(err));
		write(2, "\n", 1);
		exits("procinit: _threadmalloc");
	}
	t->cmdname = strdup("threadmain");
	t->id = nextID();
	t->grp = np->grp;	/* Inherit grp id */
	t->proc = p;
	t->state = Running;
	t->nextt = nil;
	t->next = (Thread *)~0;
	t->garbage = nil;
	t->stk = np->stack;
	t->stksize = np->stacksize;
	t->env[JMPBUFPC] = (np->launcher+JMPBUFDPC);
	/* -STACKOFF leaves room for old pc and new pc in frame */
	t->env[JMPBUFSP] = (ulong)(np->stackptr - STACKOFF);
/*fprint(2, "SP = %lux\n", t->env[JMPBUFSP]); */
	p->curthread = t;
	p->threads.head = p->threads.tail = t;
	p->nthreads = 1;
	p->pid = 0;
	p->next = nil;

	lock(&pq.lock);
	if (pq.head == nil) {
		pq.head = pq.tail = p;
	} else {
		threadassert(pq.tail->next == nil);
		pq.tail->next = p;
		pq.tail = p;
	}
	unlock(&pq.lock);

	return p;
}

int
threadprint(int fd, char *fmt, ...)
{
	int n;
	va_list arg;
	char *buf;

	buf = _threadmalloc(2048, 0);
	if(buf == nil)
		return -1;
	va_start(arg, fmt);
	doprint(buf, buf+2048, fmt, arg);
	va_end(arg);
	n = write(fd, buf, strlen(buf));
	free(buf);
	return n;
}
