plan 9 kernel history: overview | file list | diff list

1991/0913/port/devcons.c (diff list | history)

1991/0913/sys/src/9/port/devcons.c:1,7871991/0920/sys/src/9/port/devcons.c:1,787 (short | long | prev | next)
1990/0227    
#include	"u.h" 
#include	"lib.h" 
#include	"mem.h" 
#include	"dat.h" 
#include	"fns.h" 
#include	"io.h" 
#include	"errno.h" 
 
#include	"devtab.h" 
 
1991/0607    
struct { 
	IOQ;			/* lock to klogputs */ 
	QLock;			/* qlock to getc */ 
1991/0723    
}klogq; 
1990/0227    
 
1991/0607    
IOQ	lineq;			/* lock to getc; interrupt putc's */ 
IOQ	printq; 
IOQ	mouseq; 
KIOQ	kbdq; 
1990/0227    
 
1991/0607    
Ref	raw;		/* whether kbd i/o is raw (rcons is open) */ 
1990/0227    
 
/* 
1991/0607    
 *  init the queues and set the output routine 
1990/0227    
 */ 
void 
1991/0607    
printinit(void) 
1990/0227    
{ 
1991/0607    
	initq(&printq); 
1991/0608    
	printq.puts = 0; 
1991/0607    
	initq(&lineq); 
	initq(&kbdq); 
	kbdq.putc = kbdputc; 
	initq(&klogq); 
	initq(&mouseq); 
	mouseq.putc = mouseputc; 
1990/0227    
} 
 
/* 
1991/0809    
 *   Print a string on the console.  Convert \n to \r\n for serial 
 *   line consoles.  Locking of the queues is left up to the screen 
 *   or uart code.  Multi-line messages to serial consoles may get 
 *   interspersed with other messages. 
1990/0227    
 */ 
void 
putstrn(char *str, int n) 
{ 
1991/0809    
	char buf[PRINTSIZE+2]; 
	int m; 
1990/0227    
	char *t; 
 
1991/0809    
	/* 
	 *  if there's an attached bit mapped display, 
	 *  put the message there.  screenputs is defined 
	 *  as a null macro for systems that have no such 
	 *  display. 
	 */ 
	screenputs(str, n); 
 
	/* 
	 *  if there's a serial line being used as a console, 
	 *  put the message there.  Tack a carriage return 
	 *  before new lines. 
	 */ 
	if(printq.puts == 0) 
		return; 
1990/0227    
	while(n > 0){ 
1991/0809    
		t = memchr(str, '\n', n); 
		if(t){ 
			m = t - str; 
			memmove(buf, str, m); 
			buf[m] = '\r'; 
			buf[m+1] = '\n'; 
			(*printq.puts)(&printq, buf, m+2); 
			str = t + 1; 
			n -= m + 1; 
		} else { 
			(*printq.puts)(&printq, str, n); 
			break; 
		} 
1990/0227    
	} 
} 
 
1991/0607    
/* 
 *   Print a string in the kernel log.  Ignore overflow. 
 */ 
void 
klogputs(char *str, long n) 
1990/0227    
{ 
1991/0607    
	int s, m; 
	uchar *nextin; 
 
	s = splhi(); 
	lock(&klogq); 
	while(n){ 
		m = &klogq.buf[NQ] - klogq.in; 
		if(m > n) 
			m = n; 
		memmove(klogq.in, str, m); 
		n -= m; 
		str += m; 
		nextin = klogq.in + m; 
		if(nextin >= &klogq.buf[NQ]) 
			klogq.in = klogq.buf; 
		else 
			klogq.in = nextin; 
	} 
	unlock(&klogq); 
	splx(s); 
	wakeup(&klogq.r); 
1990/0227    
} 
 
int 
1991/0607    
isbrkc(KIOQ *q) 
1990/0227    
{ 
	uchar *p; 
 
	for(p=q->out; p!=q->in; ){ 
1991/0607    
		if(raw.ref) 
			return 1; 
1990/0227    
		if(*p==0x04 || *p=='\n') 
			return 1; 
		p++; 
		if(p >= q->buf+sizeof(q->buf)) 
			p = q->buf; 
	} 
	return 0; 
} 
 
int 
sprint(char *s, char *fmt, ...) 
{ 
1990/06111    
	return doprint(s, s+PRINTSIZE, fmt, (&fmt+1)) - s; 
1990/0227    
} 
 
