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

1994/0619/port/sysproc.c (diff list | history)

1994/0619/sys/src/9/port/sysproc.c:1,6651994/0727/sys/src/9/port/sysproc.c:1,670 (short | long | prev | next)
1990/0227    
#include	"u.h" 
1992/0321    
#include	"../port/lib.h" 
1990/0227    
#include	"mem.h" 
#include	"dat.h" 
#include	"fns.h" 
1992/0111    
#include	"../port/error.h" 
1990/0227    
 
1990/0330    
#include	<a.out.h> 
1990/0227    
 
int	shargs(char*, int, char**); 
 
long 
sysr1(ulong *arg) 
{ 
1993/1004    
	print("[%s %s %d] r1 = %d\n", up->user, up->text, up->pid, arg[0]); 
1990/0227    
	return 0; 
} 
 
long 
1991/0807    
sysrfork(ulong *arg) 
{ 
1993/0501    
	Proc *p; 
	int n, i; 
1992/0428    
	Pgrp *opg; 
	Egrp *oeg; 
	Fgrp *ofg; 
1993/0501    
	ulong pid, flag; 
1991/0709    
 
1992/1206    
	flag = arg[0]; 
1992/0501    
	if((flag&RFPROC) == 0) { 
		if(flag & (RFNAMEG|RFCNAMEG)) { 
			if((flag & (RFNAMEG|RFCNAMEG)) == (RFNAMEG|RFCNAMEG)) 
1992/0428    
				error(Ebadarg); 
1993/0501    
			opg = up->pgrp; 
			up->pgrp = newpgrp(); 
1992/0501    
			if(flag & RFNAMEG) 
1993/0501    
				pgrpcpy(up->pgrp, opg); 
1992/0428    
			closepgrp(opg); 
		} 
1992/0501    
		if(flag & (RFENVG|RFCENVG)) { 
			if((flag & (RFENVG|RFCENVG)) == (RFENVG|RFCENVG)) 
1992/0428    
				error(Ebadarg); 
1993/0501    
			oeg = up->egrp; 
			up->egrp = smalloc(sizeof(Egrp)); 
			up->egrp->ref = 1; 
1992/0501    
			if(flag & RFENVG) 
1993/0501    
				envcpy(up->egrp, oeg); 
1992/0428    
			closeegrp(oeg); 
		} 
1992/0607    
		if(flag & RFFDG) { 
1993/0501    
			ofg = up->fgrp; 
			up->fgrp = dupfgrp(ofg); 
1992/0607    
			closefgrp(ofg); 
		} 
		else 
1992/0501    
		if(flag & RFCFDG) { 
1993/0501    
			ofg = up->fgrp; 
			up->fgrp = smalloc(sizeof(Fgrp)); 
			up->fgrp->ref = 1; 
1992/0428    
			closefgrp(ofg); 
		} 
1992/0501    
		if(flag & RFNOTEG) 
1993/0501    
			up->noteid = incref(¬eidalloc); 
1992/0501    
		if(flag & (RFMEM|RFNOWAIT)) 
1992/0428    
			error(Ebadarg); 
		return 0; 
	} 
1992/0430    
	/* Check flags before we commit */ 
1992/0501    
	if((flag & (RFFDG|RFCFDG)) == (RFFDG|RFCFDG)) 
1992/0430    
		error(Ebadarg); 
1992/0501    
	if((flag & (RFNAMEG|RFCNAMEG)) == (RFNAMEG|RFCNAMEG)) 
1992/0430    
		error(Ebadarg); 
1992/0501    
	if((flag & (RFENVG|RFCENVG)) == (RFENVG|RFCENVG)) 
1992/0430    
		error(Ebadarg); 
1992/0428    
 
1990/0227    
	p = newproc(); 
1991/0705    
 
1993/0501    
	p->fpsave = up->fpsave; 
	p->scallnr = up->scallnr; 
	p->s = up->s; 
	p->nerrlab = 0; 
	p->slash = up->slash; 
	p->dot = up->dot; 
	incref(p->dot);	 
1990/0614    
 
1993/0501    
	memmove(p->note, up->note, sizeof(p->note)); 
	p->nnote = up->nnote; 
	p->notified = 0; 
	p->lastnote = up->lastnote; 
	p->notify = up->notify; 
	p->ureg = 0; 
	p->dbgreg = 0; 
1990/0227    
 
1991/0705    
	/* Make a new set of memory segments */ 
1992/0501    
	n = flag & RFMEM; 
1991/0705    
	for(i = 0; i < NSEG; i++) 
1993/0501    
		if(up->seg[i]) 
			p->seg[i] = dupseg(up->seg, i, n); 
1991/0705    
 
1992/0428    
	/* File descriptors */ 
1992/0501    
	if(flag & (RFFDG|RFCFDG)) { 
		if(flag & RFFDG) 
1993/0501    
			p->fgrp = dupfgrp(up->fgrp); 
1992/0623    
		else { 
			p->fgrp = smalloc(sizeof(Fgrp)); 
			p->fgrp->ref = 1; 
		} 
1992/0428    
	} 
	else { 
1993/0501    
		p->fgrp = up->fgrp; 
1991/0807    
		incref(p->fgrp); 
	} 
1991/0705    
 
1992/0428    
	/* Process groups */ 
1992/0501    
	if(flag & (RFNAMEG|RFCNAMEG)) {	 
1992/0928    
		p->pgrp = newpgrp(); 
		if(flag & RFNAMEG) 
1993/0501    
			pgrpcpy(p->pgrp, up->pgrp); 
1991/0807    
	} 
1991/0808    
	else { 
1993/0501    
		p->pgrp = up->pgrp; 
1991/0808    
		incref(p->pgrp); 
	} 
1991/0705    
 
1992/0428    
	/* Environment group */ 
1992/0501    
	if(flag & (RFENVG|RFCENVG)) { 
1992/0623    
		p->egrp = smalloc(sizeof(Egrp)); 
		p->egrp->ref = 1; 
		if(flag & RFENVG) 
1993/0501    
			envcpy(p->egrp, up->egrp); 
1991/0807    
	} 
1991/0808    
	else { 
1993/0501    
		p->egrp = up->egrp; 
1991/0808    
		incref(p->egrp); 
	} 
1993/0501    
	p->hang = up->hang; 
	p->procmode = up->procmode; 
1991/1110    
 
1993/0501    
	/* Craft a return frame which will cause the child to pop out of 
	 * the scheduler in user mode with the return register zero 
	 */ 
	forkchild(p, up->dbgreg); 
	 
	p->parent = up; 
	p->parentpid = up->pid; 
1992/0501    
	if(flag&RFNOWAIT) 
1992/0430    
		p->parentpid = 1; 
1992/0715    
	else { 
1993/0501    
		lock(&up->exl); 
		up->nchild++; 
		unlock(&up->exl); 
1992/0715    
	} 
1992/0501    
	if((flag&RFNOTEG) == 0) 
1993/0501    
		p->noteid = up->noteid; 
1991/0705    
 
1993/0501    
	p->fpstate = up->fpstate; 
1990/0227    
	pid = p->pid; 
	memset(p->time, 0, sizeof(p->time)); 
	p->time[TReal] = MACHP(0)->ticks; 
1993/0501    
	memmove(p->text, up->text, NAMELEN); 
	memmove(p->user, up->user, NAMELEN); 
1991/0529    
	/* 
	 *  since the bss/data segments are now shareable, 
	 *  any mmu info about this process is now stale 
	 *  (i.e. has bad properties) and has to be discarded. 
	 */ 
1990/0227    
	flushmmu(); 
1991/0430    
	ready(p); 
1990/0227    
	return pid; 
} 
 
