shithub: drawterm-fdroid

Download patch

ref: 7c69c6d7f62c06ced83e85eba0f04deea14f73c8
parent: 748142a97c32cd48a8df7e354b40d9638e27ae38
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sat Nov 23 19:21:28 EST 2019

cmd(3): port the os command execution device from inferno (win32 only so far)

the devcmd device (/mnt/term/cmd) allows execution of commands on the
os, with access stdin/stdout/stderr of the executing command.

--- a/kern/Makefile
+++ b/kern/Makefile
@@ -10,6 +10,7 @@
 	dev.$O\
 	devaudio.$O\
 	devaudio-$(AUDIO).$O\
+	devcmd.$O\
 	devcons.$O\
 	devdraw.$O\
 	devfs-$(OS).$O\
@@ -16,7 +17,7 @@
 	devip.$O\
 	devip-$(OS).$O\
 	devkbd.$O\
-	devlfd.$O\
+	devlfd-$(OS).$O\
 	devmnt.$O\
 	devmouse.$O\
 	devpipe.$O\
--- /dev/null
+++ b/kern/devcmd.c
@@ -1,0 +1,700 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Qtopdir,	/* top level directory */
+	Qcmd,
+	Qclonus,
+	Qconvdir,
+	Qconvbase,
+	Qdata = Qconvbase,
+	Qstderr,
+	Qctl,
+	Qstatus,
+	Qwait,
+
+	Debug=0	/* to help debug os.c */
+};
+#define TYPE(x) 	((ulong)(x).path & 0xf)
+#define CONV(x) 	(((ulong)(x).path >> 4)&0xfff)
+#define QID(c, y) 	(((c)<<4) | (y))
+
+typedef struct Conv	Conv;
+struct Conv
+{
+	int	x;
+	int	inuse;
+	Chan*	fd[3];		/* stdin, stdout, and stderr */
+	int	count[3];	/* number of readers on stdin/stdout/stderr */
+	int	perm;
+	char*	owner;
+	char*	state;
+	Cmdbuf*	cmd;
+	char*	dir;
+	QLock	l;	/* protects state changes */
+	Queue*	waitq;
+	void*	child;
+	char*	error;	/* on start up */
+	int	nice;
+	short	killonclose;
+	short	killed;
+	Rendez	startr;
+};
+
+static struct
+{
+	QLock	l;
+	int	nc;
+	int	maxconv;
+	Conv**	conv;
+} cmd;
+
+static	Conv*	cmdclone(char*);
+static	void	cmdproc(void*);
+
+static int
+cmd3gen(Chan *c, int i, Dir *dp)
+{
+	Qid q;
+	Conv *cv;
+
+	cv = cmd.conv[CONV(c->qid)];
+	switch(i){
+	default:
+		return -1;
+	case Qdata:
+		mkqid(&q, QID(CONV(c->qid), Qdata), 0, QTFILE);
+		devdir(c, q, "data", 0, cv->owner, cv->perm, dp);
+		return 1;
+	case Qstderr:
+		mkqid(&q, QID(CONV(c->qid), Qstderr), 0, QTFILE);
+		devdir(c, q, "stderr", 0, cv->owner, 0444, dp);
+		return 1;
+	case Qctl:
+		mkqid(&q, QID(CONV(c->qid), Qctl), 0, QTFILE);
+		devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp);
+		return 1;
+	case Qstatus:
+		mkqid(&q, QID(CONV(c->qid), Qstatus), 0, QTFILE);
+		devdir(c, q, "status", 0, cv->owner, 0444, dp);
+		return 1;
+	case Qwait:
+		mkqid(&q, QID(CONV(c->qid), Qwait), 0, QTFILE);
+		devdir(c, q, "wait", 0, cv->owner, 0444, dp);
+		return 1;
+	}
+}
+
+static int
+cmdgen(Chan *c, char *name, Dirtab *d, int nd, int s, Dir *dp)
+{
+	Qid q;
+	Conv *cv;
+
+	USED(name);
+	USED(nd);
+	USED(d);
+
+	if(s == DEVDOTDOT){
+		switch(TYPE(c->qid)){
+		case Qtopdir:
+		case Qcmd:
+			mkqid(&q, QID(0, Qtopdir), 0, QTDIR);
+			devdir(c, q, "#C", 0, eve, DMDIR|0555, dp);
+			break;
+		case Qconvdir:
+			mkqid(&q, QID(0, Qcmd), 0, QTDIR);
+			devdir(c, q, "cmd", 0, eve, DMDIR|0555, dp);
+			break;
+		default:
+			panic("cmdgen %llux", c->qid.path);
+		}
+		return 1;
+	}
+
+	switch(TYPE(c->qid)) {
+	case Qtopdir:
+		if(s >= 1)
+			return -1;
+		mkqid(&q, QID(0, Qcmd), 0, QTDIR);
+		devdir(c, q, "cmd", 0, "cmd", DMDIR|0555, dp);
+		return 1;
+	case Qcmd:
+		if(s < cmd.nc) {
+			cv = cmd.conv[s];
+			mkqid(&q, QID(s, Qconvdir), 0, QTDIR);
+			sprint(up->genbuf, "%d", s);
+			devdir(c, q, up->genbuf, 0, cv->owner, DMDIR|0555, dp);
+			return 1;
+		}
+		s -= cmd.nc;
+		if(s == 0){
+			mkqid(&q, QID(0, Qclonus), 0, QTFILE);
+			devdir(c, q, "clone", 0, "cmd", 0666, dp);
+			return 1;
+		}
+		return -1;
+	case Qclonus:
+		if(s == 0){
+			mkqid(&q, QID(0, Qclonus), 0, QTFILE);
+			devdir(c, q, "clone", 0, "cmd", 0666, dp);
+			return 1;
+		}
+		return -1;
+	case Qconvdir:
+		return cmd3gen(c, Qconvbase+s, dp);
+	case Qdata:
+	case Qstderr:
+	case Qctl:
+	case Qstatus:
+	case Qwait:
+		return cmd3gen(c, TYPE(c->qid), dp);
+	}
+	return -1;
+}
+
+static void
+cmdinit(void)
+{
+	cmd.maxconv = 1000;
+	cmd.conv = mallocz(sizeof(Conv*)*(cmd.maxconv+1), 1);
+	/* cmd.conv is checked by cmdattach, below */
+}
+
+static Chan *
+cmdattach(char *spec)
+{
+	Chan *c;
+
+	if(cmd.conv == nil)
+		error(Enomem);
+	c = devattach('C', spec);
+	mkqid(&c->qid, QID(0, Qtopdir), 0, QTDIR);
+	return c;
+}
+
+static Walkqid*
+cmdwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, 0, 0, cmdgen);
+}
+
+static int
+cmdstat(Chan *c, uchar *db, int n)
+{
+	return devstat(c, db, n, 0, 0, cmdgen);
+}
+
+static Chan *
+cmdopen(Chan *c, int omode)
+{
+	int perm;
+	Conv *cv;
+	char *user;
+
+	perm = 0;
+	omode = openmode(omode);
+	switch(omode) {
+	case OREAD:
+		perm = 4;
+		break;
+	case OWRITE:
+		perm = 2;
+		break;
+	case ORDWR:
+		perm = 6;
+		break;
+	}
+
+	switch(TYPE(c->qid)) {
+	default:
+		break;
+	case Qtopdir:
+	case Qcmd:
+	case Qconvdir:
+	case Qstatus:
+		if(omode != OREAD)
+			error(Eperm);
+		break;
+	case Qclonus:
+		qlock(&cmd.l);
+		if(waserror()){
+			qunlock(&cmd.l);
+			nexterror();
+		}
+		cv = cmdclone(up->user);
+		poperror();
+		qunlock(&cmd.l);
+		if(cv == 0)
+			error(Enodev);
+		mkqid(&c->qid, QID(cv->x, Qctl), 0, QTFILE);
+		break;
+	case Qdata:
+	case Qstderr:
+	case Qctl:
+	case Qwait:
+		qlock(&cmd.l);
+		cv = cmd.conv[CONV(c->qid)];
+		qlock(&cv->l);
+		if(waserror()){
+			qunlock(&cv->l);
+			qunlock(&cmd.l);
+			nexterror();
+		}
+		user = up->user;
+		if((perm & (cv->perm>>6)) != perm) {
+			if(strcmp(user, cv->owner) != 0 ||
+		 	  (perm & cv->perm) != perm)
+				error(Eperm);
+		}
+		switch(TYPE(c->qid)){
+		case Qdata:
+			if(omode == OWRITE || omode == ORDWR)
+				cv->count[0]++;
+			if(omode == OREAD || omode == ORDWR)
+				cv->count[1]++;
+			break;
+		case Qstderr:
+			if(omode != OREAD)
+				error(Eperm);
+			cv->count[2]++;
+			break;
+		case Qwait:
+			if(cv->waitq == nil)
+				cv->waitq = qopen(1024, Qmsg, nil, 0);
+			break;
+		}
+		cv->inuse++;
+		if(cv->inuse == 1) {
+			cv->state = "Open";
+			kstrdup(&cv->owner, user);
+			cv->perm = 0660;
+			cv->nice = 0;
+		}
+		poperror();
+		qunlock(&cv->l);
+		qunlock(&cmd.l);
+		break;
+	}
+	c->mode = omode;
+	c->flag |= COPEN;
+	c->offset = 0;
+	return c;
+}
+
+static void
+closeconv(Conv *c)
+{
+	kstrdup(&c->owner, "cmd");
+	kstrdup(&c->dir, ".");
+	c->perm = 0666;
+	c->state = "Closed";
+	c->killonclose = 0;
+	c->killed = 0;
+	c->nice = 0;
+	free(c->cmd);
+	c->cmd = nil;
+	if(c->waitq != nil){
+		qfree(c->waitq);
+		c->waitq = nil;
+	}
+	free(c->error);
+	c->error = nil;
+}
+
+static void
+cmdfdclose(Conv *c, int fd)
+{
+	if(--c->count[fd] == 0 && c->fd[fd] != nil){
+		cclose(c->fd[fd]);
+		c->fd[fd] = nil;
+	}
+}
+
+static void
+cmdclose(Chan *c)
+{
+	Conv *cc;
+	int r;
+
+	if((c->flag & COPEN) == 0)
+		return;
+
+	switch(TYPE(c->qid)) {
+	case Qctl:
+	case Qdata:
+	case Qstderr:
+	case Qwait:
+		cc = cmd.conv[CONV(c->qid)];
+		qlock(&cc->l);
+		if(TYPE(c->qid) == Qdata){
+			if(c->mode == OWRITE || c->mode == ORDWR)
+				cmdfdclose(cc, 0);
+			if(c->mode == OREAD || c->mode == ORDWR)
+				cmdfdclose(cc, 1);
+		}else if(TYPE(c->qid) == Qstderr)
+			cmdfdclose(cc, 2);
+
+		r = --cc->inuse;
+		if(cc->child != nil){
+			if(!cc->killed)
+			if(r == 0 || (cc->killonclose && TYPE(c->qid) == Qctl)){
+				iprint("cmdclose killing (killonclose %d)\n", cc->killonclose);
+				oscmdkill(cc->child);
+				cc->killed = 1;
+			}
+		}else if(r == 0)
+			closeconv(cc);
+
+		qunlock(&cc->l);
+		break;
+	}
+}
+
+static long
+cmdread(Chan *ch, void *a, long n, vlong offset)
+{
+	Conv *c;
+	char *p, *cmds;
+	int fd;
+
+	USED(offset);
+
+	p = a;
+	switch(TYPE(ch->qid)) {
+	default:
+		error(Eperm);
+	case Qcmd:
+	case Qtopdir:
+	case Qconvdir:
+		return devdirread(ch, a, n, 0, 0, cmdgen);
+	case Qctl:
+		sprint(up->genbuf, "%ld", CONV(ch->qid));
+		return readstr(offset, p, n, up->genbuf);
+	case Qstatus:
+		c = cmd.conv[CONV(ch->qid)];
+		cmds = "";
+		if(c->cmd != nil)
+			cmds = c->cmd->f[1];
+		snprint(up->genbuf, sizeof(up->genbuf), "cmd/%d %d %s %q %q\n",
+			c->x, c->inuse, c->state, c->dir, cmds);
+		return readstr(offset, p, n, up->genbuf);
+	case Qdata:
+	case Qstderr:
+		fd = 1;
+		if(TYPE(ch->qid) == Qstderr)
+			fd = 2;
+		c = cmd.conv[CONV(ch->qid)];
+		qlock(&c->l);
+		ch = c->fd[fd];
+		if(ch == nil){
+			qunlock(&c->l);
+			return 0;
+		}
+		incref(&ch->ref);
+		qunlock(&c->l);
+		if(waserror()){
+			cclose(ch);
+			nexterror();
+		}
+		n = devtab[ch->type]->read(ch, a, n, 0);
+		if(n < 0)
+			oserror();
+		poperror();
+		cclose(ch);
+		return n;
+	case Qwait:
+		c = cmd.conv[CONV(ch->qid)];
+		return qread(c->waitq, a, n);
+	}
+}
+
+static int
+cmdstarted(void *a)
+{
+	Conv *c;
+
+	c = a;
+	return c->child != nil || c->error != nil || strcmp(c->state, "Execute") != 0;
+}
+
+enum
+{
+	CMdir,
+	CMexec,
+	CMkill,
+	CMnice,
+	CMkillonclose
+};
+
+static
+Cmdtab cmdtab[] = {
+	CMdir,	"dir",	2,
+	CMexec,	"exec",	0,
+	CMkill,	"kill",	1,
+	CMnice,	"nice",	0,
+	CMkillonclose, "killonclose", 0,
+};
+
+static long
+cmdwrite(Chan *ch, void *a, long n, vlong offset)
+{
+	int i, r;
+	Conv *c;
+	Cmdbuf *cb;
+	Cmdtab *ct;
+
+	USED(offset);
+
+	switch(TYPE(ch->qid)) {
+	default:
+		error(Eperm);
+	case Qctl:
+		c = cmd.conv[CONV(ch->qid)];
+		cb = parsecmd(a, n);
+		if(waserror()){
+			free(cb);
+			nexterror();
+		}
+		ct = lookupcmd(cb, cmdtab, nelem(cmdtab));
+		switch(ct->index){
+		case CMdir:
+			kstrdup(&c->dir, cb->f[1]);
+			break;
+		case CMexec:
+			poperror();	/* cb */
+			qlock(&c->l);
+			if(waserror()){
+				qunlock(&c->l);
+				free(cb);
+				nexterror();
+			}
+			if(c->child != nil || c->cmd != nil)
+				error(Einuse);
+			for(i = 0; i < nelem(c->fd); i++)
+				if(c->fd[i] != nil)
+					error(Einuse);
+			if(cb->nf < 1)
+				error(Etoosmall);
+			kproc("cmdproc", cmdproc, c);	/* cmdproc held back until unlock below */
+			free(c->cmd);
+			c->cmd = cb;	/* don't free cb */
+			c->state = "Execute";
+			poperror();
+			qunlock(&c->l);
+			while(waserror())
+				;
+			sleep(&c->startr, cmdstarted, c);
+			poperror();
+			if(c->error)
+				error(c->error);
+			return n;	/* avoid free(cb) below */
+		case CMkill:
+			qlock(&c->l);
+			if(waserror()){
+				qunlock(&c->l);
+				nexterror();
+			}
+			if(c->child == nil)
+				error("not started");
+			if(oscmdkill(c->child) < 0)
+				oserror();
+			poperror();
+			qunlock(&c->l);
+			break;
+		case CMnice:
+			c->nice = cb->nf > 1? atoi(cb->f[1]): 1;
+			break;
+		case CMkillonclose:
+			c->killonclose = 1;
+			break;
+		}
+		poperror();
+		free(cb);
+		break;
+	case Qdata:
+		c = cmd.conv[CONV(ch->qid)];
+		qlock(&c->l);
+		ch = c->fd[0];
+		if(ch == nil){
+			qunlock(&c->l);
+			error(Ehungup);
+		}
+		incref(&ch->ref);
+		qunlock(&c->l);
+		if(waserror()){
+			cclose(ch);
+			nexterror();
+		}
+		r = devtab[ch->type]->write(ch, a, n, 0);
+		if(r == 0)
+			error(Ehungup);
+		if(r < 0) {
+			/* XXX perhaps should kill writer "write on closed pipe" here, 2nd time around? */
+			oserror();
+		}
+		poperror();
+		cclose(ch);
+		return r;
+	}
+	return n;
+}
+
+static int
+cmdwstat(Chan *c, uchar *dp, int n)
+{
+	Dir *d;
+	Conv *cv;
+
+	switch(TYPE(c->qid)){
+	default:
+		error(Eperm);
+	case Qctl:
+	case Qdata:
+	case Qstderr:
+		d = malloc(sizeof(*d)+n);
+		if(d == nil)
+			error(Enomem);
+		if(waserror()){
+			free(d);
+			nexterror();
+		}
+		n = convM2D(dp, n, d, (char*)&d[1]);
+		if(n == 0)
+			error(Eshortstat);
+		cv = cmd.conv[CONV(c->qid)];
+		if(!iseve() && strcmp(up->user, cv->owner) != 0)
+			error(Eperm);
+		if(!emptystr(d->uid))
+			kstrdup(&cv->owner, d->uid);
+		if(d->mode != ~0UL)
+			cv->perm = d->mode & 0777;
+		poperror();
+		free(d);
+		break;
+	}
+	return n;
+}
+
+static Conv*
+cmdclone(char *user)
+{
+	Conv *c, **pp, **ep;
+	int i;
+
+	c = nil;
+	ep = &cmd.conv[cmd.maxconv];
+	for(pp = cmd.conv; pp < ep; pp++) {
+		c = *pp;
+		if(c == nil) {
+			c = malloc(sizeof(Conv));
+			if(c == nil)
+				error(Enomem);
+			qlock(&c->l);
+			c->inuse = 1;
+			c->x = pp - cmd.conv;
+			cmd.nc++;
+			*pp = c;
+			break;
+		}
+		if(canqlock(&c->l)){
+			if(c->inuse == 0 && c->child == nil)
+				break;
+			qunlock(&c->l);
+		}
+	}
+	if(pp >= ep)
+		return nil;
+
+	c->inuse = 1;
+	kstrdup(&c->owner, user);
+	kstrdup(&c->dir, ".");
+	c->perm = 0660;
+	c->state = "Closed";
+	for(i=0; i<nelem(c->fd); i++)
+		c->fd[i] = nil;
+
+	qunlock(&c->l);
+	return c;
+}
+
+static void
+cmdproc(void *a)
+{
+	Conv *c;
+	int n;
+	char status[ERRMAX];
+	void *t;
+
+	c = a;
+	qlock(&c->l);
+	if(Debug)
+		print("f[0]=%q f[1]=%q\n", c->cmd->f[0], c->cmd->f[1]);
+	if(waserror()){
+		if(Debug)
+			print("failed: %q\n", up->errstr);
+		kstrdup(&c->error, up->errstr);
+		c->state = "Done";
+		wakeup(&c->startr);
+		qunlock(&c->l);
+		pexit("cmdproc", 0);
+	}
+	t = oscmd(c->cmd->f+1, c->nice, c->dir, c->fd);
+	if(t == nil)
+		oserror();
+	c->child = t;	/* to allow oscmdkill */
+	poperror();
+	qunlock(&c->l);
+	wakeup(&c->startr);
+	if(Debug)
+		print("started\n");
+	while(waserror()){
+		iprint("XXX %s\n", up->errstr);
+		oscmdkill(t);
+	}
+	n = oscmdwait(t, status, sizeof(status));
+	if(n < 0){
+		oserrstr();
+		n = snprint(status, sizeof(status), "0 0 0 0 %q", up->errstr);
+	}
+	qlock(&c->l);
+	c->child = nil;
+	oscmdfree(t);
+	if(Debug){
+		status[n]=0;
+		print("done %s %s %s: %q\n", chanpath(c->fd[0]), chanpath(c->fd[1]), chanpath(c->fd[2]), status);
+	}
+	if(c->inuse > 0){
+		c->state = "Done";
+		if(c->waitq != nil)
+			qproduce(c->waitq, status, n);
+	}else
+		closeconv(c);
+	qunlock(&c->l);
+	pexit("", 0);
+}
+
+Dev cmddevtab = {
+	'C',
+	"cmd",
+
+	devreset,
+	cmdinit,
+	devshutdown,
+	cmdattach,
+	cmdwalk,
+	cmdstat,
+	cmdopen,
+	devcreate,
+	cmdclose,
+	cmdread,
+	devbread,
+	cmdwrite,
+	devbwrite,
+	devremove,
+	cmdwstat
+};
--- /dev/null
+++ b/kern/devlfd-posix.c
@@ -1,0 +1,115 @@
+#include	"u.h"
+#include 	<errno.h>
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#undef read
+#undef write
+
+Chan*
+lfdchan(void *fd)
+{
+	Chan *c;
+	
+	c = newchan();
+	c->type = devno('L', 0);
+	c->aux = fd;
+	c->path = newpath("fd");
+	c->mode = ORDWR;
+	c->qid.type = 0;
+	c->qid.path = 0;
+	c->qid.vers = 0;
+	c->dev = 0;
+	c->offset = 0;
+	return c;
+}
+
+static Chan*
+lfdattach(char *x)
+{
+	USED(x);
+	
+	error(Egreg);
+	return nil;
+}
+
+static Walkqid*
+lfdwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	USED(c);
+	USED(nc);
+	USED(name);
+	USED(nname);
+	error(Egreg);
+	return nil;
+}
+
+static int
+lfdstat(Chan *c, uchar *dp, int n)
+{
+	USED(c);
+	USED(dp);
+	USED(n);
+	error(Egreg);
+	return -1;
+}
+
+static Chan*
+lfdopen(Chan *c, int omode)
+{
+	USED(c);
+	USED(omode);
+	error(Egreg);
+	return nil;
+}
+
+static void
+lfdclose(Chan *c)
+{
+	close((int)(uintptr)c->aux);
+}
+
+static long
+lfdread(Chan *c, void *buf, long n, vlong off)
+{
+	USED(off);	/* can't pread on pipes */
+
+	n = read((int)(uintptr)c->aux, buf, n);
+	if(n < 0)
+		oserror();
+	return n;
+}
+
+static long
+lfdwrite(Chan *c, void *buf, long n, vlong off)
+{
+	USED(off);	/* can't pread on pipes */
+
+	n = write((int)(uintptr)c->aux, buf, n);
+	if(n < 0)
+		oserror();
+	return n;
+}
+
+Dev lfddevtab = {
+	'L',
+	"lfd",
+	
+	devreset,
+	devinit,
+	devshutdown,
+	lfdattach,
+	lfdwalk,
+	lfdstat,
+	lfdopen,
+	devcreate,
+	lfdclose,
+	lfdread,
+	devbread,
+	lfdwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
--- /dev/null
+++ b/kern/devlfd-win32.c
@@ -1,0 +1,113 @@
+#include	<windows.h>
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+Chan*
+lfdchan(void *fd)
+{
+	Chan *c;
+	
+	c = newchan();
+	c->type = devno('L', 0);
+	c->aux = fd;
+	c->path = newpath("fd");
+	c->mode = ORDWR;
+	c->qid.type = 0;
+	c->qid.path = 0;
+	c->qid.vers = 0;
+	c->dev = 0;
+	c->offset = 0;
+	return c;
+}
+
+static Chan*
+lfdattach(char *x)
+{
+	USED(x);
+	
+	error(Egreg);
+	return nil;
+}
+
+static Walkqid*
+lfdwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	USED(c);
+	USED(nc);
+	USED(name);
+	USED(nname);
+	
+	error(Egreg);
+	return nil;
+}
+
+static int
+lfdstat(Chan *c, uchar *dp, int n)
+{
+	USED(c);
+	USED(dp);
+	USED(n);
+	error(Egreg);
+	return -1;
+}
+
+static Chan*
+lfdopen(Chan *c, int omode)
+{
+	USED(c);
+	USED(omode);
+	error(Egreg);
+	return nil;
+}
+
+static void
+lfdclose(Chan *c)
+{
+	CloseHandle((HANDLE)c->aux);
+}
+
+static long
+lfdread(Chan *c, void *buf, long n, vlong off)
+{
+	DWORD r;
+
+	USED(off);	/* can't pread on pipes */
+	if(!ReadFile((HANDLE)c->aux, buf, (DWORD)n, &r, NULL))
+		oserror();
+	return r;
+}
+
+static long
+lfdwrite(Chan *c, void *buf, long n, vlong off)
+{
+	DWORD r;
+
+	USED(off);	/* can't pread on pipes */
+	if(!WriteFile((HANDLE)c->aux, buf, (DWORD)n, &r, NULL))
+		oserror();
+	return r;
+}
+
+Dev lfddevtab = {
+	'L',
+	"lfd",
+	
+	devreset,
+	devinit,
+	devshutdown,
+	lfdattach,
+	lfdwalk,
+	lfdstat,
+	lfdopen,
+	devcreate,
+	lfdclose,
+	lfdread,
+	devbread,
+	lfdwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
--- a/kern/devlfd.c
+++ /dev/null
@@ -1,126 +1,0 @@
-#include	"u.h"
-#include <errno.h>
-#include	"lib.h"
-#include	"dat.h"
-#include	"fns.h"
-#include	"error.h"
-
-#undef pread
-#undef pwrite
-
-Chan*
-lfdchan(int fd)
-{
-	Chan *c;
-	
-	c = newchan();
-	c->type = devno('L', 0);
-	c->aux = (void*)(uintptr)fd;
-	c->path = newpath("fd");
-	c->mode = ORDWR;
-	c->qid.type = 0;
-	c->qid.path = 0;
-	c->qid.vers = 0;
-	c->dev = 0;
-	c->offset = 0;
-	return c;
-}
-
-int
-lfdfd(int fd)
-{
-	return newfd(lfdchan(fd));
-}
-
-static Chan*
-lfdattach(char *x)
-{
-	USED(x);
-	
-	error(Egreg);
-	return nil;
-}
-
-static Walkqid*
-lfdwalk(Chan *c, Chan *nc, char **name, int nname)
-{
-	USED(c);
-	USED(nc);
-	USED(name);
-	USED(nname);
-	
-	error(Egreg);
-	return nil;
-}
-
-static int
-lfdstat(Chan *c, uchar *dp, int n)
-{
-	USED(c);
-	USED(dp);
-	USED(n);
-	error(Egreg);
-	return -1;
-}
-
-static Chan*
-lfdopen(Chan *c, int omode)
-{
-	USED(c);
-	USED(omode);
-	
-	error(Egreg);
-	return nil;
-}
-
-static void
-lfdclose(Chan *c)
-{
-	close((int)(uintptr)c->aux);
-}
-
-static long
-lfdread(Chan *c, void *buf, long n, vlong off)
-{
-	USED(off);	/* can't pread on pipes */
-	n = read((int)(uintptr)c->aux, buf, n);
-	if(n < 0){
-		iprint("error %d\n", errno);
-		oserror();
-	}
-	return n;
-}
-
-static long
-lfdwrite(Chan *c, void *buf, long n, vlong off)
-{
-	USED(off);	/* can't pread on pipes */
-
-	n = write((int)(uintptr)c->aux, buf, n);
-	if(n < 0){
-		iprint("error %d\n", errno);
-		oserror();
-	}
-	return n;
-}
-
-Dev lfddevtab = {
-	'L',
-	"lfd",
-	
-	devreset,
-	devinit,
-	devshutdown,
-	lfdattach,
-	lfdwalk,
-	lfdstat,
-	lfdopen,
-	devcreate,
-	lfdclose,
-	lfdread,
-	devbread,
-	lfdwrite,
-	devbwrite,
-	devremove,
-	devwstat,
-};
--- a/kern/devtab.c
+++ b/kern/devtab.c
@@ -17,6 +17,7 @@
 extern Dev lfddevtab;
 extern Dev audiodevtab;
 extern Dev kbddevtab;
+extern Dev cmddevtab;
 
 Dev *devtab[] = {
 	&rootdevtab,
@@ -32,6 +33,7 @@
 	&lfddevtab,
 	&audiodevtab,
 	&kbddevtab,
+	&cmddevtab,
 	0
 };
 
--- a/kern/fns.h
+++ b/kern/fns.h
@@ -84,6 +84,7 @@
 void		kstrcpy(char*, char*, int);
 void		kstrdup(char**, char*);
 long		latin1(Rune*, int);
+Chan*		lfdchan(void *);
 void		lock(Lock*);
 void		lockinit(void);
 void		logopen(Log*);
@@ -114,8 +115,13 @@
 char*		nextelem(char*, char*);
 void		nexterror(void);
 int		openmode(ulong);
+void*		oscmd(char**, int, char*, Chan**);
+int		oscmdwait(void*, char*, int);
+int		oscmdkill(void*);
+void		oscmdfree(void*);
 void		oserrstr(void);
 void		oserror(void);
+void		osexit(void);
 Block*		packblock(Block*);
 Block*		padblock(Block*, int);
 void		panic(char*, ...);
--- a/kern/posix.c
+++ b/kern/posix.c
@@ -106,8 +106,19 @@
 	nexterror();
 }
 