int 
print(char *fmt, ...) 
{ 
	char buf[PRINTSIZE]; 
	int n; 
 
1990/06111    
	n = doprint(buf, buf+sizeof(buf), fmt, (&fmt+1)) - buf; 
1990/0227    
	putstrn(buf, n); 
	return n; 
} 
 
1991/0607    
int 
kprint(char *fmt, ...) 
{ 
	char buf[PRINTSIZE]; 
	int n; 
 
	n = doprint(buf, buf+sizeof(buf), fmt, (&fmt+1)) - buf; 
	klogputs(buf, n); 
	return n; 
} 
 
1990/0227    
void 
panic(char *fmt, ...) 
{ 
	char buf[PRINTSIZE]; 
	int n; 
 
1991/0911    
	strcpy(buf, "inconceivable: "); 
1990/06111    
	n = doprint(buf+7, buf+sizeof(buf), fmt, (&fmt+1)) - buf; 
1991/0920    
	n = doprint(buf+strlen(buf), buf+sizeof(buf), fmt, (&fmt+1)) - buf; 
1990/0227    
	buf[n] = '\n'; 
	putstrn(buf, n+1); 
1990/0907    
	dumpstack(); 
1991/0608    
	if(conf.cntrlp) 
		exit(); 
	else 
		for(;;); 
1990/0227    
} 
 
int 
pprint(char *fmt, ...) 
{ 
	char buf[2*PRINTSIZE]; 
	Chan *c; 
	int n; 
 
1991/0720    
	if(u->p->fgrp == 0) 
		return 0; 
 
1991/0705    
	c = u->p->fgrp->fd[2]; 
1990/0227    
	if(c==0 || (c->mode!=OWRITE && c->mode!=ORDWR)) 
1990/0321    
		return 0; 
1990/0227    
	n = sprint(buf, "%s %d: ", u->p->text, u->p->pid); 
1990/06111    
	n = doprint(buf+n, buf+sizeof(buf), fmt, (&fmt+1)) - buf; 
1991/0411    
	qlock(&c->wrl); 
1990/0227    
	if(waserror()){ 
1991/0411    
		qunlock(&c->wrl); 
1990/0321    
		return 0; 
1990/0227    
	} 
1991/0411    
	(*devtab[c->type].write)(c, buf, n, c->offset); 
1990/0227    
	c->offset += n; 
1991/0411    
	qunlock(&c->wrl); 
1990/0321    
	poperror(); 
1990/0227    
	return n; 
} 
 
void 
prflush(void) 
{ 
1991/0607    
	while(printq.in != printq.out) ; 
1990/0227    
} 
 
void 
echo(int c) 
{ 
	char ch; 
1991/0607    
	static int ctrlt; 
1990/03091    
 
1990/0227    
	/* 
1991/0607    
	 * ^p hack 
1990/0227    
	 */ 
1991/0608    
	if(c==0x10 && conf.cntrlp) 
1991/0327    
		panic("^p"); 
1991/0608    
 
1991/0607    
	/* 
	 * ^t hack BUG 
	 */ 
	if(ctrlt == 2){ 
		ctrlt = 0; 
		switch(c){ 
		case 0x14: 
			break;	/* pass it on */ 
		case 'm': 
			mntdump(); 
			return; 
		case 'p': 
1991/0608    
			procdump(); 
1991/0607    
			return; 
		case 'q': 
			dumpqueues(); 
			return; 
		case 'r': 
			exit(); 
			break; 
		} 
	}else if(c == 0x14){ 
		ctrlt++; 
		return; 
	} 
	ctrlt = 0; 
	if(raw.ref) 
		return; 
1990/0227    
	if(c == 0x15) 
		putstrn("^U\n", 3); 
	else{ 
		ch = c; 
		putstrn(&ch, 1); 
	} 
} 
 
/* 
1991/0727    
 * turn '\r' into '\n' before putting it into the queue 
 */ 
int 
kbdcr2nl(IOQ *q, int ch) 
{ 
	if(ch == '\r') 
		ch = '\n'; 
	return kbdputc(q, ch); 
} 
 
/* 
1990/0227    
 * Put character into read queue at interrupt time. 
 * Always called splhi from proc 0. 
 */ 