1991/0723    
static ulong 
l2be(long l) 
{ 
	uchar *cp; 
 
	cp = (uchar*)&l; 
	return (cp[0]<<24) | (cp[1]<<16) | (cp[2]<<8) | cp[3]; 
} 
 
1990/0227    
long 
sysexec(ulong *arg) 
{ 
1991/0705    
	Segment *s, *ts; 
1992/0711    
	ulong t, d, b; 
1990/0227    
	int i; 
1992/0711    
	Chan *tc; 
1990/0227    
	char **argv, **argp; 
	char *a, *charp, *file; 
	char *progarg[sizeof(Exec)/2+1], elem[NAMELEN]; 
1991/0523    
	ulong ssize, spage, nargs, nbytes, n, bssend; 
1990/0227    
	int indir; 
	Exec exec; 
	char line[sizeof(Exec)]; 
1991/0705    
	Fgrp *f; 
	Image *img; 
1991/0723    
	ulong magic, text, entry, data, bss; 
1990/0227    
 
	validaddr(arg[0], 1, 0); 
	file = (char*)arg[0]; 
	indir = 0; 
    Header: 
	tc = namec(file, Aopen, OEXEC, 0); 
	if(waserror()){ 
		close(tc); 
		nexterror(); 
	} 
	if(!indir) 
1993/0501    
		strcpy(elem, up->elem); 
1991/0706    
 
1991/0411    
	n = (*devtab[tc->type].read)(tc, &exec, sizeof(Exec), 0); 
1990/0227    
	if(n < 2) 
    Err: 
1990/11211    
		error(Ebadexec); 
1991/0723    
	magic = l2be(exec.magic); 
	text = l2be(exec.text); 
	entry = l2be(exec.entry); 
1994/0619    
	if(n==sizeof(Exec) && (magic == AOUT_MAGIC)){ 
1991/0723    
		if((text&KZERO) 
		|| entry < UTZERO+sizeof(Exec) 
		|| entry >= UTZERO+sizeof(Exec)+text) 
1990/0227    
			goto Err; 
		goto Binary; 
	} 
 
	/* 
	 * Process #! /bin/sh args ... 
	 */ 
1991/0318    
	memmove(line, &exec, sizeof(Exec)); 
1990/0227    
	if(indir || line[0]!='#' || line[1]!='!') 
		goto Err; 
	n = shargs(line, n, progarg); 
	if(n == 0) 
		goto Err; 
	indir = 1; 
	/* 
	 * First arg becomes complete file name 
	 */ 
	progarg[n++] = file; 
	progarg[n] = 0; 
	validaddr(arg[1], BY2WD, 1); 
	arg[1] += BY2WD; 
	file = progarg[0]; 
	progarg[0] = elem; 
	poperror(); 
1991/0614    
	close(tc); 
1990/0227    
	goto Header; 
 
    Binary: 
1991/0705    
	poperror(); 
1991/0723    
	data = l2be(exec.data); 
	bss = l2be(exec.bss); 
	t = (UTZERO+sizeof(Exec)+text+(BY2PG-1)) & ~(BY2PG-1); 
	d = (t + data + (BY2PG-1)) & ~(BY2PG-1); 
	bssend = t + data + bss; 
1990/1211    
	b = (bssend + (BY2PG-1)) & ~(BY2PG-1); 
1990/0227    
	if((t|d|b) & KZERO) 
1990/11211    
		error(Ebadexec); 
1990/0227    
 
	/* 
	 * Args: pass 1: count 
	 */ 
1991/0710    
	nbytes = BY2WD;		/* hole for profiling clock at top of stack */ 
1990/0227    
	nargs = 0; 
	if(indir){ 
		argp = progarg; 
		while(*argp){ 
			a = *argp++; 
			nbytes += strlen(a) + 1; 
			nargs++; 
		} 
	} 
	evenaddr(arg[1]); 
	argp = (char**)arg[1]; 
	validaddr((ulong)argp, BY2WD, 0); 
	while(*argp){ 
		a = *argp++; 
		if(((ulong)argp&(BY2PG-1)) < BY2WD) 
			validaddr((ulong)argp, BY2WD, 0); 
		validaddr((ulong)a, 1, 0); 
1993/0227    
		nbytes += (vmemchr(a, 0, 0x7FFFFFFF) - a) + 1; 
1990/1226    
		nargs++; 
1990/0227    
	} 
	ssize = BY2WD*(nargs+1) + ((nbytes+(BY2WD-1)) & ~(BY2WD-1)); 
1992/0923    
 
1992/0802    
	/* 
	 * 8-byte align SP for those (e.g. sparc) that need it. 
	 * execregs() will subtract another 4 bytes for argc. 
	 */ 
	if((ssize+4) & 7) 
		ssize += 4; 
1990/0227    
	spage = (ssize+(BY2PG-1)) >> PGSHIFT; 
1993/0727    
 
1990/0227    
	/* 
	 * Build the stack segment, putting it in kernel virtual for the moment 
	 */ 
1991/0523    
	if(spage > TSTKSIZ) 
1992/0114    
		error(Enovmem); 
1991/0522    
 
1993/0501    
	up->seg[ESEG] = newseg(SG_STACK, TSTKTOP-USTKSIZE, USTKSIZE/BY2PG); 
1990/0227    
 
	/* 
	 * Args: pass 2: assemble; the pages will be faulted in 
	 */ 
	argv = (char**)(TSTKTOP - ssize); 
	charp = (char*)(TSTKTOP - nbytes); 
	if(indir) 
		argp = progarg; 
	else 
		argp = (char**)arg[1]; 
1991/0705    
 
1990/0227    
	for(i=0; i<nargs; i++){ 
1991/0705    
		if(indir && *argp==0) { 
1990/0227    
			indir = 0; 
			argp = (char**)arg[1]; 
		} 
		*argv++ = charp + (USTKTOP-TSTKTOP); 
		n = strlen(*argp) + 1; 
1991/0318    
		memmove(charp, *argp++, n); 
1990/0227    
		charp += n; 
	} 
 
1993/0501    
	memmove(up->text, elem, NAMELEN); 
1990/0227    
 
	/* 
1992/1206    
	 * Committed. 
	 * Free old memory. 
	 * Special segments are maintained accross exec 
1990/0227    
	 */ 
1991/0709    
	for(i = SSEG; i <= BSEG; i++) { 
1993/0501    
		putseg(up->seg[i]); 
1994/0106    
		/* prevent a second free if we have an error */ 
		up->seg[i] = 0; 
	} 
	for(i = BSEG+1; i < NSEG; i++) { 
		s = up->seg[i]; 
		if(s != 0 && (s->type&SG_CEXEC)) { 
			putseg(s); 
			up->seg[i] = 0; 
		} 
1991/0709    
	} 
1990/08141    
 
	/* 
	 * Close on exec 
	 */ 
1993/0501    
	f = up->fgrp; 
1991/0705    
	for(i=0; i<=f->maxfd; i++) 
		fdclose(i, CCEXEC); 
1990/0227    
 
1991/0705    
	/* Text.  Shared. Attaches to cache image if possible */ 
1991/0724    
	/* attachimage returns a locked cache image */ 
1991/0705    
	img = attachimage(SG_TEXT|SG_RONLY, tc, UTZERO, (t-UTZERO)>>PGSHIFT); 
	ts = img->s; 
1993/0501    
	up->seg[TSEG] = ts; 
1991/0706    
	ts->flushme = 1; 
1991/0705    
	ts->fstart = 0; 
1991/0723    
	ts->flen = sizeof(Exec)+text; 
1991/0724    
	unlock(img); 
1990/0227    
 
1991/0705    
	/* Data. Shared. */ 
	s = newseg(SG_DATA, t, (d-t)>>PGSHIFT); 
1993/0501    
	up->seg[DSEG] = s; 
1990/0227    
 
1991/0705    
	/* Attached by hand */ 
	incref(img); 
	s->image = img; 
	s->fstart = ts->fstart+ts->flen; 
1991/0723    
	s->flen = data; 
1990/0227    
 
1991/0705    
	/* BSS. Zero fill on demand */ 
1993/0501    
	up->seg[BSEG] = newseg(SG_BSS, d, (b-d)>>PGSHIFT); 
1990/1211    
 
1990/0227    
	/* 
	 * Move the stack 
	 */ 
1993/0501    
	s = up->seg[ESEG]; 
	up->seg[ESEG] = 0; 
	up->seg[SSEG] = s; 
1991/0705    
	s->base = USTKTOP-USTKSIZE; 
	s->top = USTKTOP; 
1993/0502    
	relocateseg(s, USTKTOP-TSTKTOP); 
1990/0227    
 
1994/0727    
	/* 
	 *  '/' processes are high priority (hack to make /ip more responsive) 
	 */ 
	if(devchar[tc->type] == L'/') 
		up->priority = 1; 
1991/0706    
	close(tc); 
1991/0705    
 
1991/0529    
	/* 
1991/0705    
	 *  At this point, the mmu contains info about the old address 
1991/0529    
	 *  space and needs to be flushed 
	 */ 
1990/0227    
	flushmmu(); 
1993/0501    
	qlock(&up->debug); 
	up->nnote = 0; 
	up->notify = 0; 
	up->notified = 0; 
	procsetup(up); 
	qunlock(&up->debug); 
	if(up->hang) 
		up->procctl = Proc_stopme; 
1991/1110    
 
1991/1214    
	return execregs(entry, ssize, nargs); 
1990/0227    
} 
 