-static void* tramp(void*);
+static void*
+tramp(void *vp)
+{
+	Proc *p;
 
+	p = vp;
+	if(pthread_setspecific(prdakey, p))
+		panic("cannot setspecific");
+	(*p->fn)(p->arg);
+	pexit("", 0);
+	return 0;
+}
+
 void
 osproc(Proc *p)
 {
@@ -120,19 +131,11 @@
 	sched_yield();
 }
 
-static void*
-tramp(void *vp)
+void
+osexit(void)
 {
-	Proc *p;
-
-	p = vp;
-	if(pthread_setspecific(prdakey, p))
-		panic("cannot setspecific");
-	(*p->fn)(p->arg);
-	/* BUG: leaks Proc */
 	pthread_setspecific(prdakey, 0);
 	pthread_exit(0);
-	return 0;
 }
 
 void
@@ -161,6 +164,41 @@
 	if(op->nwakeup == op->nsleep)
 		pthread_cond_signal(&op->cond);
 	pthread_mutex_unlock(&op->mutex);
+}
+
+void*
+oscmd(char **argv, int nice, char *dir, Chan **fd)
+{
+	USED(argv);
+	USED(nice);
+	USED(dir);
+	USED(fd);
+
+	error("not implemented");
+	return nil;
+}
+
+int
+oscmdwait(void *c, char *status, int nstatus)
+{
+	USED(c);
+	USED(status);
+	USED(nstatus);
+
+	return -1;
+}
+
+int
+oscmdkill(void *c)
+{
+	USED(c);
+	return -1;
+}
+
+void
+oscmdfree(void *c)
+{
+	USED(c);
 }
 
 static int randfd;