1991/0607    
int 
kbdputc(IOQ *q, int ch) 
1990/0227    
{ 
1991/0607    
	echo(ch); 
	kbdq.c = ch; 
	*kbdq.in++ = ch; 
1990/0227    
	if(kbdq.in == kbdq.buf+sizeof(kbdq.buf)) 
		kbdq.in = kbdq.buf; 
1991/0607    
	if(raw.ref || ch=='\n' || ch==0x04) 
1990/0227    
		wakeup(&kbdq.r); 
1991/0607    
	return 0; 
1990/0227    
} 
 
void 
1991/0607    
kbdrepeat(int rep) 
1990/0227    
{ 
1991/0607    
	kbdq.repeat = rep; 
	kbdq.count = 0; 
} 
1990/0227    
 
1991/0607    
void 
kbdclock(void) 
{ 
	if(kbdq.repeat == 0) 
		return; 
	if(kbdq.repeat==1 && ++kbdq.count>HZ){ 
		kbdq.repeat = 2; 
		kbdq.count = 0; 
		return; 
1990/0227    
	} 
1991/0607    
	if(++kbdq.count&1) 
		kbdputc(&kbdq, kbdq.c); 
1990/0227    
} 
 
int 
consactive(void) 
{ 
	return printq.in != printq.out; 
} 
 
/* 
 * I/O interface 
 */ 
enum{ 
	Qdir, 
	Qcons, 
	Qcputime, 
1991/0607    
	Qlights, 
	Qnoise, 
1990/0227    
	Qnull, 
	Qpgrpid, 
	Qpid, 
	Qppid, 
1991/0607    
	Qrcons, 
1990/0227    
	Qtime, 
	Quser, 
1991/0607    
	Qklog, 
	Qmsec, 
	Qclock, 
1991/0425    
	Qsysstat, 
1991/0705    
	Qswap, 
1990/0227    
}; 
 
Dirtab consdir[]={ 
1991/0607    
	"cons",		{Qcons},	0,		0600, 
	"cputime",	{Qcputime},	6*NUMSIZE,	0600, 
	"lights",	{Qlights},	0,		0600, 
	"noise",	{Qnoise},	0,		0600, 
	"null",		{Qnull},	0,		0600, 
	"pgrpid",	{Qpgrpid},	NUMSIZE,	0600, 
	"pid",		{Qpid},		NUMSIZE,	0600, 
	"ppid",		{Qppid},	NUMSIZE,	0600, 
	"rcons",	{Qrcons},	0,		0600, 
	"time",		{Qtime},	NUMSIZE,	0600, 
	"user",		{Quser},	0,		0600, 
	"klog",		{Qklog},	0,		0400, 
	"msec",		{Qmsec},	NUMSIZE,	0400, 
	"clock",	{Qclock},	2*NUMSIZE,	0400, 
	"sysstat",	{Qsysstat},	0,		0600, 
1991/0705    
	"swap",		{Qswap},	0,		0666, 
1990/0227    
}; 
 
#define	NCONS	(sizeof consdir/sizeof(Dirtab)) 
 
ulong	boottime;		/* seconds since epoch at boot */ 
 
long 
seconds(void) 
{ 
1990/0620    
	return boottime + TK2SEC(MACHP(0)->ticks); 
1990/0227    
} 
 
int 
readnum(ulong off, char *buf, ulong n, ulong val, int size) 
{ 
	char tmp[64]; 
	Op op = (Op){ tmp, tmp+sizeof(tmp), &val, size-1, 0, FUNSIGN|FLONG }; 
 
	numbconv(&op, 10); 
	tmp[size-1] = ' '; 
1990/0312    
	if(off >= size) 
		return 0; 
1990/0227    
	if(off+n > size) 
		n = size-off; 
1991/0318    
	memmove(buf, tmp+off, n); 
1990/0227    
	return n; 
} 
 
int 
readstr(ulong off, char *buf, ulong n, char *str) 
{ 
	int size; 
 
	size = strlen(str); 
1990/0312    
	if(off >= size) 
		return 0; 
1990/0227    
	if(off+n > size) 
		n = size-off; 
1991/0318    
	memmove(buf, str+off, n); 
1990/0227    
	return n; 
} 
 
void 
consreset(void) 
{ 
} 
 
void 
consinit(void) 
{ 
} 
 