int 
shargs(char *s, int n, char **ap) 
{ 
	int i; 
 
1992/0928    
	s += 2; 
	n -= 2;		/* skip #! */ 
1990/0227    
	for(i=0; s[i]!='\n'; i++) 
		if(i == n-1) 
			return 0; 
	s[i] = 0; 
	*ap = 0; 
	i = 0; 
1993/0501    
	for(;;) { 
1990/0227    
		while(*s==' ' || *s=='\t') 
			s++; 
		if(*s == 0) 
			break; 
		i++; 
		*ap++ = s; 
		*ap = 0; 
		while(*s && *s!=' ' && *s!='\t') 
			s++; 
		if(*s == 0) 
			break; 
		else 
			*s++ = 0; 
	} 
	return i; 
} 
 
int 
return0(void *a) 
{ 
1991/1115    
	USED(a); 
1990/0227    
	return 0; 
} 
 
long 
syssleep(ulong *arg) 
{ 
1991/1219    
	if(arg[0] == 0) 
		sched(); 
	else 
1993/0501    
		tsleep(&up->sleep, return0, 0, arg[0]); 
1991/1219    
 
1990/0227    
	return 0; 
} 
 
1991/0513    
long 
sysalarm(ulong *arg) 
{ 
	return procalarm(arg[0]);		 
} 
1990/0227    
 