--- a/kern/procinit.c
+++ b/kern/procinit.c
@@ -4,8 +4,6 @@
 #include "fns.h"
 #include "error.h"
 
-Rgrp *thergrp;
-
 void
 procinit0(void)
 {
@@ -51,13 +49,13 @@
 	p->slash = cclone(up->slash);
 	p->dot = cclone(up->dot);
 	p->rgrp = up->rgrp;
-	if(p->rgrp)
+	if(p->rgrp != nil)
 		incref(&p->rgrp->ref);
 	p->pgrp = up->pgrp;
-	if(up->pgrp)
+	if(up->pgrp != nil)
 		incref(&up->pgrp->ref);
 	p->fgrp = up->fgrp;
-	if(p->fgrp)
+	if(p->fgrp != nil)
 		incref(&p->fgrp->ref);
 	strecpy(p->text, p->text+sizeof p->text, name);
 
@@ -65,3 +63,30 @@
 	return p->pid;
 }
 
+void
+pexit(char *msg, int freemem)
+{
+	Proc *p = up;
+
+	USED(msg);
+	USED(freemem);
+
+	if(p->pgrp != nil){
+		closepgrp(p->pgrp);
+		p->pgrp = nil;
+	}
+	if(p->rgrp != nil){
+		closergrp(p->rgrp);
+		p->rgrp = nil;
+	}
+	if(p->fgrp != nil){
+		closefgrp(p->fgrp);
+		p->fgrp = nil;
+	}
+
+	cclose(p->dot);
+	cclose(p->slash);
+
+	free(p);
+	osexit();
+}
--- a/kern/win32.c
+++ b/kern/win32.c
@@ -92,7 +92,7 @@
 	_setproc(p);
 	op->tid = GetCurrentThreadId();
  	(*p->fn)(p->arg);