Chan* 
consattach(char *spec) 
{ 
	return devattach('c', spec); 
} 
 
Chan* 
consclone(Chan *c, Chan *nc) 
{ 
	return devclone(c, nc); 
} 
 
int 
conswalk(Chan *c, char *name) 
{ 
	return devwalk(c, name, consdir, NCONS, devgen); 
} 
 
void 
consstat(Chan *c, char *dp) 
{ 
	devstat(c, dp, consdir, NCONS, devgen); 
} 
 
Chan* 
consopen(Chan *c, int omode) 
{ 
1991/0607    
	int ch; 
 
	switch(c->qid.path){ 
	case Quser: 
		if(omode==(OWRITE|OTRUNC)){ 
			/* truncate? */ 
			if(strcmp(u->p->pgrp->user, "bootes") == 0)	/* BUG */ 
				u->p->pgrp->user[0] = 0; 
			else 
				error(Eperm); 
		} 
		break; 
	case Qrcons: 
1991/0620    
		if(conf.cntrlp) 
			error(Eperm); 
1991/0607    
		if(incref(&raw) == 1){ 
			lock(&lineq); 
			while((ch=getc(&kbdq)) != -1){ 
				*lineq.in++ = ch; 
				if(lineq.in == lineq.buf+sizeof(lineq.buf)) 
					lineq.in = lineq.buf; 
			} 
			unlock(&lineq); 
		} 
		break; 
1991/0720    
	case Qswap: 
		kickpager();		/* start a pager if not already started */ 
		break; 
1990/0227    
	} 
	return devopen(c, omode, consdir, NCONS, devgen); 
} 
 
void 
conscreate(Chan *c, char *name, int omode, ulong perm) 
{ 
1990/11211    
	error(Eperm); 
1990/0227    
} 
 
void 
consclose(Chan *c) 
{ 
1991/0607    
	if(c->qid.path==Qrcons && (c->flag&COPEN)) 
		decref(&raw); 
1990/0227    
} 
 
1991/0607    
 