long 
sysexits(ulong *arg) 
{ 
	char *status; 
1991/0808    
	char *inval = "invalid exit string"; 
1990/0227    
 
	status = (char*)arg[0]; 
	if(status){ 
		if(waserror()) 
1991/0808    
			status = inval; 
1990/0227    
		else{ 
			validaddr((ulong)status, 1, 0); 
1991/0808    
			if(vmemchr(status, 0, ERRLEN) == 0) 
				status = inval; 
1990/0227    
		} 
1991/0614    
		poperror(); 
1991/0705    
 
1990/0227    
	} 
	pexit(status, 1); 
1992/0520    
	return 0;		/* not reached */ 
1990/0227    
} 
 
long 
syswait(ulong *arg) 
{ 
	if(arg[0]){ 
		validaddr(arg[0], sizeof(Waitmsg), 1); 
		evenaddr(arg[0]); 
	} 
	return pwait((Waitmsg*)arg[0]); 
} 
 
long 
1990/11211    
sysdeath(ulong *arg) 
1990/0227    
{ 
1991/1115    
	USED(arg); 
1992/0628    
	pprint("deprecated system call\n"); 
1990/11211    
	pexit("Suicide", 0); 
1992/0520    
	return 0;	/* not reached */ 
1990/0227    
} 
 