-	ExitThread(0);
+	pexit("", 0);
 	return 0;
 }
 
@@ -108,6 +108,12 @@
 }
 
 void
+osexit(void)
+{
+	ExitThread(0);
+}
+
+void
 procsleep(void)
 {
 	Proc *p;
@@ -251,14 +257,14 @@
 
 	warg = GetCommandLineW();
 	n = wcslen(warg)*UTFmax+1;
-	arg = malloc(n);
-	WideCharToMultiByte(CP_UTF8,0,warg,-1,arg,n,0,0);
+	arg = smalloc(n);
+	WideCharToMultiByte(CP_UTF8, 0, warg, -1, arg, n, 0, 0);
 
 	/* conservative guess at the number of args */
 	for(argc=4,p=arg; *p; p++)
 		if(*p == ' ' || *p == '\t')
 			argc++;
-	argv = malloc(argc*sizeof(char*));
+	argv = smalloc(argc*sizeof(char*));
 	argc = args(argv, argc, arg);
 
 	main(argc, argv);
@@ -266,7 +272,155 @@
 	return 0;
 }
 
+static char*
+qarg(char *s)
+{
+	char *d, *p;
+	int n, c;
+
+	n = strlen(s);
+	d = p = smalloc(3+2*n);
+	if(s[0] == '"' || (strchr(s, ' ') == nil && strchr(s, '\t') == nil)){
+		memmove(d, s, n+1);
+		return d;
+	}
+	*p++ = '"';
+	while((c = *s++) != 0){
+		if(c == '\\' || c == '"')
+			*p++ = '\\';
+		*p++ = c;
+	}
+	*p++ = '"';
+	*p = 0;
+	return d;
+}
+
+static wchar_t*
+wcmdline(char **argv)
+{
+	wchar_t *s, *w, *e;
+	int n, i;
+	char *q;
+
+	n = 0;
+	for(i = 0; argv[i] != nil; i++){
+		q = qarg(argv[i]);
+		n += strlen(q)+1;
+		free(q);
+	}
+	s = smalloc((n+1)*sizeof(wchar_t));
+	w = s;
+	e = s + n;
+	for(i = 0; argv[i] != nil; i++){
+		if(i != 0)
+			*w++ = L' ';
+		q = qarg(argv[i]);
+		w += MultiByteToWideChar(CP_UTF8, 0, q, strlen(argv[i]), w, e - w);
+		free(q);
+	}
+	*w = 0;
+	return s;
+}
+
+void*
+oscmd(char **argv, int nice, char *dir, Chan **fd)
+{
+	SECURITY_ATTRIBUTES sa;
+	PROCESS_INFORMATION pi;
+	STARTUPINFOW si;
+	HANDLE p[3][2], tmp;
+	wchar_t *wcmd, *wdir;
+	int i;
+
+	memset(&sa, 0, sizeof(sa));
+	sa.nLength = sizeof(sa);
+	sa.lpSecurityDescriptor = NULL;
+	sa.bInheritHandle = TRUE;
+
+	for(i = 0; i < 3; i++){
+		if(!CreatePipe(&p[i][i==0], &p[i][i!=0], &sa, 0)
+		|| !DuplicateHandle(GetCurrentProcess(), p[i][0], GetCurrentProcess(), &tmp, 0, FALSE, DUPLICATE_SAME_ACCESS)){
+			while(--i >= 0){
+				CloseHandle(p[i][0]);
+				CloseHandle(p[i][1]);
+			}
+			oserror();
+		}
+		CloseHandle(p[i][0]);
+		p[i][0] = tmp;
+	}
+
+	if(waserror()){
+		for(i = 0; i < 3; i++){
+			CloseHandle(p[i][0]);
+			CloseHandle(p[i][1]);
+		}
+		nexterror();
+	}
+
+	memset(&pi, 0, sizeof(pi));
+	memset(&si, 0, sizeof(si));
+	si.cb = sizeof(si);
+	si.dwFlags = STARTF_USESTDHANDLES;
+	si.hStdInput = p[0][1];
+	si.hStdOutput = p[1][1];
+	si.hStdError = p[2][1];
+	si.lpDesktop = L"";
+
+	i = strlen(dir)+1;
+	wdir = smalloc(i*sizeof(wchar_t));
+	MultiByteToWideChar(CP_UTF8, 0, dir, i, wdir, i);
+
+	wcmd = wcmdline(argv);
+	if(waserror()){
+		free(wcmd);
+		nexterror();
+	}
+
+	if(!CreateProcessW(NULL, wcmd, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW, NULL, wdir, &si, &pi))
+		oserror();
+
+	poperror();
+	free(wcmd);
+	free(wdir);
+
+	poperror();
+	for(i = 0; i < 3; i++){
+		fd[i] = lfdchan((void*)p[i][0]);
+		CloseHandle(p[i][1]);
+	}
+	CloseHandle(pi.hThread);
+	return (void*)pi.hProcess;
+}
+
+int
+oscmdwait(void *c, char *status, int nstatus)
+{
+	DWORD code = -1;
+	for(;;){
+		if(!GetExitCodeProcess((HANDLE)c, &code))
+			return -1;
+		if(code != STILL_ACTIVE)
+			break;
+		WaitForSingleObject((HANDLE)c, INFINITE);
+	}
+	return snprint(status, nstatus, "%d", (int)code);
+}
+
+int
+oscmdkill(void *c)
+{
+	TerminateProcess((HANDLE)c, 0);
+	return 0;
+}
+
 void
+oscmdfree(void *c)
+{
+	CloseHandle((HANDLE)c);
+}
+
+void
 oserrstr(void)
 {
 	char *p, *q;
@@ -296,9 +450,7 @@
 	wchar_t *action, *arg, *cmd, *p;
 	int m;
 
-	cmd = malloc((n+1)*sizeof(wchar_t));
-	if(cmd == nil)
-		error("out of memory");
+	cmd = smalloc((n+1)*sizeof(wchar_t));
 	m = MultiByteToWideChar(CP_UTF8,0,a,n,cmd,n);
 	while(m > 0 && cmd[m-1] == '\n')
 		m--;
--- a/main.c
+++ b/main.c
@@ -52,6 +52,7 @@
 	if(bind("#U", "/root", MREPL) < 0)
 		panic("bind #U: %r");
 	bind("#A", "/dev", MAFTER);
+	bind("#C", "/", MAFTER);
 
 	if(open("/dev/cons", OREAD) != 0)
 		panic("open0: %r");