1990/0227    
long 
1991/0411    
consread(Chan *c, void *buf, long n, ulong offset) 
1990/0227    
{ 
1991/0425    
	int ch, i, j, k, id; 
1990/0227    
	ulong l; 
	uchar *out; 
	char *cbuf = buf; 
	char *user; 
	int userlen; 
1991/0607    
	char tmp[6*NUMSIZE], xbuf[1024]; 
1991/0425    
	Mach *mp; 
1990/0227    
 
	if(n <= 0) 
		return n; 
1990/11211    
	switch(c->qid.path & ~CHDIR){ 
1990/0227    
	case Qdir: 
		return devdirread(c, buf, n, consdir, NCONS, devgen); 
 
1991/0607    
	case Qrcons: 
1990/0227    
	case Qcons: 
		qlock(&kbdq); 
1990/0617    
		if(waserror()){ 
			qunlock(&kbdq); 
			nexterror(); 
		} 
1990/0227    
		while(!cangetc(&lineq)){ 
1991/0607    
			sleep(&kbdq.r, isbrkc, &kbdq); 
1990/0227    
			do{ 
1991/0607    
				lock(&lineq); 
1990/0227    
				ch = getc(&kbdq); 
1991/0607    
				if(raw.ref){ 
					unlock(&lineq); 
					goto Default; 
				} 
1990/0227    
				switch(ch){ 
				case '\b': 
					if(lineq.in != lineq.out){ 
						if(lineq.in == lineq.buf) 
							lineq.in = lineq.buf+sizeof(lineq.buf); 
						lineq.in--; 
					} 
					break; 
				case 0x15: 
					lineq.in = lineq.out; 
					break; 
1991/0607    
				Default: 
1990/0227    
				default: 
1991/0607    
					*lineq.in = ch; 
					if(lineq.in >= lineq.buf+sizeof(lineq.buf)-1) 
						lineq.in = lineq.buf; 
					else 
						lineq.in++; 
1990/0227    
				} 
1991/0607    
				unlock(&lineq); 
			}while(raw.ref==0 && ch!='\n' && ch!=0x04); 
1990/0227    
		} 
		i = 0; 
1991/0607    
		while(n > 0){ 
1990/0227    
			ch = getc(&lineq); 
1991/0607    
			if(ch==-1 || (raw.ref==0 && ch==0x04)) 
1990/0227    
				break; 
			i++; 
			*cbuf++ = ch; 
			--n; 
		} 
1991/0614    
		poperror(); 
1990/0227    
		qunlock(&kbdq); 
		return i; 
 
	case Qcputime: 
1991/0411    
		k = offset; 
1990/0312    
		if(k >= sizeof tmp) 
			return 0; 
1990/0227    
		if(k+n > sizeof tmp) 
			n = sizeof tmp - k; 
		/* easiest to format in a separate buffer and copy out */ 
		for(i=0; i<6 && NUMSIZE*i<k+n; i++){ 
			l = u->p->time[i]; 
			if(i == TReal) 
				l = MACHP(0)->ticks - l; 
1990/0614    
			l = TK2MS(l); 
1990/0227    
			readnum(0, tmp+NUMSIZE*i, NUMSIZE, l, NUMSIZE); 
		} 
1991/0318    
		memmove(buf, tmp+k, n); 
1990/0227    
		return n; 
 
	case Qpgrpid: 
1991/0411    
		return readnum(offset, buf, n, u->p->pgrp->pgrpid, NUMSIZE); 
1990/0227    
 
	case Qpid: 
1991/0411    
		return readnum(offset, buf, n, u->p->pid, NUMSIZE); 
1990/0227    
 
	case Qppid: 
1991/0411    
		return readnum(offset, buf, n, u->p->parentpid, NUMSIZE); 
1990/0227    
 
	case Qtime: 
1991/0411    
		return readnum(offset, buf, n, boottime+TK2SEC(MACHP(0)->ticks), 12); 
1990/0227    
 
1991/0607    
	case Qclock: 
		k = offset; 
		if(k >= 2*NUMSIZE) 
			return 0; 
		if(k+n > 2*NUMSIZE) 
			n = 2*NUMSIZE - k; 
		readnum(0, tmp, NUMSIZE, MACHP(0)->ticks, NUMSIZE); 
		readnum(0, tmp+NUMSIZE, NUMSIZE, HZ, NUMSIZE); 
		memmove(buf, tmp+k, n); 
		return n; 
 
1990/0227    
	case Quser: 
1991/0411    
		return readstr(offset, buf, n, u->p->pgrp->user); 
1990/0227    
 
1991/0607    
	case Qnull: 
		return 0; 
1990/0720    
 
1991/0607    
	case Qklog: 
		qlock(&klogq); 
		if(waserror()){ 
			qunlock(&klogq); 
			nexterror(); 
		} 
		while(!cangetc(&klogq)) 
			sleep(&klogq.r, cangetc, &klogq); 
		for(i=0; i<n; i++){ 
			if((ch=getc(&klogq)) == -1) 
				break; 
			*cbuf++ = ch; 
		} 
		poperror(); 
		qunlock(&klogq); 
		return i; 
 
	case Qmsec: 
		return readnum(offset, buf, n, TK2MS(MACHP(0)->ticks), NUMSIZE); 
 
1991/0425    
	case Qsysstat: 
		j = 0; 
		for(id = 0; id < 32; id++) { 
			if(active.machs & (1<<id)) { 
				mp = MACHP(id); 
				j += sprint(&xbuf[j], "%d %d %d %d %d %d %d %d\n", 
					id, mp->cs, mp->intr, mp->syscall, mp->pfault, 
1991/0607    
					    mp->tlbfault, mp->tlbpurge, m->spinlock); 
1991/0425    
			} 
		} 
		return readstr(offset, buf, n, xbuf); 
1990/0227    
 
1991/0705    
	case Qswap: 
		sprint(xbuf, "%d/%d memory %d/%d swap\n", 
				palloc.user-palloc.freecount, palloc.user,  
				conf.nswap-swapalloc.free, conf.nswap); 
 
		return readstr(offset, buf, n, xbuf); 
1991/0913    
	case Qlights: 
		errors("write only"); 
1990/0227    
	default: 
		panic("consread %lux\n", c->qid); 
		return 0; 
	} 
} 
 
1991/0607    
void 
conslights(char *a, int n) 
{ 
	int l; 
	char line[128]; 
	char *lp; 
	int c; 
 
	lp = line; 
	while(n--){ 
		*lp++ = c = *a++; 
		if(c=='\n' || n==0 || lp==&line[sizeof(line)-1]) 
			break; 
	} 
	*lp = 0; 
	lights(strtoul(line, 0, 0)); 
} 
 