long 
syserrstr(ulong *arg) 
{ 
1993/0501    
	char *e, tmp[ERRLEN]; 
1993/0324    
 
1990/11211    
	validaddr(arg[0], ERRLEN, 1); 
1993/0501    
	e = (char*)arg[0]; 
	memmove(tmp, e, ERRLEN); 
	memmove(e, up->error, ERRLEN); 
	memmove(up->error, tmp, ERRLEN); 
1990/0227    
	return 0; 
} 
 
long 
sysnotify(ulong *arg) 
{ 
1991/1115    
	USED(arg); 
1991/0326    
	if(arg[0] != 0) 
		validaddr(arg[0], sizeof(ulong), 0); 
1993/0501    
	up->notify = (int(*)(void*, char*))(arg[0]); 
1990/0227    
	return 0; 
} 
 
long 
sysnoted(ulong *arg) 
{ 
1991/1115    
	USED(arg); 
1993/0501    
	if(up->notified == 0) 
1990/11211    
		error(Egreg); 
1990/0227    
	return 0; 
} 
 
long 
1991/0705    
syssegbrk(ulong *arg) 
1990/0227    
{ 
1991/0705    
	int i; 
1993/0501    
	ulong addr; 
	Segment *s; 
1991/0705    
 
1993/0501    
	addr = arg[0]; 
	for(i = 0; i < NSEG; i++) { 
		s = up->seg[i]; 
		if(s == 0 || addr < s->base || addr >= s->top) 
			continue; 
		switch(s->type&SG_TYPE) { 
		case SG_TEXT: 
		case SG_DATA: 
			error(Ebadarg); 
		default: 
			return ibrk(arg[1], i); 
1991/0705    
		} 
1993/0501    
	} 
1991/0705    
 
1992/0114    
	error(Ebadarg); 
1992/0520    
	return 0;		/* not reached */ 
1990/0227    
} 
1991/0606    
 