void 
consnoise(char *a, int n) 
{ 
	int freq; 
	int duration; 
	char line[128]; 
	char *lp; 
	int c; 
 
	lp = line; 
	while(n--){ 
		*lp++ = c = *a++; 
		if(c=='\n' || n==0 || lp==&line[sizeof(line)-1]){ 
			*lp = 0; 
			freq = strtoul(line, &lp, 0); 
			while(*lp==' ' || *lp=='\t') 
				lp++; 
			duration = strtoul(lp, &lp, 0); 
			buzz(freq, duration); 
			lp = line; 
		} 
	} 
} 
 
1990/0227    
long 
1991/0411    
conswrite(Chan *c, void *va, long n, ulong offset) 
1990/0227    
{ 
	char cbuf[64]; 
	char buf[256]; 
	long l, m; 
	char *a = va; 
1991/0425    
	Mach *mp; 
1991/0705    
	int id, fd; 
	Chan *swc; 
1990/0227    
 
1990/11211    
	switch(c->qid.path){ 
1991/0607    
	case Qrcons: 
1990/0227    
	case Qcons: 
		/* 
		 * Damn. Can't page fault in putstrn, so copy the data locally. 
		 */ 
		l = n; 
		while(l > 0){ 
			m = l; 
			if(m > sizeof buf) 
				m = sizeof buf; 
1991/0318    
			memmove(buf, a, m); 
1990/0227    
			putstrn(a, m); 
			a += m; 
			l -= m; 
		} 
		break; 
 
	case Qtime: 
		if(n<=0 || boottime!=0)	/* only one write please */ 
			return 0; 
		if(n >= sizeof cbuf) 
			n = sizeof cbuf - 1; 
1991/0318    
		memmove(cbuf, a, n); 
1990/0227    
		cbuf[n-1] = 0; 
		boottime = strtoul(a, 0, 0); 
		break; 
 
	case Quser: 
		if(u->p->pgrp->user[0])		/* trying to overwrite /dev/user */ 
1990/11211    
			error(Eperm); 
1991/0411    
		if(offset >= NAMELEN-1) 
1990/0227    
			return 0; 
1991/0411    
		if(offset+n >= NAMELEN-1) 
			n = NAMELEN-1 - offset; 
		memmove(u->p->pgrp->user+offset, a, n); 
		u->p->pgrp->user[offset+n] = 0; 
1990/0227    
		break; 
 
	case Qcputime: 
	case Qpgrpid: 
	case Qpid: 
	case Qppid: 
1990/11211    
		error(Eperm); 
1990/0227    
 
	case Qnull: 
		break; 
1990/11161    
 
1991/0607    
	case Qnoise: 
		consnoise(a, n); 
1990/11161    
		break; 
1991/0607    
 
	case Qlights: 
		conslights(a, n); 
		break; 
 
1991/0425    
	case Qsysstat: 
		for(id = 0; id < 32; id++) { 
			if(active.machs & (1<<id)) { 
				mp = MACHP(id); 
				mp->cs = 0; 
				mp->intr = 0; 
				mp->syscall = 0; 
				mp->pfault = 0; 
				mp->tlbfault = 0; 
				mp->tlbpurge = 0; 
				mp->spinlock = 0; 
			} 
		} 
		break; 
1991/0705    
 
	case Qswap: 
		if(n >= sizeof buf) 
			error(Egreg); 
		memmove(buf, va, n);	/* so we can NUL-terminate */ 
		buf[n] = 0; 
		fd = strtoul(buf, 0, 0); 
		swc = fdtochan(fd, -1); 
		setswapchan(swc); 
		return n; 
1991/0607    
 
1990/0227    
	default: 
1990/11211    
		error(Egreg); 
1990/0227    
	} 
	return n; 
} 
 
void 
consremove(Chan *c) 
{ 
1990/11211    
	error(Eperm); 
1990/0227    
} 
 
void 
conswstat(Chan *c, char *dp) 
{ 
1990/11211    
	error(Eperm); 
1990/0227    
} 


source code copyright © 1990-2005 Lucent Technologies; see license
Plan 9 distribution
comments to russ cox (rsc@swtch.com)