1991/0605    
long 
1991/0705    
syssegattach(ulong *arg) 
1991/0605    
{ 
1993/0501    
	return segattach(up, arg[0], (char*)arg[1], arg[2], arg[3]); 
1991/0705    
} 
 
long 
syssegdetach(ulong *arg) 
{ 
	int i; 
1993/0501    
	ulong addr; 
1991/0705    
	Segment *s; 
 
1991/0723    
	s = 0; 
1993/0501    
	addr = arg[0]; 
1991/0705    
	for(i = 0; i < NSEG; i++) 
1993/0501    
		if(s = up->seg[i]) { 
1991/0705    
			qlock(&s->lk); 
1993/0501    
			if((addr >= s->base && addr < s->top) ||  
			   (s->top == s->base && addr == s->base)) 
1991/0705    
				goto found; 
			qunlock(&s->lk); 
		} 
 
1992/0114    
	error(Ebadarg); 
1991/0705    
 
found: 
1993/0501    
	/* Check we are not detaching the current stack segment */ 
1991/0705    
	if((ulong)arg >= s->base && (ulong)arg < s->top) { 
		qunlock(&s->lk); 
1992/0114    
		error(Ebadarg); 
1991/0705    
	} 
1993/0501    
	up->seg[i] = 0; 
1991/0705    
	qunlock(&s->lk); 
	putseg(s); 
 
	/* Ensure we flush any entries from the lost segment */ 
	flushmmu(); 
	return 0; 
} 
 
long 
syssegfree(ulong *arg) 
{ 
	Segment *s; 
1991/0712    
	ulong from, pages; 
1991/0705    
 
1991/0706    
	from = PGROUND(arg[0]); 
1993/0501    
	s = seg(up, from, 1); 
1991/0705    
	if(s == 0) 
1992/0114    
		error(Ebadarg); 
1991/0705    
 
1991/0712    
	pages = (arg[1]+BY2PG-1)/BY2PG; 
1991/0705    
 
1991/0712    
	if(from+pages*BY2PG > s->top) { 
1991/0705    
		qunlock(&s->lk); 
1992/0114    
		error(Ebadarg); 
1991/0705    
	} 
 
1991/0712    
	mfreeseg(s, from, pages); 
1991/0705    
	qunlock(&s->lk); 
1992/1206    
	flushmmu(); 
1991/0705    
 
	return 0; 
} 
 
/* For binary compatability */ 
long 
sysbrk_(ulong *arg) 
{ 
	return ibrk(arg[0], BSEG); 
1991/0605    
} 
1991/0806    
 
long 
sysrendezvous(ulong *arg) 
{ 
1994/0515    
	int tag; 
1991/0806    
	ulong val; 
1994/0515    
	Proc *p, **l; 
1991/0806    
 
	tag = arg[0]; 
1993/0501    
	l = &REND(up->pgrp, tag); 
1991/0806    
 
1993/0501    
	lock(up->pgrp); 
1991/0806    
	for(p = *l; p; p = p->rendhash) { 
		if(p->rendtag == tag) { 
			*l = p->rendhash; 
			val = p->rendval; 
			p->rendval = arg[1]; 
1994/0515    
 
			while(p->mach != 0) 
1991/0807    
				; 
1991/0806    
			ready(p); 
1993/0501    
			unlock(up->pgrp); 
1991/0806    
			return val;	 
		} 
		l = &p->rendhash; 
	} 
 
	/* Going to sleep here */ 
1993/0501    
	up->rendtag = tag; 
	up->rendval = arg[1]; 
	up->rendhash = *l; 
	*l = up; 
	up->state = Rendezvous; 
1994/0515    
	unlock(up->pgrp); 
 
1991/0806    
	sched(); 
 
1993/0501    
	return up->rendval; 
1991/0806    
} 


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