summaryrefslogtreecommitdiff
path: root/sys/src/cmd/unix/drawterm/kern
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/unix/drawterm/kern
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/unix/drawterm/kern')
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/Makefile50
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/allocb.c165
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/cache.c46
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/chan.c1494
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/dat.h519
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/data.c31
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/dev.c468
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devaudio-none.c49
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devaudio-unix.c183
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devaudio.c372
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devaudio.h25
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devcons.c1193
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devdraw.c2148
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devfs-posix.c635
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devfs-win32.c707
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devip-posix.c210
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devip-win32.c212
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devip.c938
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devip.h19
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devlfd.c126
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devmnt.c1216
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devmouse.c237
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devpipe.c398
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devroot.c299
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devssl.c1517
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devtab.c35
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/devtls.c2185
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/error.c50
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/error.h50
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/exportfs.c821
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/fns.h392
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/netif.h133
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/parse.c113
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/pgrp.c272
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/posix.c225
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/procinit.c67
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/qio.c1524
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/qlock.c94
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/rendez.c90
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/rwlock.c39
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/screen.h63
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/sleep.c90
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/smalloc.c18
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/stub.c171
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/sysfile.c1244
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/sysproc.c32
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/term.c205
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/uart.c15
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/unused/syscall.c837
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/waserror.c27
-rwxr-xr-xsys/src/cmd/unix/drawterm/kern/win32.c470
51 files changed, 22519 insertions, 0 deletions
diff --git a/sys/src/cmd/unix/drawterm/kern/Makefile b/sys/src/cmd/unix/drawterm/kern/Makefile
new file mode 100755
index 000000000..c4cf4b21b
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/Makefile
@@ -0,0 +1,50 @@
+ROOT=..
+include ../Make.config
+LIB=libkern.a
+
+OFILES=\
+ allocb.$O\
+ cache.$O\
+ chan.$O\
+ data.$O\
+ dev.$O\
+ devaudio.$O\
+ devaudio-$(AUDIO).$O\
+ devcons.$O\
+ devdraw.$O\
+ devfs-$(OS).$O\
+ devip.$O\
+ devip-$(OS).$O\
+ devlfd.$O\
+ devmnt.$O\
+ devmouse.$O\
+ devpipe.$O\
+ devroot.$O\
+ devssl.$O\
+ devtls.$O\
+ devtab.$O\
+ error.$O\
+ parse.$O\
+ pgrp.$O\
+ procinit.$O\
+ rwlock.$O\
+ sleep.$O\
+ smalloc.$O\
+ stub.$O\
+ sysfile.$O\
+ sysproc.$O\
+ qio.$O\
+ qlock.$O\
+ term.$O\
+ uart.$O\
+ waserror.$O\
+ $(OS).$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+ $(AR) r $(LIB) $(OFILES)
+ $(RANLIB) $(LIB)
+
+%.$O: %.c
+ $(CC) $(CFLAGS) $*.c
+
diff --git a/sys/src/cmd/unix/drawterm/kern/allocb.c b/sys/src/cmd/unix/drawterm/kern/allocb.c
new file mode 100755
index 000000000..ab1595c89
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/allocb.c
@@ -0,0 +1,165 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+enum
+{
+ Hdrspc = 64, /* leave room for high-level headers */
+ Bdead = 0x51494F42, /* "QIOB" */
+};
+
+struct
+{
+ Lock lk;
+ ulong bytes;
+} ialloc;
+
+static Block*
+_allocb(int size)
+{
+ Block *b;
+ uintptr addr;
+
+ if((b = mallocz(sizeof(Block)+size+Hdrspc, 0)) == nil)
+ return nil;
+
+ b->next = nil;
+ b->list = nil;
+ b->free = 0;
+ b->flag = 0;
+
+ /* align start of data portion by rounding up */
+ addr = (uintptr)b;
+ addr = ROUND(addr + sizeof(Block), BLOCKALIGN);
+ b->base = (uchar*)addr;
+
+ /* align end of data portion by rounding down */
+ b->lim = ((uchar*)b) + sizeof(Block)+size+Hdrspc;
+ addr = (uintptr)(b->lim);
+ addr = addr & ~(BLOCKALIGN-1);
+ b->lim = (uchar*)addr;
+
+ /* leave sluff at beginning for added headers */
+ b->rp = b->lim - ROUND(size, BLOCKALIGN);
+ if(b->rp < b->base)
+ panic("_allocb");
+ b->wp = b->rp;
+
+ return b;
+}
+
+Block*
+allocb(int size)
+{
+ Block *b;
+
+ /*
+ * Check in a process and wait until successful.
+ * Can still error out of here, though.
+ */
+ if(up == nil)
+ panic("allocb without up: %p\n", getcallerpc(&size));
+ if((b = _allocb(size)) == nil){
+ panic("allocb: no memory for %d bytes\n", size);
+ }
+ setmalloctag(b, getcallerpc(&size));
+
+ return b;
+}
+
+Block*
+iallocb(int size)
+{
+ Block *b;
+ static int m1, m2;
+
+ if(ialloc.bytes > conf.ialloc){
+ if((m1++%10000)==0)
+ print("iallocb: limited %lud/%lud\n",
+ ialloc.bytes, conf.ialloc);
+ return 0;
+ }
+
+ if((b = _allocb(size)) == nil){
+ if((m2++%10000)==0)
+ print("iallocb: no memory %lud/%lud\n",
+ ialloc.bytes, conf.ialloc);
+ return nil;
+ }
+ setmalloctag(b, getcallerpc(&size));
+ b->flag = BINTR;
+
+ ilock(&ialloc.lk);
+ ialloc.bytes += b->lim - b->base;
+ iunlock(&ialloc.lk);
+
+ return b;
+}
+
+void
+freeb(Block *b)
+{
+ void *dead = (void*)Bdead;
+
+ if(b == nil)
+ return;
+
+ /*
+ * drivers which perform non cache coherent DMA manage their own buffer
+ * pool of uncached buffers and provide their own free routine.
+ */
+ if(b->free) {
+ b->free(b);
+ return;
+ }
+ if(b->flag & BINTR) {
+ ilock(&ialloc.lk);
+ ialloc.bytes -= b->lim - b->base;
+ iunlock(&ialloc.lk);
+ }
+
+ /* poison the block in case someone is still holding onto it */
+ b->next = dead;
+ b->rp = dead;
+ b->wp = dead;
+ b->lim = dead;
+ b->base = dead;
+
+ free(b);
+}
+
+void
+checkb(Block *b, char *msg)
+{
+ void *dead = (void*)Bdead;
+
+ if(b == dead)
+ panic("checkb b %s %lux", msg, b);
+ if(b->base == dead || b->lim == dead || b->next == dead
+ || b->rp == dead || b->wp == dead){
+ print("checkb: base 0x%8.8luX lim 0x%8.8luX next 0x%8.8luX\n",
+ b->base, b->lim, b->next);
+ print("checkb: rp 0x%8.8luX wp 0x%8.8luX\n", b->rp, b->wp);
+ panic("checkb dead: %s\n", msg);
+ }
+
+ if(b->base > b->lim)
+ panic("checkb 0 %s %lux %lux", msg, b->base, b->lim);
+ if(b->rp < b->base)
+ panic("checkb 1 %s %lux %lux", msg, b->base, b->rp);
+ if(b->wp < b->base)
+ panic("checkb 2 %s %lux %lux", msg, b->base, b->wp);
+ if(b->rp > b->lim)
+ panic("checkb 3 %s %lux %lux", msg, b->rp, b->lim);
+ if(b->wp > b->lim)
+ panic("checkb 4 %s %lux %lux", msg, b->wp, b->lim);
+
+}
+
+void
+iallocsummary(void)
+{
+ print("ialloc %lud/%lud\n", ialloc.bytes, conf.ialloc);
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/cache.c b/sys/src/cmd/unix/drawterm/kern/cache.c
new file mode 100755
index 000000000..56ee23ca7
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/cache.c
@@ -0,0 +1,46 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+
+void
+cinit(void)
+{
+}
+
+void
+copen(Chan *c)
+{
+ USED(c);
+}
+
+int
+cread(Chan *c, uchar *buf, int len, vlong off)
+{
+ USED(c);
+ USED(buf);
+ USED(len);
+ USED(off);
+
+ return 0;
+}
+
+void
+cupdate(Chan *c, uchar *buf, int len, vlong off)
+{
+ USED(c);
+ USED(buf);
+ USED(len);
+ USED(off);
+}
+
+void
+cwrite(Chan* c, uchar *buf, int len, vlong off)
+{
+ USED(c);
+ USED(buf);
+ USED(len);
+ USED(off);
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/chan.c b/sys/src/cmd/unix/drawterm/kern/chan.c
new file mode 100755
index 000000000..7105f3a69
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/chan.c
@@ -0,0 +1,1494 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+int chandebug=0; /* toggled by sysr1 */
+QLock chanprint; /* probably asking for trouble (deadlocks) -rsc */
+
+int domount(Chan**, Mhead**);
+
+void
+dumpmount(void) /* DEBUGGING */
+{
+ Pgrp *pg;
+ Mount *t;
+ Mhead **h, **he, *f;
+
+ if(up == nil){
+ print("no process for dumpmount\n");
+ return;
+ }
+ pg = up->pgrp;
+ if(pg == nil){
+ print("no pgrp for dumpmount\n");
+ return;
+ }
+ rlock(&pg->ns);
+ if(waserror()) {
+ runlock(&pg->ns);
+ nexterror();
+ }
+
+ he = &pg->mnthash[MNTHASH];
+ for(h = pg->mnthash; h < he; h++) {
+ for(f = *h; f; f = f->hash) {
+ print("head: %p: %s 0x%llux.%lud %C %lud -> \n", f,
+ f->from->name->s, f->from->qid.path,
+ f->from->qid.vers, devtab[f->from->type]->dc,
+ f->from->dev);
+ for(t = f->mount; t; t = t->next)
+ print("\t%p: %s (umh %p) (path %.8llux dev %C %lud)\n", t, t->to->name->s, t->to->umh, t->to->qid.path, devtab[t->to->type]->dc, t->to->dev);
+ }
+ }
+ poperror();
+ runlock(&pg->ns);
+}
+
+
+char*
+c2name(Chan *c) /* DEBUGGING */
+{
+ if(c == nil)
+ return "<nil chan>";
+ if(c->name == nil)
+ return "<nil name>";
+ if(c->name->s == nil)
+ return "<nil name.s>";
+ return c->name->s;
+}
+
+enum
+{
+ CNAMESLOP = 20
+};
+
+struct
+{
+ Lock lk;
+ int fid;
+ Chan *free;
+ Chan *list;
+}chanalloc;
+
+typedef struct Elemlist Elemlist;
+
+struct Elemlist
+{
+ char *name; /* copy of name, so '/' can be overwritten */
+ int nelems;
+ char **elems;
+ int *off;
+ int mustbedir;
+};
+
+#define SEP(c) ((c) == 0 || (c) == '/')
+void cleancname(Cname*);
+
+int
+isdotdot(char *p)
+{
+ return p[0]=='.' && p[1]=='.' && p[2]=='\0';
+}
+
+int
+incref(Ref *r)
+{
+ int x;
+
+ lock(&r->lk);
+ x = ++r->ref;
+ unlock(&r->lk);
+ return x;
+}
+
+int
+decref(Ref *r)
+{
+ int x;
+
+ lock(&r->lk);
+ x = --r->ref;
+ unlock(&r->lk);
+ if(x < 0)
+ panic("decref, pc=0x%p", getcallerpc(&r));
+
+ return x;
+}
+
+/*
+ * Rather than strncpy, which zeros the rest of the buffer, kstrcpy
+ * truncates if necessary, always zero terminates, does not zero fill,
+ * and puts ... at the end of the string if it's too long. Usually used to
+ * save a string in up->genbuf;
+ */
+void
+kstrcpy(char *s, char *t, int ns)
+{
+ int nt;
+
+ nt = strlen(t);
+ if(nt+1 <= ns){
+ memmove(s, t, nt+1);
+ return;
+ }
+ /* too long */
+ if(ns < 4){
+ /* but very short! */
+ strncpy(s, t, ns);
+ return;
+ }
+ /* truncate with ... at character boundary (very rare case) */
+ memmove(s, t, ns-4);
+ ns -= 4;
+ s[ns] = '\0';
+ /* look for first byte of UTF-8 sequence by skipping continuation bytes */
+ while(ns>0 && (s[--ns]&0xC0)==0x80)
+ ;
+ strcpy(s+ns, "...");
+}
+
+int
+emptystr(char *s)
+{
+ if(s == nil)
+ return 1;
+ if(s[0] == '\0')
+ return 1;
+ return 0;
+}
+
+/*
+ * Atomically replace *p with copy of s
+ */
+void
+kstrdup(char **p, char *s)
+{
+ int n;
+ char *t, *prev;
+
+ n = strlen(s)+1;
+ /* if it's a user, we can wait for memory; if not, something's very wrong */
+ if(up){
+ t = smalloc(n);
+ setmalloctag(t, getcallerpc(&p));
+ }else{
+ t = malloc(n);
+ if(t == nil)
+ panic("kstrdup: no memory");
+ }
+ memmove(t, s, n);
+ prev = *p;
+ *p = t;
+ free(prev);
+}
+
+void
+chandevreset(void)
+{
+ int i;
+
+ for(i=0; devtab[i] != nil; i++)
+ devtab[i]->reset();
+}
+
+void
+chandevinit(void)
+{
+ int i;
+
+ for(i=0; devtab[i] != nil; i++)
+ devtab[i]->init();
+}
+
+void
+chandevshutdown(void)
+{
+ int i;
+
+ /* shutdown in reverse order */
+ for(i=0; devtab[i] != nil; i++)
+ ;
+ for(i--; i >= 0; i--)
+ devtab[i]->shutdown();
+}
+
+Chan*
+newchan(void)
+{
+ Chan *c;
+
+ lock(&chanalloc.lk);
+ c = chanalloc.free;
+ if(c != 0)
+ chanalloc.free = c->next;
+ unlock(&chanalloc.lk);
+
+ if(c == nil) {
+ c = smalloc(sizeof(Chan));
+ lock(&chanalloc.lk);
+ c->fid = ++chanalloc.fid;
+ c->link = chanalloc.list;
+ chanalloc.list = c;
+ unlock(&chanalloc.lk);
+ }
+
+ /* if you get an error before associating with a dev,
+ close calls rootclose, a nop */
+ c->type = 0;
+ c->flag = 0;
+ c->ref.ref = 1;
+ c->dev = 0;
+ c->offset = 0;
+ c->iounit = 0;
+ c->umh = 0;
+ c->uri = 0;
+ c->dri = 0;
+ c->aux = 0;
+ c->mchan = 0;
+ c->mcp = 0;
+ c->mux = 0;
+ memset(&c->mqid, 0, sizeof(c->mqid));
+ c->name = 0;
+ return c;
+}
+
+static Ref ncname;
+
+Cname*
+newcname(char *s)
+{
+ Cname *n;
+ int i;
+
+ n = smalloc(sizeof(Cname));
+ i = strlen(s);
+ n->len = i;
+ n->alen = i+CNAMESLOP;
+ n->s = smalloc(n->alen);
+ memmove(n->s, s, i+1);
+ n->ref.ref = 1;
+ incref(&ncname);
+ return n;
+}
+
+void
+cnameclose(Cname *n)
+{
+ if(n == nil)
+ return;
+ if(decref(&n->ref))
+ return;
+ decref(&ncname);
+ free(n->s);
+ free(n);
+}
+
+Cname*
+addelem(Cname *n, char *s)
+{
+ int i, a;
+ char *t;
+ Cname *new;
+
+ if(s[0]=='.' && s[1]=='\0')
+ return n;
+
+ if(n->ref.ref > 1){
+ /* copy on write */
+ new = newcname(n->s);
+ cnameclose(n);
+ n = new;
+ }
+
+ i = strlen(s);
+ if(n->len+1+i+1 > n->alen){
+ a = n->len+1+i+1 + CNAMESLOP;
+ t = smalloc(a);
+ memmove(t, n->s, n->len+1);
+ free(n->s);
+ n->s = t;
+ n->alen = a;
+ }
+ if(n->len>0 && n->s[n->len-1]!='/' && s[0]!='/') /* don't insert extra slash if one is present */
+ n->s[n->len++] = '/';
+ memmove(n->s+n->len, s, i+1);
+ n->len += i;
+ if(isdotdot(s))
+ cleancname(n);
+ return n;
+}
+
+void
+chanfree(Chan *c)
+{
+ c->flag = CFREE;
+
+ if(c->umh != nil){
+ putmhead(c->umh);
+ c->umh = nil;
+ }
+ if(c->umc != nil){
+ cclose(c->umc);
+ c->umc = nil;
+ }
+ if(c->mux != nil){
+ muxclose(c->mux);
+ c->mux = nil;
+ }
+ if(c->mchan != nil){
+ cclose(c->mchan);
+ c->mchan = nil;
+ }
+
+ cnameclose(c->name);
+
+ lock(&chanalloc.lk);
+ c->next = chanalloc.free;
+ chanalloc.free = c;
+ unlock(&chanalloc.lk);
+}
+
+void
+cclose(Chan *c)
+{
+ if(c->flag&CFREE)
+ panic("cclose %p", getcallerpc(&c));
+
+ if(decref(&c->ref))
+ return;
+
+ if(!waserror()){
+ devtab[c->type]->close(c);
+ poperror();
+ }
+ chanfree(c);
+}
+
+/*
+ * Make sure we have the only copy of c. (Copy on write.)
+ */
+Chan*
+cunique(Chan *c)
+{
+ Chan *nc;
+
+ if(c->ref.ref != 1) {
+ nc = cclone(c);
+ cclose(c);
+ c = nc;
+ }
+
+ return c;
+}
+
+int
+eqqid(Qid a, Qid b)
+{
+ return a.path==b.path && a.vers==b.vers;
+}
+
+int
+eqchan(Chan *a, Chan *b, int pathonly)
+{
+ if(a->qid.path != b->qid.path)
+ return 0;
+ if(!pathonly && a->qid.vers!=b->qid.vers)
+ return 0;
+ if(a->type != b->type)
+ return 0;
+ if(a->dev != b->dev)
+ return 0;
+ return 1;
+}
+
+int
+eqchantdqid(Chan *a, int type, int dev, Qid qid, int pathonly)
+{
+ if(a->qid.path != qid.path)
+ return 0;
+ if(!pathonly && a->qid.vers!=qid.vers)
+ return 0;
+ if(a->type != type)
+ return 0;
+ if(a->dev != dev)
+ return 0;
+ return 1;
+}
+
+Mhead*
+newmhead(Chan *from)
+{
+ Mhead *mh;
+
+ mh = smalloc(sizeof(Mhead));
+ mh->ref.ref = 1;
+ mh->from = from;
+ incref(&from->ref);
+
+/*
+ n = from->name->len;
+ if(n >= sizeof(mh->fromname))
+ n = sizeof(mh->fromname)-1;
+ memmove(mh->fromname, from->name->s, n);
+ mh->fromname[n] = 0;
+*/
+ return mh;
+}
+
+int
+cmount(Chan **newp, Chan *old, int flag, char *spec)
+{
+ Pgrp *pg;
+ int order, flg;
+ Mhead *m, **l, *mh;
+ Mount *nm, *f, *um, **h;
+ Chan *new;
+
+ if(QTDIR & (old->qid.type^(*newp)->qid.type))
+ error(Emount);
+
+if(old->umh)print("cmount old extra umh\n");
+
+ order = flag&MORDER;
+
+ if((old->qid.type&QTDIR)==0 && order != MREPL)
+ error(Emount);
+
+ new = *newp;
+ mh = new->umh;
+
+ /*
+ * Not allowed to bind when the old directory
+ * is itself a union. (Maybe it should be allowed, but I don't see
+ * what the semantics would be.)
+ *
+ * We need to check mh->mount->next to tell unions apart from
+ * simple mount points, so that things like
+ * mount -c fd /root
+ * bind -c /root /
+ * work. The check of mount->mflag catches things like
+ * mount fd /root
+ * bind -c /root /
+ *
+ * This is far more complicated than it should be, but I don't
+ * see an easier way at the moment. -rsc
+ */
+ if((flag&MCREATE) && mh && mh->mount
+ && (mh->mount->next || !(mh->mount->mflag&MCREATE)))
+ error(Emount);
+
+ pg = up->pgrp;
+ wlock(&pg->ns);
+
+ l = &MOUNTH(pg, old->qid);
+ for(m = *l; m; m = m->hash) {
+ if(eqchan(m->from, old, 1))
+ break;
+ l = &m->hash;
+ }
+
+ if(m == nil) {
+ /*
+ * nothing mounted here yet. create a mount
+ * head and add to the hash table.
+ */
+ m = newmhead(old);
+ *l = m;
+
+ /*
+ * if this is a union mount, add the old
+ * node to the mount chain.
+ */
+ if(order != MREPL)
+ m->mount = newmount(m, old, 0, 0);
+ }
+ wlock(&m->lock);
+ if(waserror()){
+ wunlock(&m->lock);
+ nexterror();
+ }
+ wunlock(&pg->ns);
+
+ nm = newmount(m, new, flag, spec);
+ if(mh != nil && mh->mount != nil) {
+ /*
+ * copy a union when binding it onto a directory
+ */
+ flg = order;
+ if(order == MREPL)
+ flg = MAFTER;
+ h = &nm->next;
+ um = mh->mount;
+ for(um = um->next; um; um = um->next) {
+ f = newmount(m, um->to, flg, um->spec);
+ *h = f;
+ h = &f->next;
+ }
+ }
+
+ if(m->mount && order == MREPL) {
+ mountfree(m->mount);
+ m->mount = 0;
+ }
+
+ if(flag & MCREATE)
+ nm->mflag |= MCREATE;
+
+ if(m->mount && order == MAFTER) {
+ for(f = m->mount; f->next; f = f->next)
+ ;
+ f->next = nm;
+ }
+ else {
+ for(f = nm; f->next; f = f->next)
+ ;
+ f->next = m->mount;
+ m->mount = nm;
+ }
+
+ wunlock(&m->lock);
+ poperror();
+ return nm->mountid;
+}
+
+void
+cunmount(Chan *mnt, Chan *mounted)
+{
+ Pgrp *pg;
+ Mhead *m, **l;
+ Mount *f, **p;
+
+ if(mnt->umh) /* should not happen */
+ print("cunmount newp extra umh %p has %p\n", mnt, mnt->umh);
+
+ /*
+ * It _can_ happen that mounted->umh is non-nil,
+ * because mounted is the result of namec(Aopen)
+ * (see sysfile.c:/^sysunmount).
+ * If we open a union directory, it will have a umh.
+ * Although surprising, this is okay, since the
+ * cclose will take care of freeing the umh.
+ */
+
+ pg = up->pgrp;
+ wlock(&pg->ns);
+
+ l = &MOUNTH(pg, mnt->qid);
+ for(m = *l; m; m = m->hash) {
+ if(eqchan(m->from, mnt, 1))
+ break;
+ l = &m->hash;
+ }
+
+ if(m == 0) {
+ wunlock(&pg->ns);
+ error(Eunmount);
+ }
+
+ wlock(&m->lock);
+ if(mounted == 0) {
+ *l = m->hash;
+ wunlock(&pg->ns);
+ mountfree(m->mount);
+ m->mount = nil;
+ cclose(m->from);
+ wunlock(&m->lock);
+ putmhead(m);
+ return;
+ }
+
+ p = &m->mount;
+ for(f = *p; f; f = f->next) {
+ /* BUG: Needs to be 2 pass */
+ if(eqchan(f->to, mounted, 1) ||
+ (f->to->mchan && eqchan(f->to->mchan, mounted, 1))) {
+ *p = f->next;
+ f->next = 0;
+ mountfree(f);
+ if(m->mount == nil) {
+ *l = m->hash;
+ cclose(m->from);
+ wunlock(&m->lock);
+ wunlock(&pg->ns);
+ putmhead(m);
+ return;
+ }
+ wunlock(&m->lock);
+ wunlock(&pg->ns);
+ return;
+ }
+ p = &f->next;
+ }
+ wunlock(&m->lock);
+ wunlock(&pg->ns);
+ error(Eunion);
+}
+
+Chan*
+cclone(Chan *c)
+{
+ Chan *nc;
+ Walkqid *wq;
+
+ wq = devtab[c->type]->walk(c, nil, nil, 0);
+ if(wq == nil)
+ error("clone failed");
+ nc = wq->clone;
+ free(wq);
+ nc->name = c->name;
+ if(c->name)
+ incref(&c->name->ref);
+ return nc;
+}
+
+int
+findmount(Chan **cp, Mhead **mp, int type, int dev, Qid qid)
+{
+ Pgrp *pg;
+ Mhead *m;
+
+ pg = up->pgrp;
+ rlock(&pg->ns);
+ for(m = MOUNTH(pg, qid); m; m = m->hash){
+ rlock(&m->lock);
+if(m->from == nil){
+ print("m %p m->from 0\n", m);
+ runlock(&m->lock);
+ continue;
+}
+ if(eqchantdqid(m->from, type, dev, qid, 1)) {
+ runlock(&pg->ns);
+ if(mp != nil){
+ incref(&m->ref);
+ if(*mp != nil)
+ putmhead(*mp);
+ *mp = m;
+ }
+ if(*cp != nil)
+ cclose(*cp);
+ incref(&m->mount->to->ref);
+ *cp = m->mount->to;
+ runlock(&m->lock);
+ return 1;
+ }
+ runlock(&m->lock);
+ }
+
+ runlock(&pg->ns);
+ return 0;
+}
+
+int
+domount(Chan **cp, Mhead **mp)
+{
+ return findmount(cp, mp, (*cp)->type, (*cp)->dev, (*cp)->qid);
+}
+
+Chan*
+undomount(Chan *c, Cname *name)
+{
+ Chan *nc;
+ Pgrp *pg;
+ Mount *t;
+ Mhead **h, **he, *f;
+
+ pg = up->pgrp;
+ rlock(&pg->ns);
+ if(waserror()) {
+ runlock(&pg->ns);
+ nexterror();
+ }
+
+ he = &pg->mnthash[MNTHASH];
+ for(h = pg->mnthash; h < he; h++) {
+ for(f = *h; f; f = f->hash) {
+ if(strcmp(f->from->name->s, name->s) != 0)
+ continue;
+ for(t = f->mount; t; t = t->next) {
+ if(eqchan(c, t->to, 1)) {
+ /*
+ * We want to come out on the left hand side of the mount
+ * point using the element of the union that we entered on.
+ * To do this, find the element that has a from name of
+ * c->name->s.
+ */
+ if(strcmp(t->head->from->name->s, name->s) != 0)
+ continue;
+ nc = t->head->from;
+ incref(&nc->ref);
+ cclose(c);
+ c = nc;
+ break;
+ }
+ }
+ }
+ }
+ poperror();
+ runlock(&pg->ns);
+ return c;
+}
+
+/*
+ * Either walks all the way or not at all. No partial results in *cp.
+ * *nerror is the number of names to display in an error message.
+ */
+static char Edoesnotexist[] = "does not exist";
+int
+walk(Chan **cp, char **names, int nnames, int nomount, int *nerror)
+{
+ int dev, dotdot, i, n, nhave, ntry, type;
+ Chan *c, *nc;
+ Cname *cname;
+ Mount *f;
+ Mhead *mh, *nmh;
+ Walkqid *wq;
+
+ c = *cp;
+ incref(&c->ref);
+ cname = c->name;
+ incref(&cname->ref);
+ mh = nil;
+
+ /*
+ * While we haven't gotten all the way down the path:
+ * 1. step through a mount point, if any
+ * 2. send a walk request for initial dotdot or initial prefix without dotdot
+ * 3. move to the first mountpoint along the way.
+ * 4. repeat.
+ *
+ * An invariant is that each time through the loop, c is on the undomount
+ * side of the mount point, and c's name is cname.
+ */
+ for(nhave=0; nhave<nnames; nhave+=n){
+ if((c->qid.type&QTDIR)==0){
+ if(nerror)
+ *nerror = nhave;
+ cnameclose(cname);
+ cclose(c);
+ strcpy(up->errstr, Enotdir);
+ if(mh != nil)
+{print("walk 1\n");
+ putmhead(mh);
+}
+ return -1;
+ }
+ ntry = nnames - nhave;
+ if(ntry > MAXWELEM)
+ ntry = MAXWELEM;
+ dotdot = 0;
+ for(i=0; i<ntry; i++){
+ if(isdotdot(names[nhave+i])){
+ if(i==0) {
+ dotdot = 1;
+ ntry = 1;
+ } else
+ ntry = i;
+ break;
+ }
+ }
+
+ if(!dotdot && !nomount)
+ domount(&c, &mh);
+
+ type = c->type;
+ dev = c->dev;
+
+ if((wq = devtab[type]->walk(c, nil, names+nhave, ntry)) == nil){
+ /* try a union mount, if any */
+ if(mh && !nomount){
+ /*
+ * mh->mount == c, so start at mh->mount->next
+ */
+ rlock(&mh->lock);
+ for(f = mh->mount->next; f; f = f->next)
+ if((wq = devtab[f->to->type]->walk(f->to, nil, names+nhave, ntry)) != nil)
+ break;
+ runlock(&mh->lock);
+ if(f != nil){
+ type = f->to->type;
+ dev = f->to->dev;
+ }
+ }
+ if(wq == nil){
+ cclose(c);
+ cnameclose(cname);
+ if(nerror)
+ *nerror = nhave+1;
+ if(mh != nil)
+ putmhead(mh);
+ return -1;
+ }
+ }
+
+ nmh = nil;
+ if(dotdot) {
+ assert(wq->nqid == 1);
+ assert(wq->clone != nil);
+
+ cname = addelem(cname, "..");
+ nc = undomount(wq->clone, cname);
+ n = 1;
+ } else {
+ nc = nil;
+ if(!nomount)
+ for(i=0; i<wq->nqid && i<ntry-1; i++)
+ if(findmount(&nc, &nmh, type, dev, wq->qid[i]))
+ break;
+ if(nc == nil){ /* no mount points along path */
+ if(wq->clone == nil){
+ cclose(c);
+ cnameclose(cname);
+ if(wq->nqid==0 || (wq->qid[wq->nqid-1].type&QTDIR)){
+ if(nerror)
+ *nerror = nhave+wq->nqid+1;
+ strcpy(up->errstr, Edoesnotexist);
+ }else{
+ if(nerror)
+ *nerror = nhave+wq->nqid;
+ strcpy(up->errstr, Enotdir);
+ }
+ free(wq);
+ if(mh != nil)
+ putmhead(mh);
+ return -1;
+ }
+ n = wq->nqid;
+ nc = wq->clone;
+ }else{ /* stopped early, at a mount point */
+ if(wq->clone != nil){
+ cclose(wq->clone);
+ wq->clone = nil;
+ }
+ n = i+1;
+ }
+ for(i=0; i<n; i++)
+ cname = addelem(cname, names[nhave+i]);
+ }
+ cclose(c);
+ c = nc;
+ putmhead(mh);
+ mh = nmh;
+ free(wq);
+ }
+
+ putmhead(mh);
+
+ c = cunique(c);
+
+ if(c->umh != nil){ //BUG
+ print("walk umh\n");
+ putmhead(c->umh);
+ c->umh = nil;
+ }
+
+ cnameclose(c->name);
+ c->name = cname;
+
+ cclose(*cp);
+ *cp = c;
+ if(nerror)
+ *nerror = 0;
+ return 0;
+}
+
+/*
+ * c is a mounted non-creatable directory. find a creatable one.
+ */
+Chan*
+createdir(Chan *c, Mhead *m)
+{
+ Chan *nc;
+ Mount *f;
+
+ rlock(&m->lock);
+ if(waserror()) {
+ runlock(&m->lock);
+ nexterror();
+ }
+ for(f = m->mount; f; f = f->next) {
+ if(f->mflag&MCREATE) {
+ nc = cclone(f->to);
+ runlock(&m->lock);
+ poperror();
+ cclose(c);
+ return nc;
+ }
+ }
+ error(Enocreate);
+ return 0;
+}
+
+void
+saveregisters(void)
+{
+}
+
+/*
+ * In place, rewrite name to compress multiple /, eliminate ., and process ..
+ */
+void
+cleancname(Cname *n)
+{
+ char *p;
+
+ if(n->s[0] == '#'){
+ p = strchr(n->s, '/');
+ if(p == nil)
+ return;
+ cleanname(p);
+
+ /*
+ * The correct name is #i rather than #i/,
+ * but the correct name of #/ is #/.
+ */
+ if(strcmp(p, "/")==0 && n->s[1] != '/')
+ *p = '\0';
+ }else
+ cleanname(n->s);
+ n->len = strlen(n->s);
+}
+
+static void
+growparse(Elemlist *e)
+{
+ char **new;
+ int *inew;
+ enum { Delta = 8 };
+
+ if(e->nelems % Delta == 0){
+ new = smalloc((e->nelems+Delta) * sizeof(char*));
+ memmove(new, e->elems, e->nelems*sizeof(char*));
+ free(e->elems);
+ e->elems = new;
+ inew = smalloc((e->nelems+Delta+1) * sizeof(int));
+ memmove(inew, e->off, e->nelems*sizeof(int));
+ free(e->off);
+ e->off = inew;
+ }
+}
+
+/*
+ * The name is known to be valid.
+ * Copy the name so slashes can be overwritten.
+ * An empty string will set nelem=0.
+ * A path ending in / or /. or /.//./ etc. will have
+ * e.mustbedir = 1, so that we correctly
+ * reject, e.g., "/adm/users/." when /adm/users is a file
+ * rather than a directory.
+ */
+static void
+parsename(char *name, Elemlist *e)
+{
+ char *slash;
+
+ kstrdup(&e->name, name);
+ name = e->name;
+ e->nelems = 0;
+ e->elems = nil;
+ e->off = smalloc(sizeof(int));
+ e->off[0] = skipslash(name) - name;
+ for(;;){
+ name = skipslash(name);
+ if(*name=='\0'){
+ e->mustbedir = 1;
+ break;
+ }
+ growparse(e);
+ e->elems[e->nelems++] = name;
+ slash = utfrune(name, '/');
+ if(slash == nil){
+ e->off[e->nelems] = name+strlen(name) - e->name;
+ e->mustbedir = 0;
+ break;
+ }
+ e->off[e->nelems] = slash - e->name;
+ *slash++ = '\0';
+ name = slash;
+ }
+}
+
+void*
+mymemrchr(void *va, int c, long n)
+{
+ uchar *a, *e;
+
+ a = va;
+ for(e=a+n-1; e>a; e--)
+ if(*e == c)
+ return e;
+ return nil;
+}
+
+/*
+ * Turn a name into a channel.
+ * &name[0] is known to be a valid address. It may be a kernel address.
+ *
+ * Opening with amode Aopen, Acreate, or Aremove guarantees
+ * that the result will be the only reference to that particular fid.
+ * This is necessary since we might pass the result to
+ * devtab[]->remove().
+ *
+ * Opening Atodir, Amount, or Aaccess does not guarantee this.
+ *
+ * Opening Aaccess can, under certain conditions, return a
+ * correct Chan* but with an incorrect Cname attached.
+ * Since the functions that open Aaccess (sysstat, syswstat, sys_stat)
+ * do not use the Cname*, this avoids an unnecessary clone.
+ */
+Chan*
+namec(char *aname, int amode, int omode, ulong perm)
+{
+ int n, prefix, len, t, nomount, npath;
+ Chan *c, *cnew;
+ Cname *cname;
+ Elemlist e;
+ Rune r;
+ Mhead *m;
+ char *createerr, tmperrbuf[ERRMAX];
+ char *name;
+
+ name = aname;
+ if(name[0] == '\0')
+ error("empty file name");
+ validname(name, 1);
+
+ /*
+ * Find the starting off point (the current slash, the root of
+ * a device tree, or the current dot) as well as the name to
+ * evaluate starting there.
+ */
+ nomount = 0;
+ switch(name[0]){
+ case '/':
+ c = up->slash;
+ incref(&c->ref);
+ break;
+
+ case '#':
+ nomount = 1;
+ up->genbuf[0] = '\0';
+ n = 0;
+ while(*name!='\0' && (*name != '/' || n < 2)){
+ if(n >= sizeof(up->genbuf)-1)
+ error(Efilename);
+ up->genbuf[n++] = *name++;
+ }
+ up->genbuf[n] = '\0';
+ /*
+ * noattach is sandboxing.
+ *
+ * the OK exceptions are:
+ * | it only gives access to pipes you create
+ * d this process's file descriptors
+ * e this process's environment
+ * the iffy exceptions are:
+ * c time and pid, but also cons and consctl
+ * p control of your own processes (and unfortunately
+ * any others left unprotected)
+ */
+ n = chartorune(&r, up->genbuf+1)+1;
+ /* actually / is caught by parsing earlier */
+ if(utfrune("M", r))
+ error(Enoattach);
+ if(up->pgrp->noattach && utfrune("|decp", r)==nil)
+ error(Enoattach);
+ t = devno(r, 1);
+ if(t == -1)
+ error(Ebadsharp);
+ c = devtab[t]->attach(up->genbuf+n);
+ break;
+
+ default:
+ c = up->dot;
+ incref(&c->ref);
+ break;
+ }
+ prefix = name - aname;
+
+ e.name = nil;
+ e.elems = nil;
+ e.off = nil;
+ e.nelems = 0;
+ if(waserror()){
+ cclose(c);
+ free(e.name);
+ free(e.elems);
+ free(e.off);
+//dumpmount();
+ nexterror();
+ }
+
+ /*
+ * Build a list of elements in the path.
+ */
+ parsename(name, &e);
+
+ /*
+ * On create, ....
+ */
+ if(amode == Acreate){
+ /* perm must have DMDIR if last element is / or /. */
+ if(e.mustbedir && !(perm&DMDIR)){
+ npath = e.nelems;
+ strcpy(tmperrbuf, "create without DMDIR");
+ goto NameError;
+ }
+
+ /* don't try to walk the last path element just yet. */
+ if(e.nelems == 0)
+ error(Eexist);
+ e.nelems--;
+ }
+
+ if(walk(&c, e.elems, e.nelems, nomount, &npath) < 0){
+ if(npath < 0 || npath > e.nelems){
+ print("namec %s walk error npath=%d\n", aname, npath);
+ nexterror();
+ }
+ strcpy(tmperrbuf, up->errstr);
+ NameError:
+ len = prefix+e.off[npath];
+ if(len < ERRMAX/3 || (name=mymemrchr(aname, '/', len))==nil || name==aname)
+ snprint(up->genbuf, sizeof up->genbuf, "%.*s", len, aname);
+ else
+ snprint(up->genbuf, sizeof up->genbuf, "...%.*s", (int)(len-(name-aname)), name);
+ snprint(up->errstr, ERRMAX, "%#q %s", up->genbuf, tmperrbuf);
+ nexterror();
+ }
+
+ if(e.mustbedir && !(c->qid.type&QTDIR)){
+ npath = e.nelems;
+ strcpy(tmperrbuf, "not a directory");
+ goto NameError;
+ }
+
+ if(amode == Aopen && (omode&3) == OEXEC && (c->qid.type&QTDIR)){
+ npath = e.nelems;
+ error("cannot exec directory");
+ }
+
+ switch(amode){
+ case Aaccess:
+ if(!nomount)
+ domount(&c, nil);
+ break;
+
+ case Abind:
+ m = nil;
+ if(!nomount)
+ domount(&c, &m);
+ if(c->umh != nil)
+ putmhead(c->umh);
+ c->umh = m;
+ break;
+
+ case Aremove:
+ case Aopen:
+ Open:
+ /* save the name; domount might change c */
+ cname = c->name;
+ incref(&cname->ref);
+ m = nil;
+ if(!nomount)
+ domount(&c, &m);
+
+ /* our own copy to open or remove */
+ c = cunique(c);
+
+ /* now it's our copy anyway, we can put the name back */
+ cnameclose(c->name);
+ c->name = cname;
+
+ switch(amode){
+ case Aremove:
+ putmhead(m);
+ break;
+
+ case Aopen:
+ case Acreate:
+if(c->umh != nil){
+ print("cunique umh Open\n");
+ putmhead(c->umh);
+ c->umh = nil;
+}
+
+ /* only save the mount head if it's a multiple element union */
+ if(m && m->mount && m->mount->next)
+ c->umh = m;
+ else
+ putmhead(m);
+
+ /* save registers else error() in open has wrong value of c saved */
+ saveregisters();
+
+ if(omode == OEXEC)
+ c->flag &= ~CCACHE;
+
+ c = devtab[c->type]->open(c, omode&~OCEXEC);
+
+ if(omode & OCEXEC)
+ c->flag |= CCEXEC;
+ if(omode & ORCLOSE)
+ c->flag |= CRCLOSE;
+ break;
+ }
+ break;
+
+ case Atodir:
+ /*
+ * Directories (e.g. for cd) are left before the mount point,
+ * so one may mount on / or . and see the effect.
+ */
+ if(!(c->qid.type & QTDIR))
+ error(Enotdir);
+ break;
+
+ case Amount:
+ /*
+ * When mounting on an already mounted upon directory,
+ * one wants subsequent mounts to be attached to the
+ * original directory, not the replacement. Don't domount.
+ */
+ break;
+
+ case Acreate:
+ /*
+ * We've already walked all but the last element.
+ * If the last exists, try to open it OTRUNC.
+ * If omode&OEXCL is set, just give up.
+ */
+ e.nelems++;
+ if(walk(&c, e.elems+e.nelems-1, 1, nomount, nil) == 0){
+ if(omode&OEXCL)
+ error(Eexist);
+ omode |= OTRUNC;
+ goto Open;
+ }
+
+ /*
+ * The semantics of the create(2) system call are that if the
+ * file exists and can be written, it is to be opened with truncation.
+ * On the other hand, the create(5) message fails if the file exists.
+ * If we get two create(2) calls happening simultaneously,
+ * they might both get here and send create(5) messages, but only
+ * one of the messages will succeed. To provide the expected create(2)
+ * semantics, the call with the failed message needs to try the above
+ * walk again, opening for truncation. This correctly solves the
+ * create/create race, in the sense that any observable outcome can
+ * be explained as one happening before the other.
+ * The create/create race is quite common. For example, it happens
+ * when two rc subshells simultaneously update the same
+ * environment variable.
+ *
+ * The implementation still admits a create/create/remove race:
+ * (A) walk to file, fails
+ * (B) walk to file, fails
+ * (A) create file, succeeds, returns
+ * (B) create file, fails
+ * (A) remove file, succeeds, returns
+ * (B) walk to file, return failure.
+ *
+ * This is hardly as common as the create/create race, and is really
+ * not too much worse than what might happen if (B) got a hold of a
+ * file descriptor and then the file was removed -- either way (B) can't do
+ * anything with the result of the create call. So we don't care about this race.
+ *
+ * Applications that care about more fine-grained decision of the races
+ * can use the OEXCL flag to get at the underlying create(5) semantics;
+ * by default we provide the common case.
+ *
+ * We need to stay behind the mount point in case we
+ * need to do the first walk again (should the create fail).
+ *
+ * We also need to cross the mount point and find the directory
+ * in the union in which we should be creating.
+ *
+ * The channel staying behind is c, the one moving forward is cnew.
+ */
+ m = nil;
+ cnew = nil; /* is this assignment necessary? */
+ if(!waserror()){ /* try create */
+ if(!nomount && findmount(&cnew, &m, c->type, c->dev, c->qid))
+ cnew = createdir(cnew, m);
+ else{
+ cnew = c;
+ incref(&cnew->ref);
+ }
+
+ /*
+ * We need our own copy of the Chan because we're
+ * about to send a create, which will move it. Once we have
+ * our own copy, we can fix the name, which might be wrong
+ * if findmount gave us a new Chan.
+ */
+ cnew = cunique(cnew);
+ cnameclose(cnew->name);
+ cnew->name = c->name;
+ incref(&cnew->name->ref);
+
+ devtab[cnew->type]->create(cnew, e.elems[e.nelems-1], omode&~(OEXCL|OCEXEC), perm);
+ poperror();
+ if(omode & OCEXEC)
+ cnew->flag |= CCEXEC;
+ if(omode & ORCLOSE)
+ cnew->flag |= CRCLOSE;
+ if(m)
+ putmhead(m);
+ cclose(c);
+ c = cnew;
+ c->name = addelem(c->name, e.elems[e.nelems-1]);
+ break;
+ }else{ /* create failed */
+ cclose(cnew);
+ if(m)
+ putmhead(m);
+ if(omode & OEXCL)
+ nexterror();
+ /* save error */
+ createerr = up->errstr;
+ up->errstr = tmperrbuf;
+ /* note: we depend that walk does not error */
+ if(walk(&c, e.elems+e.nelems-1, 1, nomount, nil) < 0){
+ up->errstr = createerr;
+ error(createerr); /* report true error */
+ }
+ up->errstr = createerr;
+ omode |= OTRUNC;
+ goto Open;
+ }
+ panic("namec: not reached");
+
+ default:
+ panic("unknown namec access %d\n", amode);
+ }
+
+ poperror();
+
+ /* place final element in genbuf for e.g. exec */
+ if(e.nelems > 0)
+ kstrcpy(up->genbuf, e.elems[e.nelems-1], sizeof up->genbuf);
+ else
+ kstrcpy(up->genbuf, ".", sizeof up->genbuf);
+ free(e.name);
+ free(e.elems);
+ free(e.off);
+
+ return c;
+}
+
+/*
+ * name is valid. skip leading / and ./ as much as possible
+ */
+char*
+skipslash(char *name)
+{
+ while(name[0]=='/' || (name[0]=='.' && (name[1]==0 || name[1]=='/')))
+ name++;
+ return name;
+}
+
+char isfrog[256]={
+ /*NUL*/ 1, 1, 1, 1, 1, 1, 1, 1, /* 0 */
+ /*BKS*/ 1, 1, 1, 1, 1, 1, 1, 1, /* 0x08 */
+ /*DLE*/ 1, 1, 1, 1, 1, 1, 1, 1, /* 0x10 */
+ /*CAN*/ 1, 1, 1, 1, 1, 1, 1, 1, /* 0x18 */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 */
+ 0, 0, 0, 0, 0, 0, 0, 1, /* 0x28 (1 is '/', 0x2F) */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x38 */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x48 */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x58 */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x68 */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 */
+ 0, 0, 0, 0, 0, 0, 0, 1, /* 0x78 (1 is DEL, 0x7F) */
+};
+
+/*
+ * Check that the name
+ * a) is in valid memory.
+ * b) is shorter than 2^16 bytes, so it can fit in a 9P string field.
+ * c) contains no frogs.
+ * The first byte is known to be addressible by the requester, so the
+ * routine works for kernel and user memory both.
+ * The parameter slashok flags whether a slash character is an error
+ * or a valid character.
+ */
+void
+validname(char *aname, int slashok)
+{
+ char *ename, *name;
+ int c;
+ Rune r;
+
+ name = aname;
+/*
+ if(((ulong)name & KZERO) != KZERO) {
+ p = name;
+ t = BY2PG-((ulong)p&(BY2PG-1));
+ while((ename=vmemchr(p, 0, t)) == nil) {
+ p += t;
+ t = BY2PG;
+ }
+ }else
+*/
+ ename = memchr(name, 0, (1<<16));
+
+ if(ename==nil || ename-name>=(1<<16))
+ error("name too long");
+
+ while(*name){
+ /* all characters above '~' are ok */
+ c = *(uchar*)name;
+ if(c >= Runeself)
+ name += chartorune(&r, name);
+ else{
+ if(isfrog[c])
+ if(!slashok || c!='/'){
+ snprint(up->genbuf, sizeof(up->genbuf), "%s: %q", Ebadchar, aname);
+ error(up->genbuf);
+ }
+ name++;
+ }
+ }
+}
+
+void
+isdir(Chan *c)
+{
+ if(c->qid.type & QTDIR)
+ return;
+ error(Enotdir);
+}
+
+/*
+ * This is necessary because there are many
+ * pointers to the top of a given mount list:
+ *
+ * - the mhead in the namespace hash table
+ * - the mhead in chans returned from findmount:
+ * used in namec and then by unionread.
+ * - the mhead in chans returned from createdir:
+ * used in the open/create race protect, which is gone.
+ *
+ * The RWlock in the Mhead protects the mount list it contains.
+ * The mount list is deleted when we cunmount.
+ * The RWlock ensures that nothing is using the mount list at that time.
+ *
+ * It is okay to replace c->mh with whatever you want as
+ * long as you are sure you have a unique reference to it.
+ *
+ * This comment might belong somewhere else.
+ */
+void
+putmhead(Mhead *m)
+{
+ if(m && decref(&m->ref) == 0){
+ m->mount = (Mount*)0xCafeBeef;
+ free(m);
+ }
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/dat.h b/sys/src/cmd/unix/drawterm/kern/dat.h
new file mode 100755
index 000000000..9d00d0085
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/dat.h
@@ -0,0 +1,519 @@
+#define KNAMELEN 28 /* max length of name held in kernel */
+#define DOMLEN 64
+
+#define BLOCKALIGN 8
+
+typedef struct Alarms Alarms;
+typedef struct Block Block;
+typedef struct CSN CSN;
+typedef struct Chan Chan;
+typedef struct Cmdbuf Cmdbuf;
+typedef struct Cmdtab Cmdtab;
+typedef struct Cname Cname;
+typedef struct Conf Conf;
+typedef struct Dev Dev;
+typedef struct Dirtab Dirtab;
+typedef struct Edfinterface Edfinterface;
+typedef struct Egrp Egrp;
+typedef struct Evalue Evalue;
+typedef struct Fgrp Fgrp;
+typedef struct FPsave FPsave;
+typedef struct DevConf DevConf;
+typedef struct Label Label;
+typedef struct List List;
+typedef struct Log Log;
+typedef struct Logflag Logflag;
+typedef struct Mntcache Mntcache;
+typedef struct Mount Mount;
+typedef struct Mntrpc Mntrpc;
+typedef struct Mntwalk Mntwalk;
+typedef struct Mnt Mnt;
+typedef struct Mhead Mhead;
+typedef struct Note Note;
+typedef struct Page Page;
+typedef struct Palloc Palloc;
+typedef struct Perf Perf;
+typedef struct Pgrps Pgrps;
+typedef struct PhysUart PhysUart;
+typedef struct Pgrp Pgrp;
+typedef struct Physseg Physseg;
+typedef struct Proc Proc;
+typedef struct Pte Pte;
+typedef struct Pthash Pthash;
+typedef struct Queue Queue;
+typedef struct Ref Ref;
+typedef struct Rendez Rendez;
+typedef struct Rgrp Rgrp;
+typedef struct RWlock RWlock;
+typedef struct Schedq Schedq;
+typedef struct Segment Segment;
+typedef struct Session Session;
+typedef struct Task Task;
+typedef struct Talarm Talarm;
+typedef struct Timer Timer;
+typedef struct Uart Uart;
+typedef struct Ureg Ureg;
+typedef struct Waitq Waitq;
+typedef struct Walkqid Walkqid;
+typedef int Devgen(Chan*, char*, Dirtab*, int, int, Dir*);
+
+#include "fcall.h"
+
+enum
+{
+ SnarfSize = 64*1024,
+};
+
+struct Conf
+{
+ ulong nmach; /* processors */
+ ulong nproc; /* processes */
+ ulong monitor; /* has monitor? */
+ ulong npage0; /* total physical pages of memory */
+ ulong npage1; /* total physical pages of memory */
+ ulong npage; /* total physical pages of memory */
+ ulong upages; /* user page pool */
+ ulong nimage; /* number of page cache image headers */
+ ulong nswap; /* number of swap pages */
+ int nswppo; /* max # of pageouts per segment pass */
+ ulong base0; /* base of bank 0 */
+ ulong base1; /* base of bank 1 */
+ ulong copymode; /* 0 is copy on write, 1 is copy on reference */
+ ulong ialloc; /* max interrupt time allocation in bytes */
+ ulong pipeqsize; /* size in bytes of pipe queues */
+ int nuart; /* number of uart devices */
+};
+
+struct Label
+{
+ jmp_buf buf;
+};
+
+struct Ref
+{
+ Lock lk;
+ long ref;
+};
+
+struct Rendez
+{
+ Lock lk;
+ Proc *p;
+};
+
+struct RWlock /* changed from kernel */
+{
+ int readers;
+ Lock lk;
+ QLock x;
+ QLock k;
+};
+
+struct Talarm
+{
+ Lock lk;
+ Proc *list;
+};
+
+struct Alarms
+{
+ QLock lk;
+ Proc *head;
+};
+
+/*
+ * Access types in namec & channel flags
+ */
+enum
+{
+ Aaccess, /* as in stat, wstat */
+ Abind, /* for left-hand-side of bind */
+ Atodir, /* as in chdir */
+ Aopen, /* for i/o */
+ Amount, /* to be mounted or mounted upon */
+ Acreate, /* is to be created */
+ Aremove, /* will be removed by caller */
+
+ COPEN = 0x0001, /* for i/o */
+ CMSG = 0x0002, /* the message channel for a mount */
+/* CCREATE = 0x0004, permits creation if c->mnt */
+ CCEXEC = 0x0008, /* close on exec */
+ CFREE = 0x0010, /* not in use */
+ CRCLOSE = 0x0020, /* remove on close */
+ CCACHE = 0x0080, /* client cache */
+};
+
+/* flag values */
+enum
+{
+ BINTR = (1<<0),
+ BFREE = (1<<1),
+ Bipck = (1<<2), /* ip checksum */
+ Budpck = (1<<3), /* udp checksum */
+ Btcpck = (1<<4), /* tcp checksum */
+ Bpktck = (1<<5), /* packet checksum */
+};
+
+struct Block
+{
+ Block* next;
+ Block* list;
+ uchar* rp; /* first unconsumed byte */
+ uchar* wp; /* first empty byte */
+ uchar* lim; /* 1 past the end of the buffer */
+ uchar* base; /* start of the buffer */
+ void (*free)(Block*);
+ ushort flag;
+ ushort checksum; /* IP checksum of complete packet (minus media header) */
+};
+#define BLEN(s) ((s)->wp - (s)->rp)
+#define BALLOC(s) ((s)->lim - (s)->base)
+
+struct Chan
+{
+ Ref ref;
+ Chan* next; /* allocation */
+ Chan* link;
+ vlong offset; /* in file */
+ ushort type;
+ ulong dev;
+ ushort mode; /* read/write */
+ ushort flag;
+ Qid qid;
+ int fid; /* for devmnt */
+ ulong iounit; /* chunk size for i/o; 0==default */
+ Mhead* umh; /* mount point that derived Chan; used in unionread */
+ Chan* umc; /* channel in union; held for union read */
+ QLock umqlock; /* serialize unionreads */
+ int uri; /* union read index */
+ int dri; /* devdirread index */
+ ulong mountid;
+ Mntcache *mcp; /* Mount cache pointer */
+ Mnt *mux; /* Mnt for clients using me for messages */
+ void* aux;
+ Qid pgrpid; /* for #p/notepg */
+ ulong mid; /* for ns in devproc */
+ Chan* mchan; /* channel to mounted server */
+ Qid mqid; /* qid of root of mount point */
+ Session*session;
+ Cname *name;
+};
+
+struct Cname
+{
+ Ref ref;
+ int alen; /* allocated length */
+ int len; /* strlen(s) */
+ char *s;
+};
+
+struct Dev
+{
+ int dc;
+ char* name;
+
+ void (*reset)(void);
+ void (*init)(void);
+ void (*shutdown)(void);
+ Chan* (*attach)(char*);
+ Walkqid* (*walk)(Chan*, Chan*, char**, int);
+ int (*stat)(Chan*, uchar*, int);
+ Chan* (*open)(Chan*, int);
+ void (*create)(Chan*, char*, int, ulong);
+ void (*close)(Chan*);
+ long (*read)(Chan*, void*, long, vlong);
+ Block* (*bread)(Chan*, long, ulong);
+ long (*write)(Chan*, void*, long, vlong);
+ long (*bwrite)(Chan*, Block*, ulong);
+ void (*remove)(Chan*);
+ int (*wstat)(Chan*, uchar*, int);
+ void (*power)(int); /* power mgt: power(1) => on, power (0) => off */
+ int (*config)(int, char*, DevConf*); // returns nil on error
+};
+
+struct Dirtab
+{
+ char name[KNAMELEN];
+ Qid qid;
+ vlong length;
+ ulong perm;
+};
+
+struct Walkqid
+{
+ Chan *clone;
+ int nqid;
+ Qid qid[1];
+};
+
+enum
+{
+ NSMAX = 1000,
+ NSLOG = 7,
+ NSCACHE = (1<<NSLOG),
+};
+
+struct Mntwalk /* state for /proc/#/ns */
+{
+ int cddone;
+ ulong id;
+ Mhead* mh;
+ Mount* cm;
+};
+
+struct Mount
+{
+ ulong mountid;
+ Mount* next;
+ Mhead* head;
+ Mount* copy;
+ Mount* order;
+ Chan* to; /* channel replacing channel */
+ int mflag;
+ char *spec;
+};
+
+struct Mhead
+{
+ Ref ref;
+ RWlock lock;
+ Chan* from; /* channel mounted upon */
+ Mount* mount; /* what's mounted upon it */
+ Mhead* hash; /* Hash chain */
+};
+
+struct Mnt
+{
+ Lock lk;
+ /* references are counted using c->ref; channels on this mount point incref(c->mchan) == Mnt.c */
+ Chan *c; /* Channel to file service */
+ Proc *rip; /* Reader in progress */
+ Mntrpc *queue; /* Queue of pending requests on this channel */
+ ulong id; /* Multiplexer id for channel check */
+ Mnt *list; /* Free list */
+ int flags; /* cache */
+ int msize; /* data + IOHDRSZ */
+ char *version; /* 9P version */
+ Queue *q; /* input queue */
+};
+
+enum
+{
+ NUser, /* note provided externally */
+ NExit, /* deliver note quietly */
+ NDebug, /* print debug message */
+};
+
+struct Note
+{
+ char msg[ERRMAX];
+ int flag; /* whether system posted it */
+};
+
+enum
+{
+ RENDLOG = 5,
+ RENDHASH = 1<<RENDLOG, /* Hash to lookup rendezvous tags */
+ MNTLOG = 5,
+ MNTHASH = 1<<MNTLOG, /* Hash to walk mount table */
+ NFD = 100, /* per process file descriptors */
+ PGHLOG = 9,
+ PGHSIZE = 1<<PGHLOG, /* Page hash for image lookup */
+};
+#define REND(p,s) ((p)->rendhash[(s)&((1<<RENDLOG)-1)])
+#define MOUNTH(p,qid) ((p)->mnthash[(qid).path&((1<<MNTLOG)-1)])
+
+struct Pgrp
+{
+ Ref ref; /* also used as a lock when mounting */
+ int noattach;
+ ulong pgrpid;
+ QLock debug; /* single access via devproc.c */
+ RWlock ns; /* Namespace n read/one write lock */
+ Mhead *mnthash[MNTHASH];
+};
+
+struct Rgrp
+{
+ Ref ref;
+ Proc *rendhash[RENDHASH]; /* Rendezvous tag hash */
+};
+
+struct Egrp
+{
+ Ref ref;
+ RWlock lk;
+ Evalue **ent;
+ int nent;
+ int ment;
+ ulong path; /* qid.path of next Evalue to be allocated */
+ ulong vers; /* of Egrp */
+};
+
+struct Evalue
+{
+ char *name;
+ char *value;
+ int len;
+ Evalue *link;
+ Qid qid;
+};
+
+struct Fgrp
+{
+ Ref ref;
+ Chan **fd;
+ int nfd; /* number allocated */
+ int maxfd; /* highest fd in use */
+ int exceed; /* debugging */
+};
+
+enum
+{
+ DELTAFD = 20, /* incremental increase in Fgrp.fd's */
+ NERR = 20
+};
+
+typedef uvlong Ticks;
+
+enum
+{
+ Running,
+ Rendezvous,
+ Wakeme,
+};
+
+struct Proc
+{
+ uint state;
+ uint mach;
+
+ ulong pid;
+ ulong parentpid;
+
+ Pgrp *pgrp; /* Process group for namespace */
+ Fgrp *fgrp; /* File descriptor group */
+ Rgrp *rgrp;
+
+ Lock rlock; /* sync sleep/wakeup with postnote */
+ Rendez *r; /* rendezvous point slept on */
+ Rendez sleep; /* place for syssleep/debug */
+ int notepending; /* note issued but not acted on */
+ int kp; /* true if a kernel process */
+
+ void* rendtag; /* Tag for rendezvous */
+ void* rendval; /* Value for rendezvous */
+ Proc *rendhash; /* Hash list for tag values */
+
+ int nerrlab;
+ Label errlab[NERR];
+ char user[KNAMELEN];
+ char *syserrstr; /* last error from a system call, errbuf0 or 1 */
+ char *errstr; /* reason we're unwinding the error stack, errbuf1 or 0 */
+ char errbuf0[ERRMAX];
+ char errbuf1[ERRMAX];
+ char genbuf[128]; /* buffer used e.g. for last name element from namec */
+ char text[KNAMELEN];
+
+ Chan *slash;
+ Chan *dot;
+
+ Proc *qnext;
+
+ void (*fn)(void*);
+ void *arg;
+
+ char oproc[1024]; /* reserved for os */
+
+};
+
+enum
+{
+ PRINTSIZE = 256,
+ MAXCRYPT = 127,
+ NUMSIZE = 12, /* size of formatted number */
+ MB = (1024*1024),
+ READSTR = 1000, /* temporary buffer size for device reads */
+};
+
+extern char* conffile;
+extern int cpuserver;
+extern Dev* devtab[];
+extern char *eve;
+extern char hostdomain[];
+extern uchar initcode[];
+extern Queue* kbdq;
+extern Queue* kprintoq;
+extern Ref noteidalloc;
+extern Palloc palloc;
+extern Queue *serialoq;
+extern char* statename[];
+extern int nsyscall;
+extern char *sysname;
+extern uint qiomaxatomic;
+extern Conf conf;
+enum
+{
+ LRESPROF = 3,
+};
+
+/*
+ * action log
+ */
+struct Log {
+ Lock lk;
+ int opens;
+ char* buf;
+ char *end;
+ char *rptr;
+ int len;
+ int nlog;
+ int minread;
+
+ int logmask; /* mask of things to debug */
+
+ QLock readq;
+ Rendez readr;
+};
+
+struct Logflag {
+ char* name;
+ int mask;
+};
+
+enum
+{
+ NCMDFIELD = 128
+};
+
+struct Cmdbuf
+{
+ char *buf;
+ char **f;
+ int nf;
+};
+
+struct Cmdtab
+{
+ int index; /* used by client to switch on result */
+ char *cmd; /* command name */
+ int narg; /* expected #args; 0 ==> variadic */
+};
+
+/* queue state bits, Qmsg, Qcoalesce, and Qkick can be set in qopen */
+enum
+{
+ /* Queue.state */
+ Qstarve = (1<<0), /* consumer starved */
+ Qmsg = (1<<1), /* message stream */
+ Qclosed = (1<<2), /* queue has been closed/hungup */
+ Qflow = (1<<3), /* producer flow controlled */
+ Qcoalesce = (1<<4), /* coallesce packets on read */
+ Qkick = (1<<5), /* always call the kick routine after qwrite */
+};
+
+#define DEVDOTDOT -1
+
+extern Proc *_getproc(void);
+extern void _setproc(Proc*);
+#define up (_getproc())
diff --git a/sys/src/cmd/unix/drawterm/kern/data.c b/sys/src/cmd/unix/drawterm/kern/data.c
new file mode 100755
index 000000000..735e5e0c4
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/data.c
@@ -0,0 +1,31 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+Proc *up;
+Conf conf =
+{
+ 1,
+ 100,
+ 0,
+ 1024*1024*1024,
+ 1024*1024*1024,
+ 1024*1024*1024,
+ 1024*1024*1024,
+ 1024*1024*1024,
+ 1024*1024*1024,
+ 1024*1024*1024,
+ 1024*1024*1024,
+ 1024*1024*1024,
+ 1024*1024*1024,
+ 1024*1024*1024,
+ 1024*1024*1024,
+ 0,
+};
+
+char *eve = "eve";
+ulong kerndate;
+int cpuserver;
+char hostdomain[] = "drawterm.net";
diff --git a/sys/src/cmd/unix/drawterm/kern/dev.c b/sys/src/cmd/unix/drawterm/kern/dev.c
new file mode 100755
index 000000000..83bf624f3
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/dev.c
@@ -0,0 +1,468 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+extern ulong kerndate;
+
+void
+mkqid(Qid *q, vlong path, ulong vers, int type)
+{
+ q->type = type;
+ q->vers = vers;
+ q->path = path;
+}
+
+int
+devno(int c, int user)
+{
+ int i;
+
+ for(i = 0; devtab[i] != nil; i++) {
+ if(devtab[i]->dc == c)
+ return i;
+ }
+ if(user == 0)
+ panic("devno %C 0x%ux", c, c);
+
+ return -1;
+}
+
+void
+devdir(Chan *c, Qid qid, char *n, vlong length, char *user, long perm, Dir *db)
+{
+ db->name = n;
+ if(c->flag&CMSG)
+ qid.type |= QTMOUNT;
+ db->qid = qid;
+ db->type = devtab[c->type]->dc;
+ db->dev = c->dev;
+ db->mode = perm;
+ db->mode |= qid.type << 24;
+ db->atime = seconds();
+ db->mtime = kerndate;
+ db->length = length;
+ db->uid = user;
+ db->gid = eve;
+ db->muid = user;
+}
+
+/*
+ * (here, Devgen is the prototype; devgen is the function in dev.c.)
+ *
+ * a Devgen is expected to return the directory entry for ".."
+ * if you pass it s==DEVDOTDOT (-1). otherwise...
+ *
+ * there are two contradictory rules.
+ *
+ * (i) if c is a directory, a Devgen is expected to list its children
+ * as you iterate s.
+ *
+ * (ii) whether or not c is a directory, a Devgen is expected to list
+ * its siblings as you iterate s.
+ *
+ * devgen always returns the list of children in the root
+ * directory. thus it follows (i) when c is the root and (ii) otherwise.
+ * many other Devgens follow (i) when c is a directory and (ii) otherwise.
+ *
+ * devwalk assumes (i). it knows that devgen breaks (i)
+ * for children that are themselves directories, and explicitly catches them.
+ *
+ * devstat assumes (ii). if the Devgen in question follows (i)
+ * for this particular c, devstat will not find the necessary info.
+ * with our particular Devgen functions, this happens only for
+ * directories, so devstat makes something up, assuming
+ * c->name, c->qid, eve, DMDIR|0555.
+ *
+ * devdirread assumes (i). the callers have to make sure
+ * that the Devgen satisfies (i) for the chan being read.
+ */
+/*
+ * the zeroth element of the table MUST be the directory itself for ..
+*/
+int
+devgen(Chan *c, char *name, Dirtab *tab, int ntab, int i, Dir *dp)
+{
+ if(tab == 0)
+ return -1;
+ if(i == DEVDOTDOT){
+ /* nothing */
+ }else if(name){
+ for(i=1; i<ntab; i++)
+ if(strcmp(tab[i].name, name) == 0)
+ break;
+ if(i==ntab)
+ return -1;
+ tab += i;
+ }else{
+ /* skip over the first element, that for . itself */
+ i++;
+ if(i >= ntab)
+ return -1;
+ tab += i;
+ }
+ devdir(c, tab->qid, tab->name, tab->length, eve, tab->perm, dp);
+ return 1;
+}
+
+void
+devreset(void)
+{
+}
+
+void
+devinit(void)
+{
+}
+
+void
+devshutdown(void)
+{
+}
+
+Chan*
+devattach(int tc, char *spec)
+{
+ Chan *c;
+ char *buf;
+
+ c = newchan();
+ mkqid(&c->qid, 0, 0, QTDIR);
+ c->type = devno(tc, 0);
+ if(spec == nil)
+ spec = "";
+ buf = smalloc(4+strlen(spec)+1);
+ sprint(buf, "#%C%s", tc, spec);
+ c->name = newcname(buf);
+ free(buf);
+ return c;
+}
+
+
+Chan*
+devclone(Chan *c)
+{
+ Chan *nc;
+
+ if(c->flag & COPEN)
+ panic("clone of open file type %C\n", devtab[c->type]->dc);
+
+ nc = newchan();
+
+ nc->type = c->type;
+ nc->dev = c->dev;
+ nc->mode = c->mode;
+ nc->qid = c->qid;
+ nc->offset = c->offset;
+ nc->umh = nil;
+ nc->mountid = c->mountid;
+ nc->aux = c->aux;
+ nc->pgrpid = c->pgrpid;
+ nc->mid = c->mid;
+ nc->mqid = c->mqid;
+ nc->mcp = c->mcp;
+ return nc;
+}
+
+Walkqid*
+devwalk(Chan *c, Chan *nc, char **name, int nname, Dirtab *tab, int ntab, Devgen *gen)
+{
+ int i, j, alloc;
+ Walkqid *wq;
+ char *n;
+ Dir dir;
+
+ if(nname > 0)
+ isdir(c);
+
+ alloc = 0;
+ wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+ if(waserror()){
+ if(alloc && wq->clone!=nil)
+ cclose(wq->clone);
+ free(wq);
+ return nil;
+ }
+ if(nc == nil){
+ nc = devclone(c);
+ nc->type = 0; /* device doesn't know about this channel yet */
+ alloc = 1;
+ }
+ wq->clone = nc;
+
+ for(j=0; j<nname; j++){
+ if(!(nc->qid.type&QTDIR)){
+ if(j==0)
+ error(Enotdir);
+ goto Done;
+ }
+ n = name[j];
+ if(strcmp(n, ".") == 0){
+ Accept:
+ wq->qid[wq->nqid++] = nc->qid;
+ continue;
+ }
+ if(strcmp(n, "..") == 0){
+ if((*gen)(nc, nil, tab, ntab, DEVDOTDOT, &dir) != 1){
+ print("devgen walk .. in dev%s %llux broken\n",
+ devtab[nc->type]->name, nc->qid.path);
+ error("broken devgen");
+ }
+ nc->qid = dir.qid;
+ goto Accept;
+ }
+ /*
+ * Ugly problem: If we're using devgen, make sure we're
+ * walking the directory itself, represented by the first
+ * entry in the table, and not trying to step into a sub-
+ * directory of the table, e.g. /net/net. Devgen itself
+ * should take care of the problem, but it doesn't have
+ * the necessary information (that we're doing a walk).
+ */
+ if(gen==devgen && nc->qid.path!=tab[0].qid.path)
+ goto Notfound;
+ for(i=0;; i++) {
+ switch((*gen)(nc, n, tab, ntab, i, &dir)){
+ case -1:
+ Notfound:
+ if(j == 0)
+ error(Enonexist);
+ kstrcpy(up->errstr, Enonexist, ERRMAX);
+ goto Done;
+ case 0:
+ continue;
+ case 1:
+ if(strcmp(n, dir.name) == 0){
+ nc->qid = dir.qid;
+ goto Accept;
+ }
+ continue;
+ }
+ }
+ }
+ /*
+ * We processed at least one name, so will return some data.
+ * If we didn't process all nname entries succesfully, we drop
+ * the cloned channel and return just the Qids of the walks.
+ */
+Done:
+ poperror();
+ if(wq->nqid < nname){
+ if(alloc)
+ cclose(wq->clone);
+ wq->clone = nil;
+ }else if(wq->clone){
+ /* attach cloned channel to same device */
+ wq->clone->type = c->type;
+ }
+ return wq;
+}
+
+int
+devstat(Chan *c, uchar *db, int n, Dirtab *tab, int ntab, Devgen *gen)
+{
+ int i;
+ Dir dir;
+ char *p, *elem;
+
+ for(i=0;; i++)
+ switch((*gen)(c, nil, tab, ntab, i, &dir)){
+ case -1:
+ if(c->qid.type & QTDIR){
+ if(c->name == nil)
+ elem = "???";
+ else if(strcmp(c->name->s, "/") == 0)
+ elem = "/";
+ else
+ for(elem=p=c->name->s; *p; p++)
+ if(*p == '/')
+ elem = p+1;
+ devdir(c, c->qid, elem, 0, eve, DMDIR|0555, &dir);
+ n = convD2M(&dir, db, n);
+ if(n == 0)
+ error(Ebadarg);
+ return n;
+ }
+ print("devstat %C %llux\n", devtab[c->type]->dc, c->qid.path);
+
+ error(Enonexist);
+ case 0:
+ break;
+ case 1:
+ if(c->qid.path == dir.qid.path) {
+ if(c->flag&CMSG)
+ dir.mode |= DMMOUNT;
+ n = convD2M(&dir, db, n);
+ if(n == 0)
+ error(Ebadarg);
+ return n;
+ }
+ break;
+ }
+ error(Egreg); /* not reached? */
+ return -1;
+}
+
+long
+devdirread(Chan *c, char *d, long n, Dirtab *tab, int ntab, Devgen *gen)
+{
+ long m, dsz;
+ struct{
+ Dir d;
+ char slop[100];
+ }dir;
+
+ for(m=0; m<n; c->dri++) {
+ switch((*gen)(c, nil, tab, ntab, c->dri, &dir.d)){
+ case -1:
+ return m;
+
+ case 0:
+ break;
+
+ case 1:
+ dsz = convD2M(&dir.d, (uchar*)d, n-m);
+ if(dsz <= BIT16SZ){ /* <= not < because this isn't stat; read is stuck */
+ if(m == 0)
+ error(Eshort);
+ return m;
+ }
+ m += dsz;
+ d += dsz;
+ break;
+ }
+ }
+
+ return m;
+}
+
+/*
+ * error(Eperm) if open permission not granted for up->user.
+ */
+void
+devpermcheck(char *fileuid, ulong perm, int omode)
+{
+ ulong t;
+ static int access[] = { 0400, 0200, 0600, 0100 };
+
+ if(strcmp(up->user, fileuid) == 0)
+ perm <<= 0;
+ else
+ if(strcmp(up->user, eve) == 0)
+ perm <<= 3;
+ else
+ perm <<= 6;
+
+ t = access[omode&3];
+ if((t&perm) != t)
+ error(Eperm);
+}
+
+Chan*
+devopen(Chan *c, int omode, Dirtab *tab, int ntab, Devgen *gen)
+{
+ int i;
+ Dir dir;
+
+ for(i=0;; i++) {
+ switch((*gen)(c, nil, tab, ntab, i, &dir)){
+ case -1:
+ goto Return;
+ case 0:
+ break;
+ case 1:
+ if(c->qid.path == dir.qid.path) {
+ devpermcheck(dir.uid, dir.mode, omode);
+ goto Return;
+ }
+ break;
+ }
+ }
+Return:
+ c->offset = 0;
+ if((c->qid.type&QTDIR) && omode!=OREAD)
+ error(Eperm);
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ return c;
+}
+
+void
+devcreate(Chan *c, char *name, int mode, ulong perm)
+{
+ USED(c);
+ USED(name);
+ USED(mode);
+ USED(perm);
+
+ error(Eperm);
+}
+
+Block*
+devbread(Chan *c, long n, ulong offset)
+{
+ Block *bp;
+
+ bp = allocb(n);
+ if(bp == 0)
+ error(Enomem);
+ if(waserror()) {
+ freeb(bp);
+ nexterror();
+ }
+ bp->wp += devtab[c->type]->read(c, bp->wp, n, offset);
+ poperror();
+ return bp;
+}
+
+long
+devbwrite(Chan *c, Block *bp, ulong offset)
+{
+ long n;
+
+ if(waserror()) {
+ freeb(bp);
+ nexterror();
+ }
+ n = devtab[c->type]->write(c, bp->rp, BLEN(bp), offset);
+ poperror();
+ freeb(bp);
+
+ return n;
+}
+
+void
+devremove(Chan *c)
+{
+ USED(c);
+ error(Eperm);
+}
+
+int
+devwstat(Chan *c, uchar *a, int n)
+{
+ USED(c);
+ USED(a);
+ USED(n);
+
+ error(Eperm);
+ return 0;
+}
+
+void
+devpower(int a)
+{
+ USED(a);
+ error(Eperm);
+}
+
+int
+devconfig(int a, char *b, DevConf *c)
+{
+ USED(a);
+ USED(b);
+ USED(c);
+ error(Eperm);
+ return 0;
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/devaudio-none.c b/sys/src/cmd/unix/drawterm/kern/devaudio-none.c
new file mode 100755
index 000000000..57a536651
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devaudio-none.c
@@ -0,0 +1,49 @@
+/*
+ * Linux and BSD
+ */
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include "devaudio.h"
+
+/* maybe this should return -1 instead of sysfatal */
+void
+audiodevopen(void)
+{
+ error("no audio support");
+}
+
+void
+audiodevclose(void)
+{
+ error("no audio support");
+}
+
+int
+audiodevread(void *a, int n)
+{
+ error("no audio support");
+ return -1;
+}
+
+int
+audiodevwrite(void *a, int n)
+{
+ error("no audio support");
+ return -1;
+}
+
+void
+audiodevsetvol(int what, int left, int right)
+{
+ error("no audio support");
+}
+
+void
+audiodevgetvol(int what, int *left, int *right)
+{
+ error("no audio support");
+}
+
diff --git a/sys/src/cmd/unix/drawterm/kern/devaudio-unix.c b/sys/src/cmd/unix/drawterm/kern/devaudio-unix.c
new file mode 100755
index 000000000..ad5af7458
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devaudio-unix.c
@@ -0,0 +1,183 @@
+/*
+ * Linux and BSD
+ */
+#include <sys/ioctl.h>
+#ifdef __linux__
+#include <linux/soundcard.h>
+#else
+#include <sys/soundcard.h>
+#endif
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include "devaudio.h"
+
+enum
+{
+ Channels = 2,
+ Rate = 44100,
+ Bits = 16,
+ Bigendian = 1,
+};
+
+static int afd = -1;
+static int cfd= -1;
+static int speed;
+
+/* maybe this should return -1 instead of sysfatal */
+void
+audiodevopen(void)
+{
+ int t;
+ ulong ul;
+
+ afd = -1;
+ cfd = -1;
+ if((afd = open("/dev/dsp", OWRITE)) < 0)
+ goto err;
+ if((cfd = open("/dev/mixer", ORDWR)) < 0)
+ goto err;
+
+ t = Bits;
+ if(ioctl(afd, SNDCTL_DSP_SAMPLESIZE, &t) < 0)
+ goto err;
+
+ t = Channels-1;
+ if(ioctl(afd, SNDCTL_DSP_STEREO, &t) < 0)
+ goto err;
+
+ speed = Rate;
+ ul = Rate;
+ if(ioctl(afd, SNDCTL_DSP_SPEED, &ul) < 0)
+ goto err;
+
+ return;
+
+err:
+ if(afd >= 0)
+ close(afd);
+ afd = -1;
+ oserror();
+}
+
+void
+audiodevclose(void)
+{
+ close(afd);
+ close(cfd);
+ afd = -1;
+ cfd = -1;
+}
+
+static struct {
+ int id9;
+ int id;
+} names[] = {
+ Vaudio, SOUND_MIXER_VOLUME,
+ Vbass, SOUND_MIXER_BASS,
+ Vtreb, SOUND_MIXER_TREBLE,
+ Vline, SOUND_MIXER_LINE,
+ Vpcm, SOUND_MIXER_PCM,
+ Vsynth, SOUND_MIXER_SYNTH,
+ Vcd, SOUND_MIXER_CD,
+ Vmic, SOUND_MIXER_MIC,
+// "record", SOUND_MIXER_RECLEV,
+// "mix", SOUND_MIXER_IMIX,
+// "pcm2", SOUND_MIXER_ALTPCM,
+ Vspeaker, SOUND_MIXER_SPEAKER
+// "line1", SOUND_MIXER_LINE1,
+// "line2", SOUND_MIXER_LINE2,
+// "line3", SOUND_MIXER_LINE3,
+// "digital1", SOUND_MIXER_DIGITAL1,
+// "digital2", SOUND_MIXER_DIGITAL2,
+// "digital3", SOUND_MIXER_DIGITAL3,
+// "phonein", SOUND_MIXER_PHONEIN,
+// "phoneout", SOUND_MIXER_PHONEOUT,
+// "radio", SOUND_MIXER_RADIO,
+// "video", SOUND_MIXER_VIDEO,
+// "monitor", SOUND_MIXER_MONITOR,
+// "igain", SOUND_MIXER_IGAIN,
+// "ogain", SOUND_MIXER_OGAIN,
+};
+
+static int
+lookname(int id9)
+{
+ int i;
+
+ for(i=0; i<nelem(names); i++)
+ if(names[i].id9 == id9)
+ return names[i].id;
+ return -1;
+}
+
+void
+audiodevsetvol(int what, int left, int right)
+{
+ int id;
+ ulong x;
+ int can, v;
+
+ if(cfd < 0)
+ error("audio device not open");
+ if(what == Vspeed){
+ x = left;
+ if(ioctl(afd, SNDCTL_DSP_SPEED, &x) < 0)
+ oserror();
+ speed = x;
+ return;
+ }
+ if((id = lookname(what)) < 0)
+ return;
+ if(ioctl(cfd, SOUND_MIXER_READ_DEVMASK, &can) < 0)
+ can = ~0;
+ if(!(can & (1<<id)))
+ return;
+ v = left | (right<<8);
+ if(ioctl(cfd, MIXER_WRITE(id), &v) < 0)
+ oserror();
+}
+
+void
+audiodevgetvol(int what, int *left, int *right)
+{
+ int id;
+ int can, v;
+
+ if(cfd < 0)
+ error("audio device not open");
+ if(what == Vspeed){
+ *left = *right = speed;
+ return;
+ }
+ if((id = lookname(what)) < 0)
+ return;
+ if(ioctl(cfd, SOUND_MIXER_READ_DEVMASK, &can) < 0)
+ can = ~0;
+ if(!(can & (1<<id)))
+ return;
+ if(ioctl(cfd, MIXER_READ(id), &v) < 0)
+ oserror();
+ *left = v&0xFF;
+ *right = (v>>8)&0xFF;
+}
+
+int
+audiodevwrite(void *v, int n)
+{
+ int m, tot;
+
+ for(tot=0; tot<n; tot+=m)
+ if((m = write(afd, (uchar*)v+tot, n-tot)) <= 0)
+ oserror();
+ return tot;
+}
+
+int
+audiodevread(void *v, int n)
+{
+ error("no reading");
+ return -1;
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/devaudio.c b/sys/src/cmd/unix/drawterm/kern/devaudio.c
new file mode 100755
index 000000000..b6d0cc512
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devaudio.c
@@ -0,0 +1,372 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include "devaudio.h"
+
+enum
+{
+ Qdir = 0,
+ Qaudio,
+ Qvolume,
+
+ Aclosed = 0,
+ Aread,
+ Awrite,
+
+ Speed = 44100,
+ Ncmd = 50, /* max volume command words */
+};
+
+Dirtab
+audiodir[] =
+{
+ ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
+ "audio", {Qaudio}, 0, 0666,
+ "volume", {Qvolume}, 0, 0666,
+};
+
+static struct
+{
+ QLock lk;
+ Rendez vous;
+ int amode; /* Aclosed/Aread/Awrite for /audio */
+} audio;
+
+#define aqlock(a) qlock(&(a)->lk)
+#define aqunlock(a) qunlock(&(a)->lk)
+
+static struct
+{
+ char* name;
+ int flag;
+ int ilval; /* initial values */
+ int irval;
+} volumes[] =
+{
+ "audio", Fout, 50, 50,
+ "synth", Fin|Fout, 0, 0,
+ "cd", Fin|Fout, 0, 0,
+ "line", Fin|Fout, 0, 0,
+ "mic", Fin|Fout|Fmono, 0, 0,
+ "speaker", Fout|Fmono, 0, 0,
+
+ "treb", Fout, 50, 50,
+ "bass", Fout, 50, 50,
+
+ "speed", Fin|Fout|Fmono, Speed, Speed,
+ 0
+};
+
+static char Emode[] = "illegal open mode";
+static char Evolume[] = "illegal volume specifier";
+
+static void
+resetlevel(void)
+{
+ int i;
+
+ for(i=0; volumes[i].name; i++)
+ audiodevsetvol(i, volumes[i].ilval, volumes[i].irval);
+}
+
+static void
+audioinit(void)
+{
+}
+
+static Chan*
+audioattach(char *param)
+{
+ return devattach('A', param);
+}
+
+static Walkqid*
+audiowalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, audiodir, nelem(audiodir), devgen);
+}
+
+static int
+audiostat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, audiodir, nelem(audiodir), devgen);
+}
+
+static Chan*
+audioopen(Chan *c, int omode)
+{
+ int amode;
+
+ switch((ulong)c->qid.path) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qvolume:
+ case Qdir:
+ break;
+
+ case Qaudio:
+ amode = Awrite;
+ if((omode&7) == OREAD)
+ amode = Aread;
+ aqlock(&audio);
+ if(waserror()){
+ aqunlock(&audio);
+ nexterror();
+ }
+ if(audio.amode != Aclosed)
+ error(Einuse);
+ audiodevopen();
+ audio.amode = amode;
+ poperror();
+ aqunlock(&audio);
+ break;
+ }
+ c = devopen(c, omode, audiodir, nelem(audiodir), devgen);
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+
+ return c;
+}
+
+static void
+audioclose(Chan *c)
+{
+ switch((ulong)c->qid.path) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qdir:
+ case Qvolume:
+ break;
+
+ case Qaudio:
+ if(c->flag & COPEN) {
+ aqlock(&audio);
+ audiodevclose();
+ audio.amode = Aclosed;
+ aqunlock(&audio);
+ }
+ break;
+ }
+}
+
+static long
+audioread(Chan *c, void *v, long n, vlong off)
+{
+ int liv, riv, lov, rov;
+ long m;
+ char buf[300];
+ int j;
+ ulong offset = off;
+ char *a;
+
+ a = v;
+ switch((ulong)c->qid.path) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qdir:
+ return devdirread(c, a, n, audiodir, nelem(audiodir), devgen);
+
+ case Qaudio:
+ if(audio.amode != Aread)
+ error(Emode);
+ aqlock(&audio);
+ if(waserror()){
+ aqunlock(&audio);
+ nexterror();
+ }
+ n = audiodevread(v, n);
+ poperror();
+ aqunlock(&audio);
+ break;
+
+ case Qvolume:
+ j = 0;
+ buf[0] = 0;
+ for(m=0; volumes[m].name; m++){
+ audiodevgetvol(m, &lov, &rov);
+ liv = lov;
+ riv = rov;
+ j += snprint(buf+j, sizeof(buf)-j, "%s", volumes[m].name);
+ if((volumes[m].flag & Fmono) || (liv==riv && lov==rov)){
+ if((volumes[m].flag&(Fin|Fout))==(Fin|Fout) && liv==lov)
+ j += snprint(buf+j, sizeof(buf)-j, " %d", liv);
+ else{
+ if(volumes[m].flag & Fin)
+ j += snprint(buf+j, sizeof(buf)-j,
+ " in %d", liv);
+ if(volumes[m].flag & Fout)
+ j += snprint(buf+j, sizeof(buf)-j,
+ " out %d", lov);
+ }
+ }else{
+ if((volumes[m].flag&(Fin|Fout))==(Fin|Fout) &&
+ liv==lov && riv==rov)
+ j += snprint(buf+j, sizeof(buf)-j,
+ " left %d right %d",
+ liv, riv);
+ else{
+ if(volumes[m].flag & Fin)
+ j += snprint(buf+j, sizeof(buf)-j,
+ " in left %d right %d",
+ liv, riv);
+ if(volumes[m].flag & Fout)
+ j += snprint(buf+j, sizeof(buf)-j,
+ " out left %d right %d",
+ lov, rov);
+ }
+ }
+ j += snprint(buf+j, sizeof(buf)-j, "\n");
+ }
+ return readstr(offset, a, n, buf);
+ }
+ return n;
+}
+
+static long
+audiowrite(Chan *c, void *vp, long n, vlong off)
+{
+ long m;
+ int i, v, left, right, in, out;
+ Cmdbuf *cb;
+ char *a;
+
+ USED(off);
+ a = vp;
+ switch((ulong)c->qid.path) {
+ default:
+ error(Eperm);
+ break;
+
+ case Qvolume:
+ v = Vaudio;
+ left = 1;
+ right = 1;
+ in = 1;
+ out = 1;
+ cb = parsecmd(vp, n);
+ if(waserror()){
+ free(cb);
+ nexterror();
+ }
+
+ for(i = 0; i < cb->nf; i++){
+ /*
+ * a number is volume
+ */
+ if(cb->f[i][0] >= '0' && cb->f[i][0] <= '9') {
+ m = strtoul(cb->f[i], 0, 10);
+ if(!out)
+ goto cont0;
+ if(left && right)
+ audiodevsetvol(v, m, m);
+ else if(left)
+ audiodevsetvol(v, m, -1);
+ else if(right)
+ audiodevsetvol(v, -1, m);
+ goto cont0;
+ }
+
+ for(m=0; volumes[m].name; m++) {
+ if(strcmp(cb->f[i], volumes[m].name) == 0) {
+ v = m;
+ in = 1;
+ out = 1;
+ left = 1;
+ right = 1;
+ goto cont0;
+ }
+ }
+
+ if(strcmp(cb->f[i], "reset") == 0) {
+ resetlevel();
+ goto cont0;
+ }
+ if(strcmp(cb->f[i], "in") == 0) {
+ in = 1;
+ out = 0;
+ goto cont0;
+ }
+ if(strcmp(cb->f[i], "out") == 0) {
+ in = 0;
+ out = 1;
+ goto cont0;
+ }
+ if(strcmp(cb->f[i], "left") == 0) {
+ left = 1;
+ right = 0;
+ goto cont0;
+ }
+ if(strcmp(cb->f[i], "right") == 0) {
+ left = 0;
+ right = 1;
+ goto cont0;
+ }
+ error(Evolume);
+ break;
+ cont0:;
+ }
+ free(cb);
+ poperror();
+ break;
+
+ case Qaudio:
+ if(audio.amode != Awrite)
+ error(Emode);
+ aqlock(&audio);
+ if(waserror()){
+ aqunlock(&audio);
+ nexterror();
+ }
+ n = audiodevwrite(vp, n);
+ poperror();
+ aqunlock(&audio);
+ break;
+ }
+ return n;
+}
+
+void
+audioswab(uchar *a, uint n)
+{
+ ulong *p, *ep, b;
+
+ p = (ulong*)a;
+ ep = p + (n>>2);
+ while(p < ep) {
+ b = *p;
+ b = (b>>24) | (b<<24) |
+ ((b&0xff0000) >> 8) |
+ ((b&0x00ff00) << 8);
+ *p++ = b;
+ }
+}
+
+Dev audiodevtab = {
+ 'A',
+ "audio",
+
+ devreset,
+ audioinit,
+ devshutdown,
+ audioattach,
+ audiowalk,
+ audiostat,
+ audioopen,
+ devcreate,
+ audioclose,
+ audioread,
+ devbread,
+ audiowrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
diff --git a/sys/src/cmd/unix/drawterm/kern/devaudio.h b/sys/src/cmd/unix/drawterm/kern/devaudio.h
new file mode 100755
index 000000000..e6cc7ed21
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devaudio.h
@@ -0,0 +1,25 @@
+enum
+{
+ Fmono = 1,
+ Fin = 2,
+ Fout = 4,
+
+ Vaudio = 0,
+ Vsynth,
+ Vcd,
+ Vline,
+ Vmic,
+ Vspeaker,
+ Vtreb,
+ Vbass,
+ Vspeed,
+ Vpcm,
+ Nvol,
+};
+
+void audiodevopen(void);
+void audiodevclose(void);
+int audiodevread(void*, int);
+int audiodevwrite(void*, int);
+void audiodevgetvol(int, int*, int*);
+void audiodevsetvol(int, int, int);
diff --git a/sys/src/cmd/unix/drawterm/kern/devcons.c b/sys/src/cmd/unix/drawterm/kern/devcons.c
new file mode 100755
index 000000000..bd80d6eae
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devcons.c
@@ -0,0 +1,1193 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "keyboard.h"
+
+void (*consdebug)(void) = 0;
+void (*screenputs)(char*, int) = 0;
+
+Queue* kbdq; /* unprocessed console input */
+Queue* lineq; /* processed console input */
+Queue* serialoq; /* serial console output */
+Queue* kprintoq; /* console output, for /dev/kprint */
+long kprintinuse; /* test and set whether /dev/kprint is open */
+Lock kprintlock;
+int iprintscreenputs = 0;
+
+int panicking;
+
+struct
+{
+ int exiting;
+ int machs;
+} active;
+
+static struct
+{
+ QLock lk;
+
+ int raw; /* true if we shouldn't process input */
+ int ctl; /* number of opens to the control file */
+ int x; /* index into line */
+ char line[1024]; /* current input line */
+
+ int count;
+ int ctlpoff;
+
+ /* a place to save up characters at interrupt time before dumping them in the queue */
+ Lock lockputc;
+ char istage[1024];
+ char *iw;
+ char *ir;
+ char *ie;
+} kbd = {
+ { 0 },
+ 0,
+ 0,
+ 0,
+ { 0 },
+ 0,
+ 0,
+ { 0 },
+ { 0 },
+ kbd.istage,
+ kbd.istage,
+ kbd.istage + sizeof(kbd.istage),
+};
+
+char *sysname;
+vlong fasthz;
+
+static int readtime(ulong, char*, int);
+static int readbintime(char*, int);
+static int writetime(char*, int);
+static int writebintime(char*, int);
+
+enum
+{
+ CMreboot,
+ CMpanic,
+};
+
+Cmdtab rebootmsg[] =
+{
+ CMreboot, "reboot", 0,
+ CMpanic, "panic", 0,
+};
+
+int
+return0(void *v)
+{
+ return 0;
+}
+
+void
+printinit(void)
+{
+ lineq = qopen(2*1024, 0, 0, nil);
+ if(lineq == nil)
+ panic("printinit");
+ qnoblock(lineq, 1);
+
+ kbdq = qopen(4*1024, 0, 0, 0);
+ if(kbdq == nil)
+ panic("kbdinit");
+ qnoblock(kbdq, 1);
+}
+
+int
+consactive(void)
+{
+ if(serialoq)
+ return qlen(serialoq) > 0;
+ return 0;
+}
+
+void
+prflush(void)
+{
+/*
+ ulong now;
+
+ now = m->ticks;
+ while(consactive())
+ if(m->ticks - now >= HZ)
+ break;
+*/
+}
+
+/*
+ * 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.
+ */
+static void
+putstrn0(char *str, int n, int usewrite)
+{
+ /*
+ * if someone is reading /dev/kprint,
+ * put the message there.
+ * if not and there's an attached bit mapped display,
+ * put the message there.
+ *
+ * if there's a serial line being used as a console,
+ * put the message there.
+ */
+ if(kprintoq != nil && !qisclosed(kprintoq)){
+ if(usewrite)
+ qwrite(kprintoq, str, n);
+ else
+ qiwrite(kprintoq, str, n);
+ }else if(screenputs != 0)
+ screenputs(str, n);
+}
+
+void
+putstrn(char *str, int n)
+{
+ putstrn0(str, n, 0);
+}
+
+int noprint;
+
+int
+print(char *fmt, ...)
+{
+ int n;
+ va_list arg;
+ char buf[PRINTSIZE];
+
+ if(noprint)
+ return -1;
+
+ va_start(arg, fmt);
+ n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+ va_end(arg);
+ putstrn(buf, n);
+
+ return n;
+}
+
+void
+panic(char *fmt, ...)
+{
+ int n;
+ va_list arg;
+ char buf[PRINTSIZE];
+
+ kprintoq = nil; /* don't try to write to /dev/kprint */
+
+ if(panicking)
+ for(;;);
+ panicking = 1;
+
+ splhi();
+ strcpy(buf, "panic: ");
+ va_start(arg, fmt);
+ n = vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg) - buf;
+ va_end(arg);
+ buf[n] = '\n';
+ uartputs(buf, n+1);
+ if(consdebug)
+ (*consdebug)();
+ spllo();
+ prflush();
+ putstrn(buf, n+1);
+ dumpstack();
+
+ exit(1);
+}
+
+int
+pprint(char *fmt, ...)
+{
+ int n;
+ Chan *c;
+ va_list arg;
+ char buf[2*PRINTSIZE];
+
+ if(up == nil || up->fgrp == nil)
+ return 0;
+
+ c = up->fgrp->fd[2];
+ if(c==0 || (c->mode!=OWRITE && c->mode!=ORDWR))
+ return 0;
+ n = sprint(buf, "%s %lud: ", up->text, up->pid);
+ va_start(arg, fmt);
+ n = vseprint(buf+n, buf+sizeof(buf), fmt, arg) - buf;
+ va_end(arg);
+
+ if(waserror())
+ return 0;
+ devtab[c->type]->write(c, buf, n, c->offset);
+ poperror();
+
+ lock(&c->ref.lk);
+ c->offset += n;
+ unlock(&c->ref.lk);
+
+ return n;
+}
+
+static void
+echoscreen(char *buf, int n)
+{
+ char *e, *p;
+ char ebuf[128];
+ int x;
+
+ p = ebuf;
+ e = ebuf + sizeof(ebuf) - 4;
+ while(n-- > 0){
+ if(p >= e){
+ screenputs(ebuf, p - ebuf);
+ p = ebuf;
+ }
+ x = *buf++;
+ if(x == 0x15){
+ *p++ = '^';
+ *p++ = 'U';
+ *p++ = '\n';
+ } else
+ *p++ = x;
+ }
+ if(p != ebuf)
+ screenputs(ebuf, p - ebuf);
+}
+
+static void
+echoserialoq(char *buf, int n)
+{
+ char *e, *p;
+ char ebuf[128];
+ int x;
+
+ p = ebuf;
+ e = ebuf + sizeof(ebuf) - 4;
+ while(n-- > 0){
+ if(p >= e){
+ qiwrite(serialoq, ebuf, p - ebuf);
+ p = ebuf;
+ }
+ x = *buf++;
+ if(x == '\n'){
+ *p++ = '\r';
+ *p++ = '\n';
+ } else if(x == 0x15){
+ *p++ = '^';
+ *p++ = 'U';
+ *p++ = '\n';
+ } else
+ *p++ = x;
+ }
+ if(p != ebuf)
+ qiwrite(serialoq, ebuf, p - ebuf);
+}
+
+static void
+echo(char *buf, int n)
+{
+ static int ctrlt;
+ int x;
+ char *e, *p;
+
+ e = buf+n;
+ for(p = buf; p < e; p++){
+ switch(*p){
+ case 0x10: /* ^P */
+ if(cpuserver && !kbd.ctlpoff){
+ active.exiting = 1;
+ return;
+ }
+ break;
+ case 0x14: /* ^T */
+ ctrlt++;
+ if(ctrlt > 2)
+ ctrlt = 2;
+ continue;
+ }
+
+ if(ctrlt != 2)
+ continue;
+
+ /* ^T escapes */
+ ctrlt = 0;
+ switch(*p){
+ case 'S':
+ x = splhi();
+ dumpstack();
+ procdump();
+ splx(x);
+ return;
+ case 's':
+ dumpstack();
+ return;
+ case 'x':
+ xsummary();
+ ixsummary();
+ mallocsummary();
+ pagersummary();
+ return;
+ case 'd':
+ if(consdebug == 0)
+ consdebug = rdb;
+ else
+ consdebug = 0;
+ print("consdebug now 0x%p\n", consdebug);
+ return;
+ case 'D':
+ if(consdebug == 0)
+ consdebug = rdb;
+ consdebug();
+ return;
+ case 'p':
+ x = spllo();
+ procdump();
+ splx(x);
+ return;
+ case 'q':
+ scheddump();
+ return;
+ case 'k':
+ killbig();
+ return;
+ case 'r':
+ exit(0);
+ return;
+ }
+ }
+
+ qproduce(kbdq, buf, n);
+ if(kbd.raw)
+ return;
+ if(screenputs != 0)
+ echoscreen(buf, n);
+ if(serialoq)
+ echoserialoq(buf, n);
+}
+
+/*
+ * Called by a uart interrupt for console input.
+ *
+ * turn '\r' into '\n' before putting it into the queue.
+ */
+int
+kbdcr2nl(Queue *q, int ch)
+{
+ char *next;
+
+ USED(q);
+ ilock(&kbd.lockputc); /* just a mutex */
+ if(ch == '\r' && !kbd.raw)
+ ch = '\n';
+ next = kbd.iw+1;
+ if(next >= kbd.ie)
+ next = kbd.istage;
+ if(next != kbd.ir){
+ *kbd.iw = ch;
+ kbd.iw = next;
+ }
+ iunlock(&kbd.lockputc);
+ return 0;
+}
+static
+void
+_kbdputc(int c)
+{
+ Rune r;
+ char buf[UTFmax];
+ int n;
+
+ r = c;
+ n = runetochar(buf, &r);
+ if(n == 0)
+ return;
+ echo(buf, n);
+// kbd.c = r;
+// qproduce(kbdq, buf, n);
+}
+
+/* _kbdputc, but with compose translation */
+int
+kbdputc(Queue *q, int c)
+{
+ int i;
+ static int collecting, nk;
+ static Rune kc[5];
+
+ if(c == Kalt){
+ collecting = 1;
+ nk = 0;
+ return 0;
+ }
+
+ if(!collecting){
+ _kbdputc(c);
+ return 0;
+ }
+
+ kc[nk++] = c;
+ c = latin1(kc, nk);
+ if(c < -1) /* need more keystrokes */
+ return 0;
+ if(c != -1) /* valid sequence */
+ _kbdputc(c);
+ else
+ for(i=0; i<nk; i++)
+ _kbdputc(kc[i]);
+ nk = 0;
+ collecting = 0;
+
+ return 0;
+}
+
+
+enum{
+ Qdir,
+ Qbintime,
+ Qcons,
+ Qconsctl,
+ Qcpunote,
+ Qcputime,
+ Qdrivers,
+ Qkprint,
+ Qhostdomain,
+ Qhostowner,
+ Qnull,
+ Qosversion,
+ Qpgrpid,
+ Qpid,
+ Qppid,
+ Qrandom,
+ Qreboot,
+ Qsecstore,
+ Qshowfile,
+ Qsnarf,
+ Qswap,
+ Qsysname,
+ Qsysstat,
+ Qtime,
+ Quser,
+ Qzero,
+};
+
+enum
+{
+ VLNUMSIZE= 22,
+};
+
+static Dirtab consdir[]={
+ ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
+ "bintime", {Qbintime}, 24, 0664,
+ "cons", {Qcons}, 0, 0660,
+ "consctl", {Qconsctl}, 0, 0220,
+ "cpunote", {Qcpunote}, 0, 0444,
+ "cputime", {Qcputime}, 6*NUMSIZE, 0444,
+ "drivers", {Qdrivers}, 0, 0444,
+ "hostdomain", {Qhostdomain}, DOMLEN, 0664,
+ "hostowner", {Qhostowner}, 0, 0664,
+ "kprint", {Qkprint, 0, QTEXCL}, 0, DMEXCL|0440,
+ "null", {Qnull}, 0, 0666,
+ "osversion", {Qosversion}, 0, 0444,
+ "pgrpid", {Qpgrpid}, NUMSIZE, 0444,
+ "pid", {Qpid}, NUMSIZE, 0444,
+ "ppid", {Qppid}, NUMSIZE, 0444,
+ "random", {Qrandom}, 0, 0444,
+ "reboot", {Qreboot}, 0, 0664,
+ "secstore", {Qsecstore}, 0, 0666,
+ "showfile", {Qshowfile}, 0, 0220,
+ "snarf", {Qsnarf}, 0, 0666,
+ "swap", {Qswap}, 0, 0664,
+ "sysname", {Qsysname}, 0, 0664,
+ "sysstat", {Qsysstat}, 0, 0666,
+ "time", {Qtime}, NUMSIZE+3*VLNUMSIZE, 0664,
+ "user", {Quser}, 0, 0666,
+ "zero", {Qzero}, 0, 0444,
+};
+
+char secstorebuf[65536];
+Dirtab *secstoretab = &consdir[Qsecstore];
+Dirtab *snarftab = &consdir[Qsnarf];
+
+int
+readnum(ulong off, char *buf, ulong n, ulong val, int size)
+{
+ char tmp[64];
+
+ snprint(tmp, sizeof(tmp), "%*.0lud", size-1, val);
+ tmp[size-1] = ' ';
+ if(off >= size)
+ return 0;
+ if(off+n > size)
+ n = size-off;
+ memmove(buf, tmp+off, n);
+ return n;
+}
+
+int
+readstr(ulong off, char *buf, ulong n, char *str)
+{
+ int size;
+
+ size = strlen(str);
+ if(off >= size)
+ return 0;
+ if(off+n > size)
+ n = size-off;
+ memmove(buf, str+off, n);
+ return n;
+}
+
+static void
+consinit(void)
+{
+ todinit();
+ randominit();
+ /*
+ * at 115200 baud, the 1024 char buffer takes 56 ms to process,
+ * processing it every 22 ms should be fine
+ */
+/* addclock0link(kbdputcclock, 22); */
+}
+
+static Chan*
+consattach(char *spec)
+{
+ return devattach('c', spec);
+}
+
+static Walkqid*
+conswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name,nname, consdir, nelem(consdir), devgen);
+}
+
+static int
+consstat(Chan *c, uchar *dp, int n)
+{
+ return devstat(c, dp, n, consdir, nelem(consdir), devgen);
+}
+
+static Chan*
+consopen(Chan *c, int omode)
+{
+ c->aux = nil;
+ c = devopen(c, omode, consdir, nelem(consdir), devgen);
+ switch((ulong)c->qid.path){
+ case Qconsctl:
+ qlock(&kbd.lk);
+ kbd.ctl++;
+ qunlock(&kbd.lk);
+ break;
+
+ case Qkprint:
+ lock(&kprintlock);
+ if(kprintinuse != 0){
+ c->flag &= ~COPEN;
+ unlock(&kprintlock);
+ error(Einuse);
+ }
+ kprintinuse = 1;
+ unlock(&kprintlock);
+ if(kprintoq == nil){
+ kprintoq = qopen(8*1024, Qcoalesce, 0, 0);
+ if(kprintoq == nil){
+ c->flag &= ~COPEN;
+ error(Enomem);
+ }
+ qnoblock(kprintoq, 1);
+ }else
+ qreopen(kprintoq);
+ c->iounit = qiomaxatomic;
+ break;
+
+ case Qsecstore:
+ if(omode == ORDWR)
+ error(Eperm);
+ if(omode != OREAD)
+ memset(secstorebuf, 0, sizeof secstorebuf);
+ break;
+
+ case Qsnarf:
+ if(omode == ORDWR)
+ error(Eperm);
+ if(omode == OREAD)
+ c->aux = strdup("");
+ else
+ c->aux = mallocz(SnarfSize, 1);
+ break;
+ }
+ return c;
+}
+
+static void
+consclose(Chan *c)
+{
+ switch((ulong)c->qid.path){
+ /* last close of control file turns off raw */
+ case Qconsctl:
+ if(c->flag&COPEN){
+ qlock(&kbd.lk);
+ if(--kbd.ctl == 0)
+ kbd.raw = 0;
+ qunlock(&kbd.lk);
+ }
+ break;
+
+ /* close of kprint allows other opens */
+ case Qkprint:
+ if(c->flag & COPEN){
+ kprintinuse = 0;
+ qhangup(kprintoq, nil);
+ }
+ break;
+
+ case Qsnarf:
+ if(c->mode == OWRITE)
+ clipwrite(c->aux);
+ free(c->aux);
+ break;
+ }
+}
+
+static long
+consread(Chan *c, void *buf, long n, vlong off)
+{
+ char *b;
+ char tmp[128]; /* must be >= 6*NUMSIZE */
+ char *cbuf = buf;
+ int ch, i, eol;
+ vlong offset = off;
+
+ if(n <= 0)
+ return n;
+ switch((ulong)c->qid.path){
+ case Qdir:
+ return devdirread(c, buf, n, consdir, nelem(consdir), devgen);
+
+ case Qcons:
+ qlock(&kbd.lk);
+ if(waserror()) {
+ qunlock(&kbd.lk);
+ nexterror();
+ }
+ if(kbd.raw) {
+ if(qcanread(lineq))
+ n = qread(lineq, buf, n);
+ else {
+ /* read as much as possible */
+ do {
+ i = qread(kbdq, cbuf, n);
+ cbuf += i;
+ n -= i;
+ } while (n>0 && qcanread(kbdq));
+ n = cbuf - (char*)buf;
+ }
+ } else {
+ while(!qcanread(lineq)) {
+ qread(kbdq, &kbd.line[kbd.x], 1);
+ ch = kbd.line[kbd.x];
+ eol = 0;
+ switch(ch){
+ case '\b':
+ if(kbd.x)
+ kbd.x--;
+ break;
+ case 0x15:
+ kbd.x = 0;
+ break;
+ case '\n':
+ case 0x04:
+ eol = 1;
+ default:
+ kbd.line[kbd.x++] = ch;
+ break;
+ }
+ if(kbd.x == sizeof(kbd.line) || eol){
+ if(ch == 0x04)
+ kbd.x--;
+ qwrite(lineq, kbd.line, kbd.x);
+ kbd.x = 0;
+ }
+ }
+ n = qread(lineq, buf, n);
+ }
+ qunlock(&kbd.lk);
+ poperror();
+ return n;
+
+ case Qcpunote:
+ sleep(&up->sleep, return0, nil);
+
+ case Qcputime:
+ return 0;
+
+ case Qkprint:
+ return qread(kprintoq, buf, n);
+
+ case Qpgrpid:
+ return readnum((ulong)offset, buf, n, up->pgrp->pgrpid, NUMSIZE);
+
+ case Qpid:
+ return readnum((ulong)offset, buf, n, up->pid, NUMSIZE);
+
+ case Qppid:
+ return readnum((ulong)offset, buf, n, up->parentpid, NUMSIZE);
+
+ case Qtime:
+ return readtime((ulong)offset, buf, n);
+
+ case Qbintime:
+ return readbintime(buf, n);
+
+ case Qhostowner:
+ return readstr((ulong)offset, buf, n, eve);
+
+ case Qhostdomain:
+ return readstr((ulong)offset, buf, n, hostdomain);
+
+ case Quser:
+ return readstr((ulong)offset, buf, n, up->user);
+
+ case Qnull:
+ return 0;
+
+ case Qsnarf:
+ if(offset == 0){
+ free(c->aux);
+ c->aux = clipread();
+ }
+ if(c->aux == nil)
+ return 0;
+ return readstr(offset, buf, n, c->aux);
+
+ case Qsecstore:
+ return readstr(offset, buf, n, secstorebuf);
+
+ case Qsysstat:
+ return 0;
+
+ case Qswap:
+ return 0;
+
+ case Qsysname:
+ if(sysname == nil)
+ return 0;
+ return readstr((ulong)offset, buf, n, sysname);
+
+ case Qrandom:
+ return randomread(buf, n);
+
+ case Qdrivers:
+ b = malloc(READSTR);
+ if(b == nil)
+ error(Enomem);
+ n = 0;
+ for(i = 0; devtab[i] != nil; i++)
+ n += snprint(b+n, READSTR-n, "#%C %s\n", devtab[i]->dc, devtab[i]->name);
+ if(waserror()){
+ free(b);
+ nexterror();
+ }
+ n = readstr((ulong)offset, buf, n, b);
+ free(b);
+ poperror();
+ return n;
+
+ case Qzero:
+ memset(buf, 0, n);
+ return n;
+
+ case Qosversion:
+ snprint(tmp, sizeof tmp, "2000");
+ n = readstr((ulong)offset, buf, n, tmp);
+ return n;
+
+ default:
+ print("consread 0x%llux\n", c->qid.path);
+ error(Egreg);
+ }
+ return -1; /* never reached */
+}
+
+static long
+conswrite(Chan *c, void *va, long n, vlong off)
+{
+ char buf[256];
+ long l, bp;
+ char *a = va;
+ int fd;
+ Chan *swc;
+ ulong offset = off;
+ Cmdbuf *cb;
+ Cmdtab *ct;
+
+ switch((ulong)c->qid.path){
+ case Qcons:
+ /*
+ * Can't page fault in putstrn, so copy the data locally.
+ */
+ l = n;
+ while(l > 0){
+ bp = l;
+ if(bp > sizeof buf)
+ bp = sizeof buf;
+ memmove(buf, a, bp);
+ putstrn0(buf, bp, 1);
+ a += bp;
+ l -= bp;
+ }
+ break;
+
+ case Qconsctl:
+ if(n >= sizeof(buf))
+ n = sizeof(buf)-1;
+ strncpy(buf, a, n);
+ buf[n] = 0;
+ for(a = buf; a;){
+ if(strncmp(a, "rawon", 5) == 0){
+ qlock(&kbd.lk);
+ if(kbd.x){
+ qwrite(kbdq, kbd.line, kbd.x);
+ kbd.x = 0;
+ }
+ kbd.raw = 1;
+ qunlock(&kbd.lk);
+ } else if(strncmp(a, "rawoff", 6) == 0){
+ qlock(&kbd.lk);
+ kbd.raw = 0;
+ kbd.x = 0;
+ qunlock(&kbd.lk);
+ } else if(strncmp(a, "ctlpon", 6) == 0){
+ kbd.ctlpoff = 0;
+ } else if(strncmp(a, "ctlpoff", 7) == 0){
+ kbd.ctlpoff = 1;
+ }
+ if((a = strchr(a, ' ')))
+ a++;
+ }
+ break;
+
+ case Qtime:
+ if(!iseve())
+ error(Eperm);
+ return writetime(a, n);
+
+ case Qbintime:
+ if(!iseve())
+ error(Eperm);
+ return writebintime(a, n);
+
+ case Qhostowner:
+ return hostownerwrite(a, n);
+
+ case Qhostdomain:
+ return hostdomainwrite(a, n);
+
+ case Quser:
+ return userwrite(a, n);
+
+ case Qnull:
+ break;
+
+ case Qreboot:
+ if(!iseve())
+ error(Eperm);
+ cb = parsecmd(a, n);
+
+ if(waserror()) {
+ free(cb);
+ nexterror();
+ }
+ ct = lookupcmd(cb, rebootmsg, nelem(rebootmsg));
+ switch(ct->index) {
+ case CMreboot:
+ rebootcmd(cb->nf-1, cb->f+1);
+ break;
+ case CMpanic:
+ panic("/dev/reboot");
+ }
+ poperror();
+ free(cb);
+ break;
+
+ case Qsecstore:
+ if(offset >= sizeof secstorebuf || offset+n+1 >= sizeof secstorebuf)
+ error(Etoobig);
+ secstoretab->qid.vers++;
+ memmove(secstorebuf+offset, va, n);
+ return n;
+
+ case Qshowfile:
+ return showfilewrite(a, n);
+
+ case Qsnarf:
+ if(offset >= SnarfSize || offset+n >= SnarfSize)
+ error(Etoobig);
+ snarftab->qid.vers++;
+ memmove((uchar*)c->aux+offset, va, n);
+ return n;
+
+ case Qsysstat:
+ n = 0;
+ break;
+
+ case Qswap:
+ if(n >= sizeof buf)
+ error(Egreg);
+ memmove(buf, va, n); /* so we can NUL-terminate */
+ buf[n] = 0;
+ /* start a pager if not already started */
+ if(strncmp(buf, "start", 5) == 0){
+ kickpager();
+ break;
+ }
+ if(cpuserver && !iseve())
+ error(Eperm);
+ if(buf[0]<'0' || '9'<buf[0])
+ error(Ebadarg);
+ fd = strtoul(buf, 0, 0);
+ swc = fdtochan(fd, -1, 1, 1);
+ setswapchan(swc);
+ break;
+
+ case Qsysname:
+ if(offset != 0)
+ error(Ebadarg);
+ if(n <= 0 || n >= sizeof buf)
+ error(Ebadarg);
+ strncpy(buf, a, n);
+ buf[n] = 0;
+ if(buf[n-1] == '\n')
+ buf[n-1] = 0;
+ kstrdup(&sysname, buf);
+ break;
+
+ default:
+ print("conswrite: 0x%llux\n", c->qid.path);
+ error(Egreg);
+ }
+ return n;
+}
+
+Dev consdevtab = {
+ 'c',
+ "cons",
+
+ devreset,
+ consinit,
+ devshutdown,
+ consattach,
+ conswalk,
+ consstat,
+ consopen,
+ devcreate,
+ consclose,
+ consread,
+ devbread,
+ conswrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
+
+static uvlong uvorder = (uvlong) 0x0001020304050607ULL;
+
+static uchar*
+le2vlong(vlong *to, uchar *f)
+{
+ uchar *t, *o;
+ int i;
+
+ t = (uchar*)to;
+ o = (uchar*)&uvorder;
+ for(i = 0; i < sizeof(vlong); i++)
+ t[o[i]] = f[i];
+ return f+sizeof(vlong);
+}
+
+static uchar*
+vlong2le(uchar *t, vlong from)
+{
+ uchar *f, *o;
+ int i;
+
+ f = (uchar*)&from;
+ o = (uchar*)&uvorder;
+ for(i = 0; i < sizeof(vlong); i++)
+ t[i] = f[o[i]];
+ return t+sizeof(vlong);
+}
+
+static long order = 0x00010203;
+
+static uchar*
+le2long(long *to, uchar *f)
+{
+ uchar *t, *o;
+ int i;
+
+ t = (uchar*)to;
+ o = (uchar*)&order;
+ for(i = 0; i < sizeof(long); i++)
+ t[o[i]] = f[i];
+ return f+sizeof(long);
+}
+
+/*
+static uchar*
+long2le(uchar *t, long from)
+{
+ uchar *f, *o;
+ int i;
+
+ f = (uchar*)&from;
+ o = (uchar*)&order;
+ for(i = 0; i < sizeof(long); i++)
+ t[i] = f[o[i]];
+ return t+sizeof(long);
+}
+*/
+
+char *Ebadtimectl = "bad time control";
+
+/*
+ * like the old #c/time but with added info. Return
+ *
+ * secs nanosecs fastticks fasthz
+ */
+static int
+readtime(ulong off, char *buf, int n)
+{
+ vlong nsec, ticks;
+ long sec;
+ char str[7*NUMSIZE];
+
+ nsec = todget(&ticks);
+ if(fasthz == (vlong)0)
+ fastticks((uvlong*)&fasthz);
+ sec = nsec/((uvlong) 1000000000);
+ snprint(str, sizeof(str), "%*.0lud %*.0llud %*.0llud %*.0llud ",
+ NUMSIZE-1, sec,
+ VLNUMSIZE-1, nsec,
+ VLNUMSIZE-1, ticks,
+ VLNUMSIZE-1, fasthz);
+ return readstr(off, buf, n, str);
+}
+
+/*
+ * set the time in seconds
+ */
+static int
+writetime(char *buf, int n)
+{
+ char b[13];
+ long i;
+ vlong now;
+
+ if(n >= sizeof(b))
+ error(Ebadtimectl);
+ strncpy(b, buf, n);
+ b[n] = 0;
+ i = strtol(b, 0, 0);
+ if(i <= 0)
+ error(Ebadtimectl);
+ now = i*((vlong) 1000000000);
+ todset(now, 0, 0);
+ return n;
+}
+
+/*
+ * read binary time info. all numbers are little endian.
+ * ticks and nsec are syncronized.
+ */
+static int
+readbintime(char *buf, int n)
+{
+ int i;
+ vlong nsec, ticks;
+ uchar *b = (uchar*)buf;
+
+ i = 0;
+ if(fasthz == (vlong)0)
+ fastticks((uvlong*)&fasthz);
+ nsec = todget(&ticks);
+ if(n >= 3*sizeof(uvlong)){
+ vlong2le(b+2*sizeof(uvlong), fasthz);
+ i += sizeof(uvlong);
+ }
+ if(n >= 2*sizeof(uvlong)){
+ vlong2le(b+sizeof(uvlong), ticks);
+ i += sizeof(uvlong);
+ }
+ if(n >= 8){
+ vlong2le(b, nsec);
+ i += sizeof(vlong);
+ }
+ return i;
+}
+
+/*
+ * set any of the following
+ * - time in nsec
+ * - nsec trim applied over some seconds
+ * - clock frequency
+ */
+static int
+writebintime(char *buf, int n)
+{
+ uchar *p;
+ vlong delta;
+ long period;
+
+ n--;
+ p = (uchar*)buf + 1;
+ switch(*buf){
+ case 'n':
+ if(n < sizeof(vlong))
+ error(Ebadtimectl);
+ le2vlong(&delta, p);
+ todset(delta, 0, 0);
+ break;
+ case 'd':
+ if(n < sizeof(vlong)+sizeof(long))
+ error(Ebadtimectl);
+ p = le2vlong(&delta, p);
+ le2long(&period, p);
+ todset(-1, delta, period);
+ break;
+ case 'f':
+ if(n < sizeof(uvlong))
+ error(Ebadtimectl);
+ le2vlong(&fasthz, p);
+ todsetfreq(fasthz);
+ break;
+ }
+ return n;
+}
+
+
+int
+iprint(char *fmt, ...)
+{
+ int n, s;
+ va_list arg;
+ char buf[PRINTSIZE];
+
+ s = splhi();
+ va_start(arg, fmt);
+ n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+ va_end(arg);
+ if(screenputs != 0 && iprintscreenputs)
+ screenputs(buf, n);
+#undef write
+ write(2, buf, n);
+ splx(s);
+
+ return n;
+}
+
diff --git a/sys/src/cmd/unix/drawterm/kern/devdraw.c b/sys/src/cmd/unix/drawterm/kern/devdraw.c
new file mode 100755
index 000000000..8b6148e3c
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devdraw.c
@@ -0,0 +1,2148 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#define Image IMAGE
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+#include <cursor.h>
+#include "screen.h"
+
+enum
+{
+ Qtopdir = 0,
+ Qnew,
+ Q3rd,
+ Q2nd,
+ Qcolormap,
+ Qctl,
+ Qdata,
+ Qrefresh,
+};
+
+/*
+ * Qid path is:
+ * 4 bits of file type (qids above)
+ * 24 bits of mux slot number +1; 0 means not attached to client
+ */
+#define QSHIFT 4 /* location in qid of client # */
+
+#define QID(q) ((((ulong)(q).path)&0x0000000F)>>0)
+#define CLIENTPATH(q) ((((ulong)q)&0x7FFFFFF0)>>QSHIFT)
+#define CLIENT(q) CLIENTPATH((q).path)
+
+#define NHASH (1<<5)
+#define HASHMASK (NHASH-1)
+#define IOUNIT (64*1024)
+
+typedef struct Client Client;
+typedef struct Draw Draw;
+typedef struct DImage DImage;
+typedef struct DScreen DScreen;
+typedef struct CScreen CScreen;
+typedef struct FChar FChar;
+typedef struct Refresh Refresh;
+typedef struct Refx Refx;
+typedef struct DName DName;
+
+ulong blanktime = 30; /* in minutes; a half hour */
+
+struct Draw
+{
+ QLock lk;
+ int clientid;
+ int nclient;
+ Client** client;
+ int nname;
+ DName* name;
+ int vers;
+ int softscreen;
+ int blanked; /* screen turned off */
+ ulong blanktime; /* time of last operation */
+ ulong savemap[3*256];
+};
+
+struct Client
+{
+ Ref r;
+ DImage* dimage[NHASH];
+ CScreen* cscreen;
+ Refresh* refresh;
+ Rendez refrend;
+ uchar* readdata;
+ int nreaddata;
+ int busy;
+ int clientid;
+ int slot;
+ int refreshme;
+ int infoid;
+ int op;
+};
+
+struct Refresh
+{
+ DImage* dimage;
+ Rectangle r;
+ Refresh* next;
+};
+
+struct Refx
+{
+ Client* client;
+ DImage* dimage;
+};
+
+struct DName
+{
+ char *name;
+ Client *client;
+ DImage* dimage;
+ int vers;
+};
+
+struct FChar
+{
+ int minx; /* left edge of bits */
+ int maxx; /* right edge of bits */
+ uchar miny; /* first non-zero scan-line */
+ uchar maxy; /* last non-zero scan-line + 1 */
+ schar left; /* offset of baseline */
+ uchar width; /* width of baseline */
+};
+
+/*
+ * Reference counts in DImages:
+ * one per open by original client
+ * one per screen image or fill
+ * one per image derived from this one by name
+ */
+struct DImage
+{
+ int id;
+ int ref;
+ char *name;
+ int vers;
+ Memimage* image;
+ int ascent;
+ int nfchar;
+ FChar* fchar;
+ DScreen* dscreen; /* 0 if not a window */
+ DImage* fromname; /* image this one is derived from, by name */
+ DImage* next;
+};
+
+struct CScreen
+{
+ DScreen* dscreen;
+ CScreen* next;
+};
+
+struct DScreen
+{
+ int id;
+ int public;
+ int ref;
+ DImage *dimage;
+ DImage *dfill;
+ Memscreen* screen;
+ Client* owner;
+ DScreen* next;
+};
+
+static Draw sdraw;
+static Memimage *screenimage;
+static Memdata screendata;
+static Rectangle flushrect;
+static int waste;
+static DScreen* dscreen;
+extern void flushmemscreen(Rectangle);
+ void drawmesg(Client*, void*, int);
+ void drawuninstall(Client*, int);
+ void drawfreedimage(DImage*);
+ Client* drawclientofpath(ulong);
+
+static char Enodrawimage[] = "unknown id for draw image";
+static char Enodrawscreen[] = "unknown id for draw screen";
+static char Eshortdraw[] = "short draw message";
+static char Eshortread[] = "draw read too short";
+static char Eimageexists[] = "image id in use";
+static char Escreenexists[] = "screen id in use";
+static char Edrawmem[] = "image memory allocation failed";
+static char Ereadoutside[] = "readimage outside image";
+static char Ewriteoutside[] = "writeimage outside image";
+static char Enotfont[] = "image not a font";
+static char Eindex[] = "character index out of range";
+static char Enoclient[] = "no such draw client";
+/* static char Edepth[] = "image has bad depth"; */
+static char Enameused[] = "image name in use";
+static char Enoname[] = "no image with that name";
+static char Eoldname[] = "named image no longer valid";
+static char Enamed[] = "image already has name";
+static char Ewrongname[] = "wrong name for image";
+
+int
+drawcanqlock(void)
+{
+ return canqlock(&sdraw.lk);
+}
+
+void
+drawqlock(void)
+{
+ qlock(&sdraw.lk);
+}
+
+void
+drawqunlock(void)
+{
+ qunlock(&sdraw.lk);
+}
+
+static int
+drawgen(Chan *c, char *name, Dirtab *dt, int ndt, int s, Dir *dp)
+{
+ int t;
+ Qid q;
+ ulong path;
+ Client *cl;
+
+ USED(name);
+ USED(dt);
+ USED(ndt);
+
+ q.vers = 0;
+
+ if(s == DEVDOTDOT){
+ switch(QID(c->qid)){
+ case Qtopdir:
+ case Q2nd:
+ mkqid(&q, Qtopdir, 0, QTDIR);
+ devdir(c, q, "#i", 0, eve, 0500, dp);
+ break;
+ case Q3rd:
+ cl = drawclientofpath(c->qid.path);
+ if(cl == nil)
+ strcpy(up->genbuf, "??");
+ else
+ sprint(up->genbuf, "%d", cl->clientid);
+ mkqid(&q, Q2nd, 0, QTDIR);
+ devdir(c, q, up->genbuf, 0, eve, 0500, dp);
+ break;
+ default:
+ panic("drawwalk %llux", c->qid.path);
+ }
+ return 1;
+ }
+
+ /*
+ * Top level directory contains the name of the device.
+ */
+ t = QID(c->qid);
+ if(t == Qtopdir){
+ switch(s){
+ case 0:
+ mkqid(&q, Q2nd, 0, QTDIR);
+ devdir(c, q, "draw", 0, eve, 0555, dp);
+ break;
+ default:
+ return -1;
+ }
+ return 1;
+ }
+
+ /*
+ * Second level contains "new" plus all the clients.
+ */
+ if(t == Q2nd || t == Qnew){
+ if(s == 0){
+ mkqid(&q, Qnew, 0, QTFILE);
+ devdir(c, q, "new", 0, eve, 0666, dp);
+ }
+ else if(s <= sdraw.nclient){
+ cl = sdraw.client[s-1];
+ if(cl == 0)
+ return 0;
+ sprint(up->genbuf, "%d", cl->clientid);
+ mkqid(&q, (s<<QSHIFT)|Q3rd, 0, QTDIR);
+ devdir(c, q, up->genbuf, 0, eve, 0555, dp);
+ return 1;
+ }
+ else
+ return -1;
+ return 1;
+ }
+
+ /*
+ * Third level.
+ */
+ path = c->qid.path&~((1<<QSHIFT)-1); /* slot component */
+ q.vers = c->qid.vers;
+ q.type = QTFILE;
+ switch(s){
+ case 0:
+ q.path = path|Qcolormap;
+ devdir(c, q, "colormap", 0, eve, 0600, dp);
+ break;
+ case 1:
+ q.path = path|Qctl;
+ devdir(c, q, "ctl", 0, eve, 0600, dp);
+ break;
+ case 2:
+ q.path = path|Qdata;
+ devdir(c, q, "data", 0, eve, 0600, dp);
+ break;
+ case 3:
+ q.path = path|Qrefresh;
+ devdir(c, q, "refresh", 0, eve, 0400, dp);
+ break;
+ default:
+ return -1;
+ }
+ return 1;
+}
+
+static
+int
+drawrefactive(void *a)
+{
+ Client *c;
+
+ c = a;
+ return c->refreshme || c->refresh!=0;
+}
+
+static
+void
+drawrefreshscreen(DImage *l, Client *client)
+{
+ while(l != nil && l->dscreen == nil)
+ l = l->fromname;
+ if(l != nil && l->dscreen->owner != client)
+ l->dscreen->owner->refreshme = 1;
+}
+
+static
+void
+drawrefresh(Memimage *m, Rectangle r, void *v)
+{
+ Refx *x;
+ DImage *d;
+ Client *c;
+ Refresh *ref;
+
+ USED(m);
+
+ if(v == 0)
+ return;
+ x = v;
+ c = x->client;
+ d = x->dimage;
+ for(ref=c->refresh; ref; ref=ref->next)
+ if(ref->dimage == d){
+ combinerect(&ref->r, r);
+ return;
+ }
+ ref = malloc(sizeof(Refresh));
+ if(ref){
+ ref->dimage = d;
+ ref->r = r;
+ ref->next = c->refresh;
+ c->refresh = ref;
+ }
+}
+
+static void
+addflush(Rectangle r)
+{
+ int abb, ar, anbb;
+ Rectangle nbb;
+
+ if(sdraw.softscreen==0 || !rectclip(&r, screenimage->r))
+ return;
+
+ if(flushrect.min.x >= flushrect.max.x){
+ flushrect = r;
+ waste = 0;
+ return;
+ }
+ nbb = flushrect;
+ combinerect(&nbb, r);
+ ar = Dx(r)*Dy(r);
+ abb = Dx(flushrect)*Dy(flushrect);
+ anbb = Dx(nbb)*Dy(nbb);
+ /*
+ * Area of new waste is area of new bb minus area of old bb,
+ * less the area of the new segment, which we assume is not waste.
+ * This could be negative, but that's OK.
+ */
+ waste += anbb-abb - ar;
+ if(waste < 0)
+ waste = 0;
+ /*
+ * absorb if:
+ * total area is small
+ * waste is less than half total area
+ * rectangles touch
+ */
+ if(anbb<=1024 || waste*2<anbb || rectXrect(flushrect, r)){
+ flushrect = nbb;
+ return;
+ }
+ /* emit current state */
+ if(flushrect.min.x < flushrect.max.x)
+ flushmemscreen(flushrect);
+ flushrect = r;
+ waste = 0;
+}
+
+static
+void
+dstflush(int dstid, Memimage *dst, Rectangle r)
+{
+ Memlayer *l;
+
+ if(dstid == 0){
+ combinerect(&flushrect, r);
+ return;
+ }
+ /* how can this happen? -rsc, dec 12 2002 */
+ if(dst == 0){
+ print("nil dstflush\n");
+ return;
+ }
+ l = dst->layer;
+ if(l == nil)
+ return;
+ do{
+ if(l->screen->image->data != screenimage->data)
+ return;
+ r = rectaddpt(r, l->delta);
+ l = l->screen->image->layer;
+ }while(l);
+ addflush(r);
+}
+
+void
+drawflush(void)
+{
+ if(flushrect.min.x < flushrect.max.x)
+ flushmemscreen(flushrect);
+ flushrect = Rect(10000, 10000, -10000, -10000);
+}
+
+void
+drawflushr(Rectangle r)
+{
+ qlock(&sdraw.lk);
+ flushmemscreen(r);
+ qunlock(&sdraw.lk);
+}
+
+static
+int
+drawcmp(char *a, char *b, int n)
+{
+ if(strlen(a) != n)
+ return 1;
+ return memcmp(a, b, n);
+}
+
+DName*
+drawlookupname(int n, char *str)
+{
+ DName *name, *ename;
+
+ name = sdraw.name;
+ ename = &name[sdraw.nname];
+ for(; name<ename; name++)
+ if(drawcmp(name->name, str, n) == 0)
+ return name;
+ return 0;
+}
+
+int
+drawgoodname(DImage *d)
+{
+ DName *n;
+
+ /* if window, validate the screen's own images */
+ if(d->dscreen)
+ if(drawgoodname(d->dscreen->dimage) == 0
+ || drawgoodname(d->dscreen->dfill) == 0)
+ return 0;
+ if(d->name == nil)
+ return 1;
+ n = drawlookupname(strlen(d->name), d->name);
+ if(n==nil || n->vers!=d->vers)
+ return 0;
+ return 1;
+}
+
+DImage*
+drawlookup(Client *client, int id, int checkname)
+{
+ DImage *d;
+
+ d = client->dimage[id&HASHMASK];
+ while(d){
+ if(d->id == id){
+ if(checkname && !drawgoodname(d))
+ error(Eoldname);
+ return d;
+ }
+ d = d->next;
+ }
+ return 0;
+}
+
+DScreen*
+drawlookupdscreen(int id)
+{
+ DScreen *s;
+
+ s = dscreen;
+ while(s){
+ if(s->id == id)
+ return s;
+ s = s->next;
+ }
+ return 0;
+}
+
+DScreen*
+drawlookupscreen(Client *client, int id, CScreen **cs)
+{
+ CScreen *s;
+
+ s = client->cscreen;
+ while(s){
+ if(s->dscreen->id == id){
+ *cs = s;
+ return s->dscreen;
+ }
+ s = s->next;
+ }
+ error(Enodrawscreen);
+ return 0;
+}
+
+Memimage*
+drawinstall(Client *client, int id, Memimage *i, DScreen *dscreen)
+{
+ DImage *d;
+
+ d = malloc(sizeof(DImage));
+ if(d == 0)
+ return 0;
+ d->id = id;
+ d->ref = 1;
+ d->name = 0;
+ d->vers = 0;
+ d->image = i;
+ d->nfchar = 0;
+ d->fchar = 0;
+ d->fromname = 0;
+ d->dscreen = dscreen;
+ d->next = client->dimage[id&HASHMASK];
+ client->dimage[id&HASHMASK] = d;
+ return i;
+}
+
+Memscreen*
+drawinstallscreen(Client *client, DScreen *d, int id, DImage *dimage, DImage *dfill, int public)
+{
+ Memscreen *s;
+ CScreen *c;
+
+ c = malloc(sizeof(CScreen));
+ if(dimage && dimage->image && dimage->image->chan == 0)
+ panic("bad image %p in drawinstallscreen", dimage->image);
+
+ if(c == 0)
+ return 0;
+ if(d == 0){
+ d = malloc(sizeof(DScreen));
+ if(d == 0){
+ free(c);
+ return 0;
+ }
+ s = malloc(sizeof(Memscreen));
+ if(s == 0){
+ free(c);
+ free(d);
+ return 0;
+ }
+ s->frontmost = 0;
+ s->rearmost = 0;
+ d->dimage = dimage;
+ if(dimage){
+ s->image = dimage->image;
+ dimage->ref++;
+ }
+ d->dfill = dfill;
+ if(dfill){
+ s->fill = dfill->image;
+ dfill->ref++;
+ }
+ d->ref = 0;
+ d->id = id;
+ d->screen = s;
+ d->public = public;
+ d->next = dscreen;
+ d->owner = client;
+ dscreen = d;
+ }
+ c->dscreen = d;
+ d->ref++;
+ c->next = client->cscreen;
+ client->cscreen = c;
+ return d->screen;
+}
+
+void
+drawdelname(DName *name)
+{
+ int i;
+
+ i = name-sdraw.name;
+ memmove(name, name+1, (sdraw.nname-(i+1))*sizeof(DName));
+ sdraw.nname--;
+}
+
+void
+drawfreedscreen(DScreen *this)
+{
+ DScreen *ds, *next;
+
+ this->ref--;
+ if(this->ref < 0)
+ print("negative ref in drawfreedscreen\n");
+ if(this->ref > 0)
+ return;
+ ds = dscreen;
+ if(ds == this){
+ dscreen = this->next;
+ goto Found;
+ }
+ while((next = ds->next)){ /* assign = */
+ if(next == this){
+ ds->next = this->next;
+ goto Found;
+ }
+ ds = next;
+ }
+ error(Enodrawimage);
+
+ Found:
+ if(this->dimage)
+ drawfreedimage(this->dimage);
+ if(this->dfill)
+ drawfreedimage(this->dfill);
+ free(this->screen);
+ free(this);
+}
+
+void
+drawfreedimage(DImage *dimage)
+{
+ int i;
+ Memimage *l;
+ DScreen *ds;
+
+ dimage->ref--;
+ if(dimage->ref < 0)
+ print("negative ref in drawfreedimage\n");
+ if(dimage->ref > 0)
+ return;
+
+ /* any names? */
+ for(i=0; i<sdraw.nname; )
+ if(sdraw.name[i].dimage == dimage)
+ drawdelname(sdraw.name+i);
+ else
+ i++;
+ if(dimage->fromname){ /* acquired by name; owned by someone else*/
+ drawfreedimage(dimage->fromname);
+ goto Return;
+ }
+ if(dimage->image == screenimage) /* don't free the display */
+ goto Return;
+ ds = dimage->dscreen;
+ if(ds){
+ l = dimage->image;
+ if(l->data == screenimage->data)
+ addflush(l->layer->screenr);
+ if(l->layer->refreshfn == drawrefresh) /* else true owner will clean up */
+ free(l->layer->refreshptr);
+ l->layer->refreshptr = nil;
+ if(drawgoodname(dimage))
+ memldelete(l);
+ else
+ memlfree(l);
+ drawfreedscreen(ds);
+ }else
+ freememimage(dimage->image);
+ Return:
+ free(dimage->fchar);
+ free(dimage);
+}
+
+void
+drawuninstallscreen(Client *client, CScreen *this)
+{
+ CScreen *cs, *next;
+
+ cs = client->cscreen;
+ if(cs == this){
+ client->cscreen = this->next;
+ drawfreedscreen(this->dscreen);
+ free(this);
+ return;
+ }
+ while((next = cs->next)){ /* assign = */
+ if(next == this){
+ cs->next = this->next;
+ drawfreedscreen(this->dscreen);
+ free(this);
+ return;
+ }
+ cs = next;
+ }
+}
+
+void
+drawuninstall(Client *client, int id)
+{
+ DImage *d, *next;
+
+ d = client->dimage[id&HASHMASK];
+ if(d == 0)
+ error(Enodrawimage);
+ if(d->id == id){
+ client->dimage[id&HASHMASK] = d->next;
+ drawfreedimage(d);
+ return;
+ }
+ while((next = d->next)){ /* assign = */
+ if(next->id == id){
+ d->next = next->next;
+ drawfreedimage(next);
+ return;
+ }
+ d = next;
+ }
+ error(Enodrawimage);
+}
+
+void
+drawaddname(Client *client, DImage *di, int n, char *str)
+{
+ DName *name, *ename, *new, *t;
+
+ name = sdraw.name;
+ ename = &name[sdraw.nname];
+ for(; name<ename; name++)
+ if(drawcmp(name->name, str, n) == 0)
+ error(Enameused);
+ t = smalloc((sdraw.nname+1)*sizeof(DName));
+ memmove(t, sdraw.name, sdraw.nname*sizeof(DName));
+ free(sdraw.name);
+ sdraw.name = t;
+ new = &sdraw.name[sdraw.nname++];
+ new->name = smalloc(n+1);
+ memmove(new->name, str, n);
+ new->name[n] = 0;
+ new->dimage = di;
+ new->client = client;
+ new->vers = ++sdraw.vers;
+}
+
+Client*
+drawnewclient(void)
+{
+ Client *cl, **cp;
+ int i;
+
+ for(i=0; i<sdraw.nclient; i++){
+ cl = sdraw.client[i];
+ if(cl == 0)
+ break;
+ }
+ if(i == sdraw.nclient){
+ cp = malloc((sdraw.nclient+1)*sizeof(Client*));
+ if(cp == 0)
+ return 0;
+ memmove(cp, sdraw.client, sdraw.nclient*sizeof(Client*));
+ free(sdraw.client);
+ sdraw.client = cp;
+ sdraw.nclient++;
+ cp[i] = 0;
+ }
+ cl = malloc(sizeof(Client));
+ if(cl == 0)
+ return 0;
+ memset(cl, 0, sizeof(Client));
+ cl->slot = i;
+ cl->clientid = ++sdraw.clientid;
+ cl->op = SoverD;
+ sdraw.client[i] = cl;
+ return cl;
+}
+
+static int
+drawclientop(Client *cl)
+{
+ int op;
+
+ op = cl->op;
+ cl->op = SoverD;
+ return op;
+}
+
+int
+drawhasclients(void)
+{
+ /*
+ * if draw has ever been used, we can't resize the frame buffer,
+ * even if all clients have exited (nclients is cumulative); it's too
+ * hard to make work.
+ */
+ return sdraw.nclient != 0;
+}
+
+Client*
+drawclientofpath(ulong path)
+{
+ Client *cl;
+ int slot;
+
+ slot = CLIENTPATH(path);
+ if(slot == 0)
+ return nil;
+ cl = sdraw.client[slot-1];
+ if(cl==0 || cl->clientid==0)
+ return nil;
+ return cl;
+}
+
+
+Client*
+drawclient(Chan *c)
+{
+ Client *client;
+
+ client = drawclientofpath(c->qid.path);
+ if(client == nil)
+ error(Enoclient);
+ return client;
+}
+
+Memimage*
+drawimage(Client *client, uchar *a)
+{
+ DImage *d;
+
+ d = drawlookup(client, BGLONG(a), 1);
+ if(d == nil)
+ error(Enodrawimage);
+ return d->image;
+}
+
+void
+drawrectangle(Rectangle *r, uchar *a)
+{
+ r->min.x = BGLONG(a+0*4);
+ r->min.y = BGLONG(a+1*4);
+ r->max.x = BGLONG(a+2*4);
+ r->max.y = BGLONG(a+3*4);
+}
+
+void
+drawpoint(Point *p, uchar *a)
+{
+ p->x = BGLONG(a+0*4);
+ p->y = BGLONG(a+1*4);
+}
+
+#define isvgascreen(dst) 1
+
+
+Point
+drawchar(Memimage *dst, Memimage *rdst, Point p,
+ Memimage *src, Point *sp, DImage *font, int index, int op)
+{
+ FChar *fc;
+ Rectangle r;
+ Point sp1;
+ static Memimage *tmp;
+
+ fc = &font->fchar[index];
+ r.min.x = p.x+fc->left;
+ r.min.y = p.y-(font->ascent-fc->miny);
+ r.max.x = r.min.x+(fc->maxx-fc->minx);
+ r.max.y = r.min.y+(fc->maxy-fc->miny);
+ sp1.x = sp->x+fc->left;
+ sp1.y = sp->y+fc->miny;
+
+ /*
+ * If we're drawing greyscale fonts onto a VGA screen,
+ * it's very costly to read the screen memory to do the
+ * alpha blending inside memdraw. If this is really a stringbg,
+ * then rdst is the bg image (in main memory) which we can
+ * refer to for the underlying dst pixels instead of reading dst
+ * directly.
+ */
+ if(1 || (isvgascreen(dst) && !isvgascreen(rdst) /*&& font->image->depth > 1*/)){
+ if(tmp == nil || tmp->chan != dst->chan || Dx(tmp->r) < Dx(r) || Dy(tmp->r) < Dy(r)){
+ if(tmp)
+ freememimage(tmp);
+ tmp = allocmemimage(Rect(0,0,Dx(r),Dy(r)), dst->chan);
+ if(tmp == nil)
+ goto fallback;
+ }
+ memdraw(tmp, Rect(0,0,Dx(r),Dy(r)), rdst, r.min, memopaque, ZP, S);
+ memdraw(tmp, Rect(0,0,Dx(r),Dy(r)), src, sp1, font->image, Pt(fc->minx, fc->miny), op);
+ memdraw(dst, r, tmp, ZP, memopaque, ZP, S);
+ }else{
+ fallback:
+ memdraw(dst, r, src, sp1, font->image, Pt(fc->minx, fc->miny), op);
+ }
+
+ p.x += fc->width;
+ sp->x += fc->width;
+ return p;
+}
+
+static int
+initscreenimage(void)
+{
+ int width, depth;
+ ulong chan;
+ void *X;
+ Rectangle r;
+
+ if(screenimage != nil)
+ return 1;
+
+ screendata.base = nil;
+ screendata.bdata = attachscreen(&r, &chan, &depth, &width, &sdraw.softscreen, &X);
+ if(screendata.bdata == nil && X == nil)
+ return 0;
+ screendata.ref = 1;
+
+ screenimage = allocmemimaged(r, chan, &screendata, X);
+ if(screenimage == nil){
+ /* RSC: BUG: detach screen */
+ return 0;
+ }
+
+ screenimage->width = width;
+ screenimage->clipr = r;
+ return 1;
+}
+
+void
+deletescreenimage(void)
+{
+ qlock(&sdraw.lk);
+ /* RSC: BUG: detach screen */
+ if(screenimage)
+ freememimage(screenimage);
+ screenimage = nil;
+ qunlock(&sdraw.lk);
+}
+
+static Chan*
+drawattach(char *spec)
+{
+ qlock(&sdraw.lk);
+ if(!initscreenimage()){
+ qunlock(&sdraw.lk);
+ error("no frame buffer");
+ }
+ qunlock(&sdraw.lk);
+ return devattach('i', spec);
+}
+
+static Walkqid*
+drawwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ if(screendata.bdata == nil)
+ error("no frame buffer");
+ return devwalk(c, nc, name, nname, 0, 0, drawgen);
+}
+
+static int
+drawstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, 0, 0, drawgen);
+}
+
+static Chan*
+drawopen(Chan *c, int omode)
+{
+ Client *cl;
+
+ if(c->qid.type & QTDIR){
+ c = devopen(c, omode, 0, 0, drawgen);
+ c->iounit = IOUNIT;
+ }
+
+ qlock(&sdraw.lk);
+ if(waserror()){
+ qunlock(&sdraw.lk);
+ nexterror();
+ }
+
+ if(QID(c->qid) == Qnew){
+ cl = drawnewclient();
+ if(cl == 0)
+ error(Enodev);
+ c->qid.path = Qctl|((cl->slot+1)<<QSHIFT);
+ }
+
+ switch(QID(c->qid)){
+ case Qnew:
+ break;
+
+ case Qctl:
+ cl = drawclient(c);
+ if(cl->busy)
+ error(Einuse);
+ cl->busy = 1;
+ flushrect = Rect(10000, 10000, -10000, -10000);
+ drawinstall(cl, 0, screenimage, 0);
+ incref(&cl->r);
+ break;
+ case Qcolormap:
+ case Qdata:
+ case Qrefresh:
+ cl = drawclient(c);
+ incref(&cl->r);
+ break;
+ }
+ qunlock(&sdraw.lk);
+ poperror();
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ c->iounit = IOUNIT;
+ return c;
+}
+
+static void
+drawclose(Chan *c)
+{
+ int i;
+ DImage *d, **dp;
+ Client *cl;
+ Refresh *r;
+
+ if(QID(c->qid) < Qcolormap) /* Qtopdir, Qnew, Q3rd, Q2nd have no client */
+ return;
+ qlock(&sdraw.lk);
+ if(waserror()){
+ qunlock(&sdraw.lk);
+ nexterror();
+ }
+
+ cl = drawclient(c);
+ if(QID(c->qid) == Qctl)
+ cl->busy = 0;
+ if((c->flag&COPEN) && (decref(&cl->r)==0)){
+ while((r = cl->refresh)){ /* assign = */
+ cl->refresh = r->next;
+ free(r);
+ }
+ /* free names */
+ for(i=0; i<sdraw.nname; )
+ if(sdraw.name[i].client == cl)
+ drawdelname(sdraw.name+i);
+ else
+ i++;
+ while(cl->cscreen)
+ drawuninstallscreen(cl, cl->cscreen);
+ /* all screens are freed, so now we can free images */
+ dp = cl->dimage;
+ for(i=0; i<NHASH; i++){
+ while((d = *dp) != nil){
+ *dp = d->next;
+ drawfreedimage(d);
+ }
+ dp++;
+ }
+ sdraw.client[cl->slot] = 0;
+ drawflush(); /* to erase visible, now dead windows */
+ free(cl);
+ }
+ qunlock(&sdraw.lk);
+ poperror();
+}
+
+long
+drawread(Chan *c, void *a, long n, vlong off)
+{
+ int index, m;
+ ulong red, green, blue;
+ Client *cl;
+ uchar *p;
+ Refresh *r;
+ DImage *di;
+ Memimage *i;
+ ulong offset = off;
+ char buf[16];
+
+ if(c->qid.type & QTDIR)
+ return devdirread(c, a, n, 0, 0, drawgen);
+ cl = drawclient(c);
+ qlock(&sdraw.lk);
+ if(waserror()){
+ qunlock(&sdraw.lk);
+ nexterror();
+ }
+ switch(QID(c->qid)){
+ case Qctl:
+ if(n < 12*12)
+ error(Eshortread);
+ if(cl->infoid < 0)
+ error(Enodrawimage);
+ if(cl->infoid == 0){
+ i = screenimage;
+ if(i == nil)
+ error(Enodrawimage);
+ }else{
+ di = drawlookup(cl, cl->infoid, 1);
+ if(di == nil)
+ error(Enodrawimage);
+ i = di->image;
+ }
+ n = sprint(a, "%11d %11d %11s %11d %11d %11d %11d %11d %11d %11d %11d %11d ",
+ cl->clientid, cl->infoid, chantostr(buf, i->chan), (i->flags&Frepl)==Frepl,
+ i->r.min.x, i->r.min.y, i->r.max.x, i->r.max.y,
+ i->clipr.min.x, i->clipr.min.y, i->clipr.max.x, i->clipr.max.y);
+ cl->infoid = -1;
+ break;
+
+ case Qcolormap:
+ drawactive(1); /* to restore map from backup */
+ p = malloc(4*12*256+1);
+ if(p == 0)
+ error(Enomem);
+ m = 0;
+ for(index = 0; index < 256; index++){
+ getcolor(index, &red, &green, &blue);
+ m += sprint((char*)p+m, "%11d %11lud %11lud %11lud\n", index, red>>24, green>>24, blue>>24);
+ }
+ n = readstr(offset, a, n, (char*)p);
+ free(p);
+ break;
+
+ case Qdata:
+ if(cl->readdata == nil)
+ error("no draw data");
+ if(n < cl->nreaddata)
+ error(Eshortread);
+ n = cl->nreaddata;
+ memmove(a, cl->readdata, cl->nreaddata);
+ free(cl->readdata);
+ cl->readdata = nil;
+ break;
+
+ case Qrefresh:
+ if(n < 5*4)
+ error(Ebadarg);
+ for(;;){
+ if(cl->refreshme || cl->refresh)
+ break;
+ qunlock(&sdraw.lk);
+ if(waserror()){
+ qlock(&sdraw.lk); /* restore lock for waserror() above */
+ nexterror();
+ }
+ sleep(&cl->refrend, drawrefactive, cl);
+ poperror();
+ qlock(&sdraw.lk);
+ }
+ p = a;
+ while(cl->refresh && n>=5*4){
+ r = cl->refresh;
+ BPLONG(p+0*4, r->dimage->id);
+ BPLONG(p+1*4, r->r.min.x);
+ BPLONG(p+2*4, r->r.min.y);
+ BPLONG(p+3*4, r->r.max.x);
+ BPLONG(p+4*4, r->r.max.y);
+ cl->refresh = r->next;
+ free(r);
+ p += 5*4;
+ n -= 5*4;
+ }
+ cl->refreshme = 0;
+ n = p-(uchar*)a;
+ }
+ qunlock(&sdraw.lk);
+ poperror();
+ return n;
+}
+
+void
+drawwakeall(void)
+{
+ Client *cl;
+ int i;
+
+ for(i=0; i<sdraw.nclient; i++){
+ cl = sdraw.client[i];
+ if(cl && (cl->refreshme || cl->refresh))
+ wakeup(&cl->refrend);
+ }
+}
+
+static long
+drawwrite(Chan *c, void *a, long n, vlong offset)
+{
+ char buf[128], *fields[4], *q;
+ Client *cl;
+ int i, m, red, green, blue, x;
+
+ USED(offset);
+
+ if(c->qid.type & QTDIR)
+ error(Eisdir);
+ cl = drawclient(c);
+ qlock(&sdraw.lk);
+ if(waserror()){
+ drawwakeall();
+ qunlock(&sdraw.lk);
+ nexterror();
+ }
+ switch(QID(c->qid)){
+ case Qctl:
+ if(n != 4)
+ error("unknown draw control request");
+ cl->infoid = BGLONG((uchar*)a);
+ break;
+
+ case Qcolormap:
+ drawactive(1); /* to restore map from backup */
+ m = n;
+ n = 0;
+ while(m > 0){
+ x = m;
+ if(x > sizeof(buf)-1)
+ x = sizeof(buf)-1;
+ q = memccpy(buf, a, '\n', x);
+ if(q == 0)
+ break;
+ i = q-buf;
+ n += i;
+ a = (char*)a + i;
+ m -= i;
+ *q = 0;
+ if(tokenize(buf, fields, nelem(fields)) != 4)
+ error(Ebadarg);
+ i = strtoul(fields[0], 0, 0);
+ red = strtoul(fields[1], 0, 0);
+ green = strtoul(fields[2], 0, 0);
+ blue = strtoul(fields[3], &q, 0);
+ if(fields[3] == q)
+ error(Ebadarg);
+ if(red>255 || green>255 || blue>255 || i<0 || i>255)
+ error(Ebadarg);
+ red |= red<<8;
+ red |= red<<16;
+ green |= green<<8;
+ green |= green<<16;
+ blue |= blue<<8;
+ blue |= blue<<16;
+ setcolor(i, red, green, blue);
+ }
+ break;
+
+ case Qdata:
+ drawmesg(cl, a, n);
+ drawwakeall();
+ break;
+
+ default:
+ error(Ebadusefd);
+ }
+ qunlock(&sdraw.lk);
+ poperror();
+ return n;
+}
+
+uchar*
+drawcoord(uchar *p, uchar *maxp, int oldx, int *newx)
+{
+ int b, x;
+
+ if(p >= maxp)
+ error(Eshortdraw);
+ b = *p++;
+ x = b & 0x7F;
+ if(b & 0x80){
+ if(p+1 >= maxp)
+ error(Eshortdraw);
+ x |= *p++ << 7;
+ x |= *p++ << 15;
+ if(x & (1<<22))
+ x |= ~0<<23;
+ }else{
+ if(b & 0x40)
+ x |= ~0<<7;
+ x += oldx;
+ }
+ *newx = x;
+ return p;
+}
+
+static void
+printmesg(char *fmt, uchar *a, int plsprnt)
+{
+ char buf[256];
+ char *p, *q;
+ int s;
+
+ if(1|| plsprnt==0){
+ SET(s);
+ SET(q);
+ SET(p);
+ USED(fmt);
+ USED(a);
+ p = buf;
+ USED(p);
+ USED(q);
+ USED(s);
+ return;
+ }
+ q = buf;
+ *q++ = *a++;
+ for(p=fmt; *p; p++){
+ switch(*p){
+ case 'l':
+ q += sprint(q, " %ld", (long)BGLONG(a));
+ a += 4;
+ break;
+ case 'L':
+ q += sprint(q, " %.8lux", (ulong)BGLONG(a));
+ a += 4;
+ break;
+ case 'R':
+ q += sprint(q, " [%d %d %d %d]", BGLONG(a), BGLONG(a+4), BGLONG(a+8), BGLONG(a+12));
+ a += 16;
+ break;
+ case 'P':
+ q += sprint(q, " [%d %d]", BGLONG(a), BGLONG(a+4));
+ a += 8;
+ break;
+ case 'b':
+ q += sprint(q, " %d", *a++);
+ break;
+ case 's':
+ q += sprint(q, " %d", BGSHORT(a));
+ a += 2;
+ break;
+ case 'S':
+ q += sprint(q, " %.4ux", BGSHORT(a));
+ a += 2;
+ break;
+ }
+ }
+ *q++ = '\n';
+ *q = 0;
+ iprint("%.*s", (int)(q-buf), buf);
+}
+
+void
+drawmesg(Client *client, void *av, int n)
+{
+ int c, repl, m, y, dstid, scrnid, ni, ci, j, nw, e0, e1, op, ox, oy, oesize, esize, doflush;
+ uchar *u, *a, refresh;
+ char *fmt;
+ ulong value, chan;
+ Rectangle r, clipr;
+ Point p, q, *pp, sp;
+ Memimage *i, *dst, *src, *mask;
+ Memimage *l, **lp;
+ Memscreen *scrn;
+ DImage *font, *ll, *di, *ddst, *dsrc;
+ DName *dn;
+ DScreen *dscrn;
+ FChar *fc;
+ Refx *refx;
+ CScreen *cs;
+ Refreshfn reffn;
+
+ a = av;
+ m = 0;
+ fmt = nil;
+ if(waserror()){
+ if(fmt) printmesg(fmt, a, 1);
+ /* iprint("error: %s\n", up->errstr); */
+ nexterror();
+ }
+ while((n-=m) > 0){
+ USED(fmt);
+ a += m;
+ switch(*a){
+ default:
+ error("bad draw command");
+ /* new allocate: 'b' id[4] screenid[4] refresh[1] chan[4] repl[1] R[4*4] clipR[4*4] rrggbbaa[4] */
+ case 'b':
+ printmesg(fmt="LLbLbRRL", a, 0);
+ m = 1+4+4+1+4+1+4*4+4*4+4;
+ if(n < m)
+ error(Eshortdraw);
+ dstid = BGLONG(a+1);
+ scrnid = BGSHORT(a+5);
+ refresh = a[9];
+ chan = BGLONG(a+10);
+ repl = a[14];
+ drawrectangle(&r, a+15);
+ drawrectangle(&clipr, a+31);
+ value = BGLONG(a+47);
+ if(drawlookup(client, dstid, 0))
+ error(Eimageexists);
+ if(scrnid){
+ dscrn = drawlookupscreen(client, scrnid, &cs);
+ scrn = dscrn->screen;
+ if(repl || chan!=scrn->image->chan)
+ error("image parameters incompatible with screen");
+ reffn = 0;
+ switch(refresh){
+ case Refbackup:
+ break;
+ case Refnone:
+ reffn = memlnorefresh;
+ break;
+ case Refmesg:
+ reffn = drawrefresh;
+ break;
+ default:
+ error("unknown refresh method");
+ }
+ l = memlalloc(scrn, r, reffn, 0, value);
+ if(l == 0)
+ error(Edrawmem);
+ addflush(l->layer->screenr);
+ l->clipr = clipr;
+ rectclip(&l->clipr, r);
+ if(drawinstall(client, dstid, l, dscrn) == 0){
+ memldelete(l);
+ error(Edrawmem);
+ }
+ dscrn->ref++;
+ if(reffn){
+ refx = nil;
+ if(reffn == drawrefresh){
+ refx = malloc(sizeof(Refx));
+ if(refx == 0){
+ drawuninstall(client, dstid);
+ error(Edrawmem);
+ }
+ refx->client = client;
+ refx->dimage = drawlookup(client, dstid, 1);
+ }
+ memlsetrefresh(l, reffn, refx);
+ }
+ continue;
+ }
+ i = allocmemimage(r, chan);
+ if(i == 0)
+ error(Edrawmem);
+ if(repl)
+ i->flags |= Frepl;
+ i->clipr = clipr;
+ if(!repl)
+ rectclip(&i->clipr, r);
+ if(drawinstall(client, dstid, i, 0) == 0){
+ freememimage(i);
+ error(Edrawmem);
+ }
+ memfillcolor(i, value);
+ continue;
+
+ /* allocate screen: 'A' id[4] imageid[4] fillid[4] public[1] */
+ case 'A':
+ printmesg(fmt="LLLb", a, 1);
+ m = 1+4+4+4+1;
+ if(n < m)
+ error(Eshortdraw);
+ dstid = BGLONG(a+1);
+ if(dstid == 0)
+ error(Ebadarg);
+ if(drawlookupdscreen(dstid))
+ error(Escreenexists);
+ ddst = drawlookup(client, BGLONG(a+5), 1);
+ dsrc = drawlookup(client, BGLONG(a+9), 1);
+ if(ddst==0 || dsrc==0)
+ error(Enodrawimage);
+ if(drawinstallscreen(client, 0, dstid, ddst, dsrc, a[13]) == 0)
+ error(Edrawmem);
+ continue;
+
+ /* set repl and clip: 'c' dstid[4] repl[1] clipR[4*4] */
+ case 'c':
+ printmesg(fmt="LbR", a, 0);
+ m = 1+4+1+4*4;
+ if(n < m)
+ error(Eshortdraw);
+ ddst = drawlookup(client, BGLONG(a+1), 1);
+ if(ddst == nil)
+ error(Enodrawimage);
+ if(ddst->name)
+ error("can't change repl/clipr of shared image");
+ dst = ddst->image;
+ if(a[5])
+ dst->flags |= Frepl;
+ drawrectangle(&dst->clipr, a+6);
+ continue;
+
+ /* draw: 'd' dstid[4] srcid[4] maskid[4] R[4*4] P[2*4] P[2*4] */
+ case 'd':
+ printmesg(fmt="LLLRPP", a, 0);
+ m = 1+4+4+4+4*4+2*4+2*4;
+ if(n < m)
+ error(Eshortdraw);
+ dst = drawimage(client, a+1);
+ dstid = BGLONG(a+1);
+ src = drawimage(client, a+5);
+ mask = drawimage(client, a+9);
+ drawrectangle(&r, a+13);
+ drawpoint(&p, a+29);
+ drawpoint(&q, a+37);
+ op = drawclientop(client);
+ memdraw(dst, r, src, p, mask, q, op);
+ dstflush(dstid, dst, r);
+ continue;
+
+ /* toggle debugging: 'D' val[1] */
+ case 'D':
+ printmesg(fmt="b", a, 0);
+ m = 1+1;
+ if(n < m)
+ error(Eshortdraw);
+ drawdebug = a[1];
+ continue;
+
+ /* ellipse: 'e' dstid[4] srcid[4] center[2*4] a[4] b[4] thick[4] sp[2*4] alpha[4] phi[4]*/
+ case 'e':
+ case 'E':
+ printmesg(fmt="LLPlllPll", a, 0);
+ m = 1+4+4+2*4+4+4+4+2*4+2*4;
+ if(n < m)
+ error(Eshortdraw);
+ dst = drawimage(client, a+1);
+ dstid = BGLONG(a+1);
+ src = drawimage(client, a+5);
+ drawpoint(&p, a+9);
+ e0 = BGLONG(a+17);
+ e1 = BGLONG(a+21);
+ if(e0<0 || e1<0)
+ error("invalid ellipse semidiameter");
+ j = BGLONG(a+25);
+ if(j < 0)
+ error("negative ellipse thickness");
+ drawpoint(&sp, a+29);
+ c = j;
+ if(*a == 'E')
+ c = -1;
+ ox = BGLONG(a+37);
+ oy = BGLONG(a+41);
+ op = drawclientop(client);
+ /* high bit indicates arc angles are present */
+ if(ox & (1U<<31)){
+ if((ox & (1<<30)) == 0)
+ ox &= ~(1U<<31);
+ memarc(dst, p, e0, e1, c, src, sp, ox, oy, op);
+ }else
+ memellipse(dst, p, e0, e1, c, src, sp, op);
+ dstflush(dstid, dst, Rect(p.x-e0-j, p.y-e1-j, p.x+e0+j+1, p.y+e1+j+1));
+ continue;
+
+ /* free: 'f' id[4] */
+ case 'f':
+ printmesg(fmt="L", a, 1);
+ m = 1+4;
+ if(n < m)
+ error(Eshortdraw);
+ ll = drawlookup(client, BGLONG(a+1), 0);
+ if(ll && ll->dscreen && ll->dscreen->owner != client)
+ ll->dscreen->owner->refreshme = 1;
+ drawuninstall(client, BGLONG(a+1));
+ continue;
+
+ /* free screen: 'F' id[4] */
+ case 'F':
+ printmesg(fmt="L", a, 1);
+ m = 1+4;
+ if(n < m)
+ error(Eshortdraw);
+ drawlookupscreen(client, BGLONG(a+1), &cs);
+ drawuninstallscreen(client, cs);
+ continue;
+
+ /* initialize font: 'i' fontid[4] nchars[4] ascent[1] */
+ case 'i':
+ printmesg(fmt="Llb", a, 1);
+ m = 1+4+4+1;
+ if(n < m)
+ error(Eshortdraw);
+ dstid = BGLONG(a+1);
+ if(dstid == 0)
+ error("can't use display as font");
+ font = drawlookup(client, dstid, 1);
+ if(font == 0)
+ error(Enodrawimage);
+ if(font->image->layer)
+ error("can't use window as font");
+ ni = BGLONG(a+5);
+ if(ni<=0 || ni>4096)
+ error("bad font size (4096 chars max)");
+ free(font->fchar); /* should we complain if non-zero? */
+ font->fchar = malloc(ni*sizeof(FChar));
+ if(font->fchar == 0)
+ error("no memory for font");
+ memset(font->fchar, 0, ni*sizeof(FChar));
+ font->nfchar = ni;
+ font->ascent = a[9];
+ continue;
+
+ /* load character: 'l' fontid[4] srcid[4] index[2] R[4*4] P[2*4] left[1] width[1] */
+ case 'l':
+ printmesg(fmt="LLSRPbb", a, 0);
+ m = 1+4+4+2+4*4+2*4+1+1;
+ if(n < m)
+ error(Eshortdraw);
+ font = drawlookup(client, BGLONG(a+1), 1);
+ if(font == 0)
+ error(Enodrawimage);
+ if(font->nfchar == 0)
+ error(Enotfont);
+ src = drawimage(client, a+5);
+ ci = BGSHORT(a+9);
+ if(ci >= font->nfchar)
+ error(Eindex);
+ drawrectangle(&r, a+11);
+ drawpoint(&p, a+27);
+ memdraw(font->image, r, src, p, memopaque, p, S);
+ fc = &font->fchar[ci];
+ fc->minx = r.min.x;
+ fc->maxx = r.max.x;
+ fc->miny = r.min.y;
+ fc->maxy = r.max.y;
+ fc->left = a[35];
+ fc->width = a[36];
+ continue;
+
+ /* draw line: 'L' dstid[4] p0[2*4] p1[2*4] end0[4] end1[4] radius[4] srcid[4] sp[2*4] */
+ case 'L':
+ printmesg(fmt="LPPlllLP", a, 0);
+ m = 1+4+2*4+2*4+4+4+4+4+2*4;
+ if(n < m)
+ error(Eshortdraw);
+ dst = drawimage(client, a+1);
+ dstid = BGLONG(a+1);
+ drawpoint(&p, a+5);
+ drawpoint(&q, a+13);
+ e0 = BGLONG(a+21);
+ e1 = BGLONG(a+25);
+ j = BGLONG(a+29);
+ if(j < 0)
+ error("negative line width");
+ src = drawimage(client, a+33);
+ drawpoint(&sp, a+37);
+ op = drawclientop(client);
+ memline(dst, p, q, e0, e1, j, src, sp, op);
+ /* avoid memlinebbox if possible */
+ if(dstid==0 || dst->layer!=nil){
+ /* BUG: this is terribly inefficient: update maximal containing rect*/
+ r = memlinebbox(p, q, e0, e1, j);
+ dstflush(dstid, dst, insetrect(r, -(1+1+j)));
+ }
+ continue;
+
+ /* create image mask: 'm' newid[4] id[4] */
+/*
+ *
+ case 'm':
+ printmesg("LL", a, 0);
+ m = 4+4;
+ if(n < m)
+ error(Eshortdraw);
+ break;
+ *
+ */
+
+ /* attach to a named image: 'n' dstid[4] j[1] name[j] */
+ case 'n':
+ printmesg(fmt="Lz", a, 0);
+ m = 1+4+1;
+ if(n < m)
+ error(Eshortdraw);
+ j = a[5];
+ if(j == 0) /* give me a non-empty name please */
+ error(Eshortdraw);
+ m += j;
+ if(n < m)
+ error(Eshortdraw);
+ dstid = BGLONG(a+1);
+ if(drawlookup(client, dstid, 0))
+ error(Eimageexists);
+ dn = drawlookupname(j, (char*)a+6);
+ if(dn == nil)
+ error(Enoname);
+ if(drawinstall(client, dstid, dn->dimage->image, 0) == 0)
+ error(Edrawmem);
+ di = drawlookup(client, dstid, 0);
+ if(di == 0)
+ error("draw: can't happen");
+ di->vers = dn->vers;
+ di->name = smalloc(j+1);
+ di->fromname = dn->dimage;
+ di->fromname->ref++;
+ memmove(di->name, a+6, j);
+ di->name[j] = 0;
+ client->infoid = dstid;
+ continue;
+
+ /* name an image: 'N' dstid[4] in[1] j[1] name[j] */
+ case 'N':
+ printmesg(fmt="Lbz", a, 0);
+ m = 1+4+1+1;
+ if(n < m)
+ error(Eshortdraw);
+ c = a[5];
+ j = a[6];
+ if(j == 0) /* give me a non-empty name please */
+ error(Eshortdraw);
+ m += j;
+ if(n < m)
+ error(Eshortdraw);
+ di = drawlookup(client, BGLONG(a+1), 0);
+ if(di == 0)
+ error(Enodrawimage);
+ if(di->name)
+ error(Enamed);
+ if(c)
+ drawaddname(client, di, j, (char*)a+7);
+ else{
+ dn = drawlookupname(j, (char*)a+7);
+ if(dn == nil)
+ error(Enoname);
+ if(dn->dimage != di)
+ error(Ewrongname);
+ drawdelname(dn);
+ }
+ continue;
+
+ /* position window: 'o' id[4] r.min [2*4] screenr.min [2*4] */
+ case 'o':
+ printmesg(fmt="LPP", a, 0);
+ m = 1+4+2*4+2*4;
+ if(n < m)
+ error(Eshortdraw);
+ dst = drawimage(client, a+1);
+ if(dst->layer){
+ drawpoint(&p, a+5);
+ drawpoint(&q, a+13);
+ r = dst->layer->screenr;
+ ni = memlorigin(dst, p, q);
+ if(ni < 0)
+ error("image origin failed");
+ if(ni > 0){
+ addflush(r);
+ addflush(dst->layer->screenr);
+ ll = drawlookup(client, BGLONG(a+1), 1);
+ drawrefreshscreen(ll, client);
+ }
+ }
+ continue;
+
+ /* set compositing operator for next draw operation: 'O' op */
+ case 'O':
+ printmesg(fmt="b", a, 0);
+ m = 1+1;
+ if(n < m)
+ error(Eshortdraw);
+ client->op = a[1];
+ continue;
+
+ /* filled polygon: 'P' dstid[4] n[2] wind[4] ignore[2*4] srcid[4] sp[2*4] p0[2*4] dp[2*2*n] */
+ /* polygon: 'p' dstid[4] n[2] end0[4] end1[4] radius[4] srcid[4] sp[2*4] p0[2*4] dp[2*2*n] */
+ case 'p':
+ case 'P':
+ printmesg(fmt="LslllLPP", a, 0);
+ m = 1+4+2+4+4+4+4+2*4;
+ if(n < m)
+ error(Eshortdraw);
+ dstid = BGLONG(a+1);
+ dst = drawimage(client, a+1);
+ ni = BGSHORT(a+5);
+ if(ni < 0)
+ error("negative count in polygon");
+ e0 = BGLONG(a+7);
+ e1 = BGLONG(a+11);
+ j = 0;
+ if(*a == 'p'){
+ j = BGLONG(a+15);
+ if(j < 0)
+ error("negative polygon line width");
+ }
+ src = drawimage(client, a+19);
+ drawpoint(&sp, a+23);
+ drawpoint(&p, a+31);
+ ni++;
+ pp = malloc(ni*sizeof(Point));
+ if(pp == nil)
+ error(Enomem);
+ doflush = 0;
+ if(dstid==0 || (dst->layer && dst->layer->screen->image->data == screenimage->data))
+ doflush = 1; /* simplify test in loop */
+ ox = oy = 0;
+ esize = 0;
+ u = a+m;
+ for(y=0; y<ni; y++){
+ q = p;
+ oesize = esize;
+ u = drawcoord(u, a+n, ox, &p.x);
+ u = drawcoord(u, a+n, oy, &p.y);
+ ox = p.x;
+ oy = p.y;
+ if(doflush){
+ esize = j;
+ if(*a == 'p'){
+ if(y == 0){
+ c = memlineendsize(e0);
+ if(c > esize)
+ esize = c;
+ }
+ if(y == ni-1){
+ c = memlineendsize(e1);
+ if(c > esize)
+ esize = c;
+ }
+ }
+ if(*a=='P' && e0!=1 && e0 !=~0)
+ r = dst->clipr;
+ else if(y > 0){
+ r = Rect(q.x-oesize, q.y-oesize, q.x+oesize+1, q.y+oesize+1);
+ combinerect(&r, Rect(p.x-esize, p.y-esize, p.x+esize+1, p.y+esize+1));
+ }
+ if(rectclip(&r, dst->clipr)) /* should perhaps be an arg to dstflush */
+ dstflush(dstid, dst, r);
+ }
+ pp[y] = p;
+ }
+ if(y == 1)
+ dstflush(dstid, dst, Rect(p.x-esize, p.y-esize, p.x+esize+1, p.y+esize+1));
+ op = drawclientop(client);
+ if(*a == 'p')
+ mempoly(dst, pp, ni, e0, e1, j, src, sp, op);
+ else
+ memfillpoly(dst, pp, ni, e0, src, sp, op);
+ free(pp);
+ m = u-a;
+ continue;
+
+ /* read: 'r' id[4] R[4*4] */
+ case 'r':
+ printmesg(fmt="LR", a, 0);
+ m = 1+4+4*4;
+ if(n < m)
+ error(Eshortdraw);
+ i = drawimage(client, a+1);
+ drawrectangle(&r, a+5);
+ if(!rectinrect(r, i->r))
+ error(Ereadoutside);
+ c = bytesperline(r, i->depth);
+ c *= Dy(r);
+ free(client->readdata);
+ client->readdata = mallocz(c, 0);
+ if(client->readdata == nil)
+ error("readimage malloc failed");
+ client->nreaddata = memunload(i, r, client->readdata, c);
+ if(client->nreaddata < 0){
+ free(client->readdata);
+ client->readdata = nil;
+ error("bad readimage call");
+ }
+ continue;
+
+ /* string: 's' dstid[4] srcid[4] fontid[4] P[2*4] clipr[4*4] sp[2*4] ni[2] ni*(index[2]) */
+ /* stringbg: 'x' dstid[4] srcid[4] fontid[4] P[2*4] clipr[4*4] sp[2*4] ni[2] bgid[4] bgpt[2*4] ni*(index[2]) */
+ case 's':
+ case 'x':
+ printmesg(fmt="LLLPRPs", a, 0);
+ m = 1+4+4+4+2*4+4*4+2*4+2;
+ if(*a == 'x')
+ m += 4+2*4;
+ if(n < m)
+ error(Eshortdraw);
+
+ dst = drawimage(client, a+1);
+ dstid = BGLONG(a+1);
+ src = drawimage(client, a+5);
+ font = drawlookup(client, BGLONG(a+9), 1);
+ if(font == 0)
+ error(Enodrawimage);
+ if(font->nfchar == 0)
+ error(Enotfont);
+ drawpoint(&p, a+13);
+ drawrectangle(&r, a+21);
+ drawpoint(&sp, a+37);
+ ni = BGSHORT(a+45);
+ u = a+m;
+ m += ni*2;
+ if(n < m)
+ error(Eshortdraw);
+ clipr = dst->clipr;
+ dst->clipr = r;
+ op = drawclientop(client);
+ l = dst;
+ if(*a == 'x'){
+ /* paint background */
+ l = drawimage(client, a+47);
+ drawpoint(&q, a+51);
+ r.min.x = p.x;
+ r.min.y = p.y-font->ascent;
+ r.max.x = p.x;
+ r.max.y = r.min.y+Dy(font->image->r);
+ j = ni;
+ while(--j >= 0){
+ ci = BGSHORT(u);
+ if(ci<0 || ci>=font->nfchar){
+ dst->clipr = clipr;
+ error(Eindex);
+ }
+ r.max.x += font->fchar[ci].width;
+ u += 2;
+ }
+ memdraw(dst, r, l, q, memopaque, ZP, op);
+ u -= 2*ni;
+ }
+ q = p;
+ while(--ni >= 0){
+ ci = BGSHORT(u);
+ if(ci<0 || ci>=font->nfchar){
+ dst->clipr = clipr;
+ error(Eindex);
+ }
+ q = drawchar(dst, l, q, src, &sp, font, ci, op);
+ u += 2;
+ }
+ dst->clipr = clipr;
+ p.y -= font->ascent;
+ dstflush(dstid, dst, Rect(p.x, p.y, q.x, p.y+Dy(font->image->r)));
+ continue;
+
+ /* use public screen: 'S' id[4] chan[4] */
+ case 'S':
+ printmesg(fmt="Ll", a, 0);
+ m = 1+4+4;
+ if(n < m)
+ error(Eshortdraw);
+ dstid = BGLONG(a+1);
+ if(dstid == 0)
+ error(Ebadarg);
+ dscrn = drawlookupdscreen(dstid);
+ if(dscrn==0 || (dscrn->public==0 && dscrn->owner!=client))
+ error(Enodrawscreen);
+ if(dscrn->screen->image->chan != BGLONG(a+5))
+ error("inconsistent chan");
+ if(drawinstallscreen(client, dscrn, 0, 0, 0, 0) == 0)
+ error(Edrawmem);
+ continue;
+
+ /* top or bottom windows: 't' top[1] nw[2] n*id[4] */
+ case 't':
+ printmesg(fmt="bsL", a, 0);
+ m = 1+1+2;
+ if(n < m)
+ error(Eshortdraw);
+ nw = BGSHORT(a+2);
+ if(nw < 0)
+ error(Ebadarg);
+ if(nw == 0)
+ continue;
+ m += nw*4;
+ if(n < m)
+ error(Eshortdraw);
+ lp = malloc(nw*sizeof(Memimage*));
+ if(lp == 0)
+ error(Enomem);
+ if(waserror()){
+ free(lp);
+ nexterror();
+ }
+ for(j=0; j<nw; j++)
+ lp[j] = drawimage(client, a+1+1+2+j*4);
+ if(lp[0]->layer == 0)
+ error("images are not windows");
+ for(j=1; j<nw; j++)
+ if(lp[j]->layer->screen != lp[0]->layer->screen)
+ error("images not on same screen");
+ if(a[1])
+ memltofrontn(lp, nw);
+ else
+ memltorearn(lp, nw);
+ if(lp[0]->layer->screen->image->data == screenimage->data)
+ for(j=0; j<nw; j++)
+ addflush(lp[j]->layer->screenr);
+ ll = drawlookup(client, BGLONG(a+1+1+2), 1);
+ drawrefreshscreen(ll, client);
+ poperror();
+ free(lp);
+ continue;
+
+ /* visible: 'v' */
+ case 'v':
+ printmesg(fmt="", a, 0);
+ m = 1;
+ drawflush();
+ continue;
+
+ /* write: 'y' id[4] R[4*4] data[x*1] */
+ /* write from compressed data: 'Y' id[4] R[4*4] data[x*1] */
+ case 'y':
+ case 'Y':
+ printmesg(fmt="LR", a, 0);
+ // iprint("load %c\n", *a);
+ m = 1+4+4*4;
+ if(n < m)
+ error(Eshortdraw);
+ dstid = BGLONG(a+1);
+ dst = drawimage(client, a+1);
+ drawrectangle(&r, a+5);
+ if(!rectinrect(r, dst->r))
+ error(Ewriteoutside);
+ y = memload(dst, r, a+m, n-m, *a=='Y');
+ if(y < 0)
+ error("bad writeimage call");
+ dstflush(dstid, dst, r);
+ m += y;
+ continue;
+ }
+ }
+ poperror();
+}
+
+Dev drawdevtab = {
+ 'i',
+ "draw",
+
+ devreset,
+ devinit,
+ devshutdown,
+ drawattach,
+ drawwalk,
+ drawstat,
+ drawopen,
+ devcreate,
+ drawclose,
+ drawread,
+ devbread,
+ drawwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
+
+/*
+ * On 8 bit displays, load the default color map
+ */
+void
+drawcmap(void)
+{
+ int r, g, b, cr, cg, cb, v;
+ int num, den;
+ int i, j;
+
+ drawactive(1); /* to restore map from backup */
+ for(r=0,i=0; r!=4; r++)
+ for(v=0; v!=4; v++,i+=16){
+ for(g=0,j=v-r; g!=4; g++)
+ for(b=0;b!=4;b++,j++){
+ den = r;
+ if(g > den)
+ den = g;
+ if(b > den)
+ den = b;
+ if(den == 0) /* divide check -- pick grey shades */
+ cr = cg = cb = v*17;
+ else{
+ num = 17*(4*den+v);
+ cr = r*num/den;
+ cg = g*num/den;
+ cb = b*num/den;
+ }
+ setcolor(i+(j&15),
+ cr*0x01010101, cg*0x01010101, cb*0x01010101);
+ }
+ }
+}
+
+void
+drawblankscreen(int blank)
+{
+ int i, nc;
+ ulong *p;
+
+ if(blank == sdraw.blanked)
+ return;
+ if(!canqlock(&sdraw.lk))
+ return;
+ if(!initscreenimage()){
+ qunlock(&sdraw.lk);
+ return;
+ }
+ p = sdraw.savemap;
+ nc = screenimage->depth > 8 ? 256 : 1<<screenimage->depth;
+
+ /*
+ * blankscreen uses the hardware to blank the screen
+ * when possible. to help in cases when it is not possible,
+ * we set the color map to be all black.
+ */
+ if(blank == 0){ /* turn screen on */
+ for(i=0; i<nc; i++, p+=3)
+ setcolor(i, p[0], p[1], p[2]);
+ // blankscreen(0);
+ }else{ /* turn screen off */
+ // blankscreen(1);
+ for(i=0; i<nc; i++, p+=3){
+ getcolor(i, &p[0], &p[1], &p[2]);
+ setcolor(i, 0, 0, 0);
+ }
+ }
+ sdraw.blanked = blank;
+ qunlock(&sdraw.lk);
+}
+
+/*
+ * record activity on screen, changing blanking as appropriate
+ */
+void
+drawactive(int active)
+{
+/*
+ if(active){
+ drawblankscreen(0);
+ sdraw.blanktime = MACHP(0)->ticks;
+ }else{
+ if(blanktime && sdraw.blanktime && TK2SEC(MACHP(0)->ticks - sdraw.blanktime)/60 >= blanktime)
+ drawblankscreen(1);
+ }
+*/
+}
+
+int
+drawidletime(void)
+{
+ return 0;
+/* return TK2SEC(MACHP(0)->ticks - sdraw.blanktime)/60; */
+}
+
diff --git a/sys/src/cmd/unix/drawterm/kern/devfs-posix.c b/sys/src/cmd/unix/drawterm/kern/devfs-posix.c
new file mode 100755
index 000000000..23a7dbc4f
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devfs-posix.c
@@ -0,0 +1,635 @@
+#include "u.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h> /* for remove, rename */
+#include <limits.h>
+
+#ifndef NAME_MAX
+# define NAME_MAX 256
+#endif
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+
+typedef struct Ufsinfo Ufsinfo;
+
+enum
+{
+ NUID = 256,
+ NGID = 256,
+ MAXPATH = 1024,
+ MAXCOMP = 128
+};
+
+struct Ufsinfo
+{
+ int mode;
+ int fd;
+ int uid;
+ int gid;
+ DIR* dir;
+ vlong offset;
+ QLock oq;
+ char nextname[NAME_MAX];
+};
+
+char *base = "/";
+
+static Qid fsqid(char*, struct stat *);
+static void fspath(Chan*, char*, char*);
+static ulong fsdirread(Chan*, uchar*, int, ulong);
+static int fsomode(int);
+
+/* clumsy hack, but not worse than the Path stuff in the last one */
+static char*
+uc2name(Chan *c)
+{
+ char *s;
+
+ if(c->name == nil)
+ return "/";
+ s = c2name(c);
+ if(s[0]=='#' && s[1]=='U')
+ return s+2;
+ return s;
+}
+
+static char*
+lastelem(Chan *c)
+{
+ char *s, *t;
+
+ s = uc2name(c);
+ if((t = strrchr(s, '/')) == nil)
+ return s;
+ if(t[1] == 0)
+ return t;
+ return t+1;
+}
+
+static Chan*
+fsattach(char *spec)
+{
+ Chan *c;
+ struct stat stbuf;
+ static int devno;
+ Ufsinfo *uif;
+
+ if(stat(base, &stbuf) < 0)
+ error(strerror(errno));
+
+ c = devattach('U', spec);
+
+ uif = mallocz(sizeof(Ufsinfo), 1);
+ uif->mode = stbuf.st_mode;
+ uif->uid = stbuf.st_uid;
+ uif->gid = stbuf.st_gid;
+
+ c->aux = uif;
+ c->dev = devno++;
+ c->qid.type = QTDIR;
+/*print("fsattach %s\n", c2name(c));*/
+
+ return c;
+}
+
+static Chan*
+fsclone(Chan *c, Chan *nc)
+{
+ Ufsinfo *uif;
+
+ uif = mallocz(sizeof(Ufsinfo), 1);
+ *uif = *(Ufsinfo*)c->aux;
+ nc->aux = uif;
+
+ return nc;
+}
+
+static int
+fswalk1(Chan *c, char *name)
+{
+ struct stat stbuf;
+ char path[MAXPATH];
+ Ufsinfo *uif;
+
+ fspath(c, name, path);
+
+ /*print("** fs walk '%s' -> %s\n", path, name); */
+
+ if(stat(path, &stbuf) < 0)
+ return 0;
+
+ uif = c->aux;
+
+ uif->mode = stbuf.st_mode;
+ uif->uid = stbuf.st_uid;
+ uif->gid = stbuf.st_gid;
+
+ c->qid = fsqid(path, &stbuf);
+
+ return 1;
+}
+
+extern Cname* addelem(Cname*, char*);
+
+static Walkqid*
+fswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ int i;
+ Cname *cname;
+ Walkqid *wq;
+
+ if(nc != nil)
+ panic("fswalk: nc != nil");
+ wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+ nc = devclone(c);
+ cname = c->name;
+ incref(&cname->ref);
+
+ fsclone(c, nc);
+ wq->clone = nc;
+ for(i=0; i<nname; i++){
+ nc->name = cname;
+ if(fswalk1(nc, name[i]) == 0)
+ break;
+ cname = addelem(cname, name[i]);
+ wq->qid[i] = nc->qid;
+ }
+ nc->name = cname;
+ if(i != nname){
+ cclose(nc);
+ wq->clone = nil;
+ }
+ wq->nqid = i;
+ return wq;
+}
+
+static int
+fsstat(Chan *c, uchar *buf, int n)
+{
+ Dir d;
+ struct stat stbuf;
+ char path[MAXPATH];
+
+ if(n < BIT16SZ)
+ error(Eshortstat);
+
+ fspath(c, 0, path);
+ if(stat(path, &stbuf) < 0)
+ error(strerror(errno));
+
+ d.name = lastelem(c);
+ d.uid = "unknown";
+ d.gid = "unknown";
+ d.muid = "unknown";
+ d.qid = c->qid;
+ d.mode = (c->qid.type<<24)|(stbuf.st_mode&0777);
+ d.atime = stbuf.st_atime;
+ d.mtime = stbuf.st_mtime;
+ d.length = stbuf.st_size;
+ d.type = 'U';
+ d.dev = c->dev;
+ return convD2M(&d, buf, n);
+}
+
+static Chan*
+fsopen(Chan *c, int mode)
+{
+ char path[MAXPATH];
+ int m, isdir;
+ Ufsinfo *uif;
+
+/*print("fsopen %s\n", c2name(c));*/
+ m = mode & (OTRUNC|3);
+ switch(m) {
+ case 0:
+ break;
+ case 1:
+ case 1|16:
+ break;
+ case 2:
+ case 0|16:
+ case 2|16:
+ break;
+ case 3:
+ break;
+ default:
+ error(Ebadarg);
+ }
+
+ isdir = c->qid.type & QTDIR;
+
+ if(isdir && mode != OREAD)
+ error(Eperm);
+
+ m = fsomode(m & 3);
+ c->mode = openmode(mode);
+
+ uif = c->aux;
+
+ fspath(c, 0, path);
+ if(isdir) {
+ uif->dir = opendir(path);
+ if(uif->dir == 0)
+ error(strerror(errno));
+ }
+ else {
+ if(mode & OTRUNC)
+ m |= O_TRUNC;
+ uif->fd = open(path, m, 0666);
+
+ if(uif->fd < 0)
+ error(strerror(errno));
+ }
+ uif->offset = 0;
+
+ c->offset = 0;
+ c->flag |= COPEN;
+ return c;
+}
+
+static void
+fscreate(Chan *c, char *name, int mode, ulong perm)
+{
+ int fd, m;
+ char path[MAXPATH];
+ struct stat stbuf;
+ Ufsinfo *uif;
+
+ m = fsomode(mode&3);
+
+ fspath(c, name, path);
+
+ uif = c->aux;
+
+ if(perm & DMDIR) {
+ if(m)
+ error(Eperm);
+
+ if(mkdir(path, perm & 0777) < 0)
+ error(strerror(errno));
+
+ fd = open(path, 0);
+ if(fd >= 0) {
+ chmod(path, perm & 0777);
+ chown(path, uif->uid, uif->uid);
+ }
+ close(fd);
+
+ uif->dir = opendir(path);
+ if(uif->dir == 0)
+ error(strerror(errno));
+ }
+ else {
+ fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+ if(fd >= 0) {
+ if(m != 1) {
+ close(fd);
+ fd = open(path, m);
+ }
+ chmod(path, perm & 0777);
+ chown(path, uif->uid, uif->gid);
+ }
+ if(fd < 0)
+ error(strerror(errno));
+ uif->fd = fd;
+ }
+
+ if(stat(path, &stbuf) < 0)
+ error(strerror(errno));
+ c->qid = fsqid(path, &stbuf);
+ c->offset = 0;
+ c->flag |= COPEN;
+ c->mode = openmode(mode);
+}
+
+static void
+fsclose(Chan *c)
+{
+ Ufsinfo *uif;
+
+ uif = c->aux;
+
+ if(c->flag & COPEN) {
+ if(c->qid.type & QTDIR)
+ closedir(uif->dir);
+ else
+ close(uif->fd);
+ }
+
+ free(uif);
+}
+
+static long
+fsread(Chan *c, void *va, long n, vlong offset)
+{
+ int fd, r;
+ Ufsinfo *uif;
+
+/*print("fsread %s\n", c2name(c));*/
+ if(c->qid.type & QTDIR)
+ return fsdirread(c, va, n, offset);
+
+ uif = c->aux;
+ qlock(&uif->oq);
+ if(waserror()) {
+ qunlock(&uif->oq);
+ nexterror();
+ }
+ fd = uif->fd;
+ if(uif->offset != offset) {
+ r = lseek(fd, offset, 0);
+ if(r < 0)
+ error(strerror(errno));
+ uif->offset = offset;
+ }
+
+ n = read(fd, va, n);
+ if(n < 0)
+ error(strerror(errno));
+
+ uif->offset += n;
+ qunlock(&uif->oq);
+ poperror();
+
+ return n;
+}
+
+static long
+fswrite(Chan *c, void *va, long n, vlong offset)
+{
+ int fd, r;
+ Ufsinfo *uif;
+
+ uif = c->aux;
+
+ qlock(&uif->oq);
+ if(waserror()) {
+ qunlock(&uif->oq);
+ nexterror();
+ }
+ fd = uif->fd;
+ if(uif->offset != offset) {
+ r = lseek(fd, offset, 0);
+ if(r < 0)
+ error(strerror(errno));
+ uif->offset = offset;
+ }
+
+ n = write(fd, va, n);
+ if(n < 0)
+ error(strerror(errno));
+
+ uif->offset += n;
+ qunlock(&uif->oq);
+ poperror();
+
+ return n;
+}
+
+static void
+fsremove(Chan *c)
+{
+ int n;
+ char path[MAXPATH];
+
+ fspath(c, 0, path);
+ if(c->qid.type & QTDIR)
+ n = rmdir(path);
+ else
+ n = remove(path);
+ if(n < 0)
+ error(strerror(errno));
+}
+
+int
+fswstat(Chan *c, uchar *buf, int n)
+{
+ Dir d;
+ struct stat stbuf;
+ char old[MAXPATH], new[MAXPATH];
+ char strs[MAXPATH*3], *p;
+ Ufsinfo *uif;
+
+ if(convM2D(buf, n, &d, strs) != n)
+ error(Ebadstat);
+
+ fspath(c, 0, old);
+ if(stat(old, &stbuf) < 0)
+ error(strerror(errno));
+
+ uif = c->aux;
+
+ if(d.name[0] && strcmp(d.name, lastelem(c)) != 0) {
+ fspath(c, 0, old);
+ strcpy(new, old);
+ p = strrchr(new, '/');
+ strcpy(p+1, d.name);
+ if(rename(old, new) < 0)
+ error(strerror(errno));
+ }
+
+ fspath(c, 0, old);
+ if(~d.mode != 0 && (int)(d.mode&0777) != (int)(stbuf.st_mode&0777)) {
+ if(chmod(old, d.mode&0777) < 0)
+ error(strerror(errno));
+ uif->mode &= ~0777;
+ uif->mode |= d.mode&0777;
+ }
+/*
+ p = name2pass(gid, d.gid);
+ if(p == 0)
+ error(Eunknown);
+
+ if(p->id != stbuf.st_gid) {
+ if(chown(old, stbuf.st_uid, p->id) < 0)
+ error(strerror(errno));
+
+ uif->gid = p->id;
+ }
+*/
+ return n;
+}
+
+static Qid
+fsqid(char *p, struct stat *st)
+{
+ Qid q;
+ int dev;
+ ulong h;
+ static int nqdev;
+ static uchar *qdev;
+
+ if(qdev == 0)
+ qdev = mallocz(65536U, 1);
+
+ q.type = 0;
+ if((st->st_mode&S_IFMT) == S_IFDIR)
+ q.type = QTDIR;
+
+ dev = st->st_dev & 0xFFFFUL;
+ if(qdev[dev] == 0)
+ qdev[dev] = ++nqdev;
+
+ h = 0;
+ while(*p != '\0')
+ h += *p++ * 13;
+
+ q.path = (vlong)qdev[dev]<<32;
+ q.path |= h;
+ q.vers = st->st_mtime;
+
+ return q;
+}
+
+static void
+fspath(Chan *c, char *ext, char *path)
+{
+ strcpy(path, base);
+ strcat(path, "/");
+ strcat(path, uc2name(c));
+ if(ext){
+ strcat(path, "/");
+ strcat(path, ext);
+ }
+ cleanname(path);
+}
+
+static int
+isdots(char *name)
+{
+ if(name[0] != '.')
+ return 0;
+ if(name[1] == '\0')
+ return 1;
+ if(name[1] != '.')
+ return 0;
+ if(name[2] == '\0')
+ return 1;
+ return 0;
+}
+
+static int
+p9readdir(char *name, Ufsinfo *uif)
+{
+ struct dirent *de;
+
+ if(uif->nextname[0]){
+ strcpy(name, uif->nextname);
+ uif->nextname[0] = 0;
+ return 1;
+ }
+
+ de = readdir(uif->dir);
+ if(de == NULL)
+ return 0;
+
+ strcpy(name, de->d_name);
+ return 1;
+}
+
+static ulong
+fsdirread(Chan *c, uchar *va, int count, ulong offset)
+{
+ int i;
+ Dir d;
+ long n;
+ char de[NAME_MAX];
+ struct stat stbuf;
+ char path[MAXPATH], dirpath[MAXPATH];
+ Ufsinfo *uif;
+
+/*print("fsdirread %s\n", c2name(c));*/
+ i = 0;
+ uif = c->aux;
+
+ errno = 0;
+ if(uif->offset != offset) {
+ if(offset != 0)
+ error("bad offset in fsdirread");
+ uif->offset = offset; /* sync offset */
+ uif->nextname[0] = 0;
+ rewinddir(uif->dir);
+ }
+
+ fspath(c, 0, dirpath);
+
+ while(i+BIT16SZ < count) {
+ if(!p9readdir(de, uif))
+ break;
+
+ if(de[0]==0 || isdots(de))
+ continue;
+
+ d.name = de;
+ sprint(path, "%s/%s", dirpath, de);
+ memset(&stbuf, 0, sizeof stbuf);
+
+ if(stat(path, &stbuf) < 0) {
+ /* fprint(2, "dir: bad path %s\n", path); */
+ /* but continue... probably a bad symlink */
+ }
+
+ d.uid = "unknown";
+ d.gid = "unknown";
+ d.muid = "unknown";
+ d.qid = fsqid(path, &stbuf);
+ d.mode = (d.qid.type<<24)|(stbuf.st_mode&0777);
+ d.atime = stbuf.st_atime;
+ d.mtime = stbuf.st_mtime;
+ d.length = stbuf.st_size;
+ d.type = 'U';
+ d.dev = c->dev;
+ n = convD2M(&d, (uchar*)va+i, count-i);
+ if(n == BIT16SZ){
+ strcpy(uif->nextname, de);
+ break;
+ }
+ i += n;
+ }
+/*print("got %d\n", i);*/
+ uif->offset += i;
+ return i;
+}
+
+static int
+fsomode(int m)
+{
+ switch(m) {
+ case 0: /* OREAD */
+ case 3: /* OEXEC */
+ return 0;
+ case 1: /* OWRITE */
+ return 1;
+ case 2: /* ORDWR */
+ return 2;
+ }
+ error(Ebadarg);
+ return 0;
+}
+
+Dev fsdevtab = {
+ 'U',
+ "fs",
+
+ devreset,
+ devinit,
+ devshutdown,
+ fsattach,
+ fswalk,
+ fsstat,
+ fsopen,
+ fscreate,
+ fsclose,
+ fsread,
+ devbread,
+ fswrite,
+ devbwrite,
+ fsremove,
+ fswstat,
+};
diff --git a/sys/src/cmd/unix/drawterm/kern/devfs-win32.c b/sys/src/cmd/unix/drawterm/kern/devfs-win32.c
new file mode 100755
index 000000000..9997bd027
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devfs-win32.c
@@ -0,0 +1,707 @@
+/*
+ * Disable Unicode until the calls to FindFirstFile etc
+ * are changed to use wide character strings.
+ */
+#undef UNICODE
+#include <windows.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#ifndef NAME_MAX
+# define NAME_MAX 256
+#endif
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+typedef struct DIR DIR;
+typedef struct Ufsinfo Ufsinfo;
+
+enum
+{
+ NUID = 256,
+ NGID = 256,
+ MAXPATH = 1024,
+ MAXCOMP = 128
+};
+
+struct DIR
+{
+ HANDLE handle;
+ char* path;
+ int index;
+ WIN32_FIND_DATA wfd;
+};
+
+struct Ufsinfo
+{
+ int mode;
+ int fd;
+ int uid;
+ int gid;
+ DIR* dir;
+ ulong offset;
+ QLock oq;
+ char nextname[NAME_MAX];
+};
+
+DIR* opendir(char*);
+int readdir(char*, DIR*);
+void closedir(DIR*);
+void rewinddir(DIR*);
+
+char *base = "c:/.";
+
+static Qid fsqid(char*, struct stat *);
+static void fspath(Chan*, char*, char*);
+// static void fsperm(Chan*, int);
+static ulong fsdirread(Chan*, uchar*, int, ulong);
+static int fsomode(int);
+static int chown(char *path, int uid, int);
+
+/* clumsy hack, but not worse than the Path stuff in the last one */
+static char*
+uc2name(Chan *c)
+{
+ char *s;
+
+ if(c->name == nil)
+ return "/";
+ s = c2name(c);
+ if(s[0]=='#' && s[1]=='U')
+ return s+2;
+ return s;
+}
+
+static char*
+lastelem(Chan *c)
+{
+ char *s, *t;
+
+ s = uc2name(c);
+ if((t = strrchr(s, '/')) == nil)
+ return s;
+ if(t[1] == 0)
+ return t;
+ return t+1;
+}
+
+static Chan*
+fsattach(char *spec)
+{
+ Chan *c;
+ struct stat stbuf;
+ static int devno;
+ Ufsinfo *uif;
+
+ if(stat(base, &stbuf) < 0)
+ error(strerror(errno));
+
+ c = devattach('U', spec);
+
+ uif = mallocz(sizeof(Ufsinfo), 1);
+ uif->gid = stbuf.st_gid;
+ uif->uid = stbuf.st_uid;
+ uif->mode = stbuf.st_mode;
+
+ c->aux = uif;
+ c->dev = devno++;
+ c->qid.type = QTDIR;
+/*print("fsattach %s\n", c2name(c));*/
+
+ return c;
+}
+
+static Chan*
+fsclone(Chan *c, Chan *nc)
+{
+ Ufsinfo *uif;
+
+ uif = mallocz(sizeof(Ufsinfo), 1);
+ *uif = *(Ufsinfo*)c->aux;
+ nc->aux = uif;
+
+ return nc;
+}
+
+static int
+fswalk1(Chan *c, char *name)
+{
+ struct stat stbuf;
+ char path[MAXPATH];
+ Ufsinfo *uif;
+
+ fspath(c, name, path);
+
+ /* print("** fs walk '%s' -> %s\n", path, name); */
+
+ if(stat(path, &stbuf) < 0)
+ return 0;
+
+ uif = c->aux;
+
+ uif->gid = stbuf.st_gid;
+ uif->uid = stbuf.st_uid;
+ uif->mode = stbuf.st_mode;
+
+ c->qid = fsqid(path, &stbuf);
+
+ return 1;
+}
+
+extern Cname* addelem(Cname*, char*);
+
+static Walkqid*
+fswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ int i;
+ Cname *cname;
+ Walkqid *wq;
+
+ if(nc != nil)
+ panic("fswalk: nc != nil");
+ wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+ nc = devclone(c);
+ cname = c->name;
+ incref(&cname->ref);
+
+ fsclone(c, nc);
+ wq->clone = nc;
+ for(i=0; i<nname; i++){
+ nc->name = cname;
+ if(fswalk1(nc, name[i]) == 0)
+ break;
+ cname = addelem(cname, name[i]);
+ wq->qid[i] = nc->qid;
+ }
+ nc->name = cname;
+ if(i != nname){
+ cclose(nc);
+ wq->clone = nil;
+ }
+ wq->nqid = i;
+ return wq;
+}
+
+static int
+fsstat(Chan *c, uchar *buf, int n)
+{
+ Dir d;
+ struct stat stbuf;
+ char path[MAXPATH];
+
+ if(n < BIT16SZ)
+ error(Eshortstat);
+
+ fspath(c, 0, path);
+ if(stat(path, &stbuf) < 0)
+ error(strerror(errno));
+
+ d.name = lastelem(c);
+ d.uid = "unknown";
+ d.gid = "unknown";
+ d.muid = "unknown";
+ d.qid = c->qid;
+ d.mode = (c->qid.type<<24)|(stbuf.st_mode&0777);
+ d.atime = stbuf.st_atime;
+ d.mtime = stbuf.st_mtime;
+ d.length = stbuf.st_size;
+ d.type = 'U';
+ d.dev = c->dev;
+ return convD2M(&d, buf, n);
+}
+
+static Chan*
+fsopen(Chan *c, int mode)
+{
+ char path[MAXPATH];
+ int m, isdir;
+ Ufsinfo *uif;
+
+/*print("fsopen %s\n", c2name(c));*/
+ m = mode & (OTRUNC|3);
+ switch(m) {
+ case 0:
+ break;
+ case 1:
+ case 1|16:
+ break;
+ case 2:
+ case 0|16:
+ case 2|16:
+ break;
+ case 3:
+ break;
+ default:
+ error(Ebadarg);
+ }
+
+ isdir = c->qid.type & QTDIR;
+
+ if(isdir && mode != OREAD)
+ error(Eperm);
+
+ m = fsomode(m & 3);
+ c->mode = openmode(mode);
+
+ uif = c->aux;
+
+ fspath(c, 0, path);
+ if(isdir) {
+ uif->dir = opendir(path);
+ if(uif->dir == 0)
+ error(strerror(errno));
+ }
+ else {
+ if(mode & OTRUNC)
+ m |= O_TRUNC;
+ uif->fd = open(path, m|_O_BINARY, 0666);
+
+ if(uif->fd < 0)
+ error(strerror(errno));
+ }
+ uif->offset = 0;
+
+ c->offset = 0;
+ c->flag |= COPEN;
+ return c;
+}
+
+static void
+fscreate(Chan *c, char *name, int mode, ulong perm)
+{
+ int fd, m;
+ char path[MAXPATH];
+ struct stat stbuf;
+ Ufsinfo *uif;
+
+ m = fsomode(mode&3);
+
+ fspath(c, name, path);
+
+ uif = c->aux;
+
+ if(perm & DMDIR) {
+ if(m)
+ error(Eperm);
+
+ if(mkdir(path) < 0)
+ error(strerror(errno));
+
+ fd = open(path, 0);
+ if(fd >= 0) {
+ chmod(path, perm & 0777);
+ chown(path, uif->uid, uif->uid);
+ }
+ close(fd);
+
+ uif->dir = opendir(path);
+ if(uif->dir == 0)
+ error(strerror(errno));
+ }
+ else {
+ fd = open(path, _O_WRONLY|_O_BINARY|_O_CREAT|_O_TRUNC, 0666);
+ if(fd >= 0) {
+ if(m != 1) {
+ close(fd);
+ fd = open(path, m|_O_BINARY);
+ }
+ chmod(path, perm & 0777);
+ chown(path, uif->uid, uif->gid);
+ }
+ if(fd < 0)
+ error(strerror(errno));
+ uif->fd = fd;
+ }
+
+ if(stat(path, &stbuf) < 0)
+ error(strerror(errno));
+ c->qid = fsqid(path, &stbuf);
+ c->offset = 0;
+ c->flag |= COPEN;
+ c->mode = openmode(mode);
+}
+
+static void
+fsclose(Chan *c)
+{
+ Ufsinfo *uif;
+
+ uif = c->aux;
+
+ if(c->flag & COPEN) {
+ if(c->qid.type & QTDIR)
+ closedir(uif->dir);
+ else
+ close(uif->fd);
+ }
+
+ free(uif);
+}
+
+static long
+fsread(Chan *c, void *va, long n, vlong offset)
+{
+ int fd, r;
+ Ufsinfo *uif;
+
+/*print("fsread %s\n", c2name(c));*/
+ if(c->qid.type & QTDIR)
+ return fsdirread(c, va, n, offset);
+
+ uif = c->aux;
+ qlock(&uif->oq);
+ if(waserror()) {
+ qunlock(&uif->oq);
+ nexterror();
+ }
+ fd = uif->fd;
+ if(uif->offset != offset) {
+ r = lseek(fd, offset, 0);
+ if(r < 0)
+ error(strerror(errno));
+ uif->offset = offset;
+ }
+
+ n = read(fd, va, n);
+ if(n < 0)
+ error(strerror(errno));
+
+ uif->offset += n;
+ qunlock(&uif->oq);
+ poperror();
+
+ return n;
+}
+
+static long
+fswrite(Chan *c, void *va, long n, vlong offset)
+{
+ int fd, r;
+ Ufsinfo *uif;
+
+ uif = c->aux;
+
+ qlock(&uif->oq);
+ if(waserror()) {
+ qunlock(&uif->oq);
+ nexterror();
+ }
+ fd = uif->fd;
+ if(uif->offset != offset) {
+ r = lseek(fd, offset, 0);
+ if(r < 0)
+ error(strerror(errno));
+ uif->offset = offset;
+ }
+
+ n = write(fd, va, n);
+ if(n < 0)
+ error(strerror(errno));
+
+ uif->offset += n;
+ qunlock(&uif->oq);
+ poperror();
+
+ return n;
+}
+
+static void
+fsremove(Chan *c)
+{
+ int n;
+ char path[MAXPATH];
+
+ fspath(c, 0, path);
+ if(c->qid.type & QTDIR)
+ n = rmdir(path);
+ else
+ n = remove(path);
+ if(n < 0)
+ error(strerror(errno));
+}
+
+static int
+fswstat(Chan *c, uchar *buf, int n)
+{
+ Dir d;
+ struct stat stbuf;
+ char old[MAXPATH], new[MAXPATH];
+ char strs[MAXPATH*3], *p;
+ Ufsinfo *uif;
+
+ if (convM2D(buf, n, &d, strs) != n)
+ error(Ebadstat);
+
+ fspath(c, 0, old);
+ if(stat(old, &stbuf) < 0)
+ error(strerror(errno));
+
+ uif = c->aux;
+
+// if(uif->uid != stbuf.st_uid)
+// error(Eowner);
+
+ if(d.name[0] && strcmp(d.name, lastelem(c)) != 0) {
+ fspath(c, 0, old);
+ strcpy(new, old);
+ p = strrchr(new, '/');
+ strcpy(p+1, d.name);
+ if(rename(old, new) < 0)
+ error(strerror(errno));
+ }
+
+ fspath(c, 0, old);
+ if(~d.mode != 0 && (int)(d.mode&0777) != (int)(stbuf.st_mode&0777)) {
+ if(chmod(old, d.mode&0777) < 0)
+ error(strerror(errno));
+ uif->mode &= ~0777;
+ uif->mode |= d.mode&0777;
+ }
+/*
+ p = name2pass(gid, d.gid);
+ if(p == 0)
+ error(Eunknown);
+
+ if(p->id != stbuf.st_gid) {
+ if(chown(old, stbuf.st_uid, p->id) < 0)
+ error(sys_errlist[errno]);
+
+ uif->gid = p->id;
+ }
+*/
+ return n;
+}
+
+static Qid
+fsqid(char *p, struct stat *st)
+{
+ Qid q;
+ int dev;
+ ulong h;
+ static int nqdev;
+ static uchar *qdev;
+
+ if(qdev == 0)
+ qdev = mallocz(65536U, 1);
+
+ q.type = 0;
+ if((st->st_mode&S_IFMT) == S_IFDIR)
+ q.type = QTDIR;
+
+ dev = st->st_dev & 0xFFFFUL;
+ if(qdev[dev] == 0)
+ qdev[dev] = ++nqdev;
+
+ h = 0;
+ while(*p != '\0')
+ h += *p++ * 13;
+
+ q.path = (vlong)qdev[dev]<<32;
+ q.path |= h;
+ q.vers = st->st_mtime;
+
+ return q;
+}
+
+static void
+fspath(Chan *c, char *ext, char *path)
+{
+ strcpy(path, base);
+ strcat(path, "/");
+ strcat(path, uc2name(c));
+ if(ext) {
+ strcat(path, "/");
+ strcat(path, ext);
+ }
+ cleanname(path);
+}
+
+static int
+isdots(char *name)
+{
+ if(name[0] != '.')
+ return 0;
+ if(name[1] == '\0')
+ return 1;
+ if(name[1] != '.')
+ return 0;
+ if(name[2] == '\0')
+ return 1;
+ return 0;
+}
+
+static int
+p9readdir(char *name, Ufsinfo *uif)
+{
+ if(uif->nextname[0]){
+ strcpy(name, uif->nextname);
+ uif->nextname[0] = 0;
+ return 1;
+ }
+
+ return readdir(name, uif->dir);
+}
+
+static ulong
+fsdirread(Chan *c, uchar *va, int count, ulong offset)
+{
+ int i;
+ Dir d;
+ long n;
+ char de[NAME_MAX];
+ struct stat stbuf;
+ char path[MAXPATH], dirpath[MAXPATH];
+ Ufsinfo *uif;
+
+/*print("fsdirread %s\n", c2name(c));*/
+ i = 0;
+ uif = c->aux;
+
+ errno = 0;
+ if(uif->offset != offset) {
+ if(offset != 0)
+ error("bad offset in fsdirread");
+ uif->offset = offset; /* sync offset */
+ uif->nextname[0] = 0;
+ rewinddir(uif->dir);
+ }
+
+ fspath(c, 0, dirpath);
+
+ while(i+BIT16SZ < count) {
+ if(!p9readdir(de, uif))
+ break;
+
+ if(de[0]==0 || isdots(de))
+ continue;
+
+ d.name = de;
+ sprint(path, "%s/%s", dirpath, de);
+ memset(&stbuf, 0, sizeof stbuf);
+
+ if(stat(path, &stbuf) < 0) {
+ print("dir: bad path %s\n", path);
+ /* but continue... probably a bad symlink */
+ }
+
+ d.uid = "unknown";
+ d.gid = "unknown";
+ d.muid = "unknown";
+ d.qid = fsqid(path, &stbuf);
+ d.mode = (d.qid.type<<24)|(stbuf.st_mode&0777);
+ d.atime = stbuf.st_atime;
+ d.mtime = stbuf.st_mtime;
+ d.length = stbuf.st_size;
+ d.type = 'U';
+ d.dev = c->dev;
+ n = convD2M(&d, (uchar*)va+i, count-i);
+ if(n == BIT16SZ){
+ strcpy(uif->nextname, de);
+ break;
+ }
+ i += n;
+ }
+/*print("got %d\n", i);*/
+ uif->offset += i;
+ return i;
+}
+
+static int
+fsomode(int m)
+{
+ switch(m) {
+ case 0: /* OREAD */
+ case 3: /* OEXEC */
+ return 0;
+ case 1: /* OWRITE */
+ return 1;
+ case 2: /* ORDWR */
+ return 2;
+ }
+ error(Ebadarg);
+ return 0;
+}
+void
+closedir(DIR *d)
+{
+ FindClose(d->handle);
+ free(d->path);
+}
+
+int
+readdir(char *name, DIR *d)
+{
+ if(d->index != 0) {
+ if(FindNextFile(d->handle, &d->wfd) == FALSE)
+ return 0;
+ }
+ strcpy(name, (char*)d->wfd.cFileName);
+ d->index++;
+
+ return 1;
+}
+
+void
+rewinddir(DIR *d)
+{
+ FindClose(d->handle);
+ d->handle = FindFirstFile(d->path, &d->wfd);
+ d->index = 0;
+}
+
+static int
+chown(char *path, int uid, int perm)
+{
+/* panic("chown"); */
+ return 0;
+}
+
+DIR*
+opendir(char *p)
+{
+ DIR *d;
+ char path[MAX_PATH];
+
+
+ snprint(path, sizeof(path), "%s/*.*", p);
+
+ d = mallocz(sizeof(DIR), 1);
+ if(d == 0)
+ return 0;
+
+ d->index = 0;
+
+ d->handle = FindFirstFile(path, &d->wfd);
+ if(d->handle == INVALID_HANDLE_VALUE) {
+ free(d);
+ return 0;
+ }
+
+ d->path = strdup(path);
+ return d;
+}
+
+Dev fsdevtab = {
+ 'U',
+ "fs",
+
+ devreset,
+ devinit,
+ devshutdown,
+ fsattach,
+ fswalk,
+ fsstat,
+ fsopen,
+ fscreate,
+ fsclose,
+ fsread,
+ devbread,
+ fswrite,
+ devbwrite,
+ fsremove,
+ fswstat,
+};
diff --git a/sys/src/cmd/unix/drawterm/kern/devip-posix.c b/sys/src/cmd/unix/drawterm/kern/devip-posix.c
new file mode 100755
index 000000000..7f6171e4e
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devip-posix.c
@@ -0,0 +1,210 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "devip.h"
+
+#undef listen
+#undef accept
+#undef bind
+
+void
+osipinit(void)
+{
+ char buf[1024];
+ gethostname(buf, sizeof(buf));
+ kstrdup(&sysname, buf);
+
+}
+
+int
+so_socket(int type)
+{
+ int fd, one;
+
+ switch(type) {
+ default:
+ error("bad protocol type");
+ case S_TCP:
+ type = SOCK_STREAM;
+ break;
+ case S_UDP:
+ type = SOCK_DGRAM;
+ break;
+ }
+
+ fd = socket(AF_INET, type, 0);
+ if(fd < 0)
+ oserror();
+
+ one = 1;
+ if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one)) > 0){
+ oserrstr();
+ print("setsockopt: %r");
+ }
+
+ return fd;
+}
+
+
+void
+so_connect(int fd, unsigned long raddr, unsigned short rport)
+{
+ struct sockaddr_in sin;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ hnputs(&sin.sin_port, rport);
+ hnputl(&sin.sin_addr.s_addr, raddr);
+
+ if(connect(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
+ oserror();
+}
+
+void
+so_getsockname(int fd, unsigned long *laddr, unsigned short *lport)
+{
+ socklen_t len;
+ struct sockaddr_in sin;
+
+ len = sizeof(sin);
+ if(getsockname(fd, (struct sockaddr*)&sin, &len) < 0)
+ oserror();
+
+ if(sin.sin_family != AF_INET || len != sizeof(sin))
+ error("not AF_INET");
+
+ *laddr = nhgetl(&sin.sin_addr.s_addr);
+ *lport = nhgets(&sin.sin_port);
+}
+
+void
+so_listen(int fd)
+{
+ if(listen(fd, 5) < 0)
+ oserror();
+}
+
+int
+so_accept(int fd, unsigned long *raddr, unsigned short *rport)
+{
+ int nfd;
+ socklen_t len;
+ struct sockaddr_in sin;
+
+ len = sizeof(sin);
+ nfd = accept(fd, (struct sockaddr*)&sin, &len);
+ if(nfd < 0)
+ oserror();
+
+ if(sin.sin_family != AF_INET || len != sizeof(sin))
+ error("not AF_INET");
+
+ *raddr = nhgetl(&sin.sin_addr.s_addr);
+ *rport = nhgets(&sin.sin_port);
+ return nfd;
+}
+
+void
+so_bind(int fd, int su, unsigned short port)
+{
+ int i, one;
+ struct sockaddr_in sin;
+
+ one = 1;
+ if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) < 0){
+ oserrstr();
+ print("setsockopt: %r");
+ }
+
+ if(su) {
+ for(i = 600; i < 1024; i++) {
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = i;
+
+ if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) >= 0)
+ return;
+ }
+ oserror();
+ }
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ hnputs(&sin.sin_port, port);
+
+ if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
+ oserror();
+}
+
+int
+so_gethostbyname(char *host, char**hostv, int n)
+{
+ int i;
+ char buf[32];
+ unsigned char *p;
+ struct hostent *hp;
+
+ hp = gethostbyname(host);
+ if(hp == 0)
+ return 0;
+
+ for(i = 0; hp->h_addr_list[i] && i < n; i++) {
+ p = (unsigned char*)hp->h_addr_list[i];
+ sprint(buf, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
+ hostv[i] = strdup(buf);
+ if(hostv[i] == 0)
+ break;
+ }
+ return i;
+}
+
+char*
+hostlookup(char *host)
+{
+ char buf[100];
+ uchar *p;
+ struct hostent *he;
+
+ he = gethostbyname(host);
+ if(he != 0 && he->h_addr_list[0]) {
+ p = (uchar*)he->h_addr_list[0];
+ sprint(buf, "%ud.%ud.%ud.%ud", p[0], p[1], p[2], p[3]);
+ } else
+ strcpy(buf, host);
+
+ return strdup(buf);
+}
+
+int
+so_getservbyname(char *service, char *net, char *port)
+{
+ struct servent *s;
+
+ s = getservbyname(service, net);
+ if(s == 0)
+ return -1;
+
+ sprint(port, "%d", nhgets(&s->s_port));
+ return 0;
+}
+
+int
+so_send(int fd, void *d, int n, int f)
+{
+ return send(fd, d, n, f);
+}
+
+int
+so_recv(int fd, void *d, int n, int f)
+{
+ return recv(fd, d, n, f);
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/devip-win32.c b/sys/src/cmd/unix/drawterm/kern/devip-win32.c
new file mode 100755
index 000000000..3caa67ad1
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devip-win32.c
@@ -0,0 +1,212 @@
+#include <windows.h>
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "devip.h"
+
+#ifdef MSVC
+#pragma comment(lib, "wsock32.lib")
+#endif
+
+#undef listen
+#undef accept
+#undef bind
+
+void
+osipinit(void)
+{
+ WSADATA wasdat;
+ char buf[1024];
+
+ if(WSAStartup(MAKEWORD(1, 1), &wasdat) != 0)
+ panic("no winsock.dll");
+
+ gethostname(buf, sizeof(buf));
+ kstrdup(&sysname, buf);
+}
+
+int
+so_socket(int type)
+{
+ int fd, one;
+
+ switch(type) {
+ default:
+ error("bad protocol type");
+ case S_TCP:
+ type = SOCK_STREAM;
+ break;
+ case S_UDP:
+ type = SOCK_DGRAM;
+ break;
+ }
+
+ fd = socket(AF_INET, type, 0);
+ if(fd < 0)
+ oserror();
+
+ one = 1;
+ if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one)) > 0){
+ oserrstr();
+ print("setsockopt: %s\n", up->errstr);
+ }
+
+ return fd;
+}
+
+
+void
+so_connect(int fd, unsigned long raddr, unsigned short rport)
+{
+ struct sockaddr_in sin;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ hnputs(&sin.sin_port, rport);
+ hnputl(&sin.sin_addr.s_addr, raddr);
+
+ if(connect(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
+ oserror();
+}
+
+void
+so_getsockname(int fd, unsigned long *laddr, unsigned short *lport)
+{
+ int len;
+ struct sockaddr_in sin;
+
+ len = sizeof(sin);
+ if(getsockname(fd, (struct sockaddr*)&sin, &len) < 0)
+ oserror();
+
+ if(sin.sin_family != AF_INET || len != sizeof(sin))
+ error("not AF_INET");
+
+ *laddr = nhgetl(&sin.sin_addr.s_addr);
+ *lport = nhgets(&sin.sin_port);
+}
+
+void
+so_listen(int fd)
+{
+ if(listen(fd, 5) < 0)
+ oserror();
+}
+
+int
+so_accept(int fd, unsigned long *raddr, unsigned short *rport)
+{
+ int nfd, len;
+ struct sockaddr_in sin;
+
+ len = sizeof(sin);
+ nfd = accept(fd, (struct sockaddr*)&sin, &len);
+ if(nfd < 0)
+ oserror();
+
+ if(sin.sin_family != AF_INET || len != sizeof(sin))
+ error("not AF_INET");
+
+ *raddr = nhgetl(&sin.sin_addr.s_addr);
+ *rport = nhgets(&sin.sin_port);
+ return nfd;
+}
+
+void
+so_bind(int fd, int su, unsigned short port)
+{
+ int i, one;
+ struct sockaddr_in sin;
+
+ one = 1;
+ if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) < 0){
+ oserrstr();
+ print("setsockopt: %s", up->errstr);
+ }
+
+ if(su) {
+ for(i = 600; i < 1024; i++) {
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = i;
+
+ if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) >= 0)
+ return;
+ }
+ oserror();
+ }
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ hnputs(&sin.sin_port, port);
+
+ if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
+ oserror();
+}
+
+int
+so_gethostbyname(char *host, char**hostv, int n)
+{
+ int i;
+ char buf[32];
+ unsigned char *p;
+ struct hostent *hp;
+
+ hp = gethostbyname(host);
+ if(hp == 0)
+ return 0;
+
+ for(i = 0; hp->h_addr_list[i] && i < n; i++) {
+ p = (unsigned char*)hp->h_addr_list[i];
+ sprint(buf, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
+ hostv[i] = strdup(buf);
+ if(hostv[i] == 0)
+ break;
+ }
+ return i;
+}
+
+char*
+hostlookup(char *host)
+{
+ char buf[100];
+ uchar *p;
+ HOSTENT *he;
+
+ he = gethostbyname(host);
+ if(he != 0 && he->h_addr_list[0]) {
+ p = (uchar*)he->h_addr_list[0];
+ sprint(buf, "%ud.%ud.%ud.%ud", p[0], p[1], p[2], p[3]);
+ } else
+ strcpy(buf, host);
+
+ return strdup(buf);
+}
+
+int
+so_getservbyname(char *service, char *net, char *port)
+{
+ struct servent *s;
+
+ s = getservbyname(service, net);
+ if(s == 0)
+ return -1;
+
+ sprint(port, "%d", nhgets(&s->s_port));
+ return 0;
+}
+
+int
+so_send(int fd, void *d, int n, int f)
+{
+ return send(fd, d, n, f);
+}
+
+int
+so_recv(int fd, void *d, int n, int f)
+{
+ return recv(fd, d, n, f);
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/devip.c b/sys/src/cmd/unix/drawterm/kern/devip.c
new file mode 100755
index 000000000..f192aebcb
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devip.c
@@ -0,0 +1,938 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "devip.h"
+
+void hnputl(void *p, unsigned long v);
+void hnputs(void *p, unsigned short v);
+unsigned long nhgetl(void *p);
+unsigned short nhgets(void *p);
+unsigned long parseip(char *to, char *from);
+void csclose(Chan*);
+long csread(Chan*, void*, long, vlong);
+long cswrite(Chan*, void*, long, vlong);
+
+void osipinit(void);
+
+enum
+{
+ Qtopdir = 1, /* top level directory */
+ Qcs,
+ Qprotodir, /* directory for a protocol */
+ Qclonus,
+ Qconvdir, /* directory for a conversation */
+ Qdata,
+ Qctl,
+ Qstatus,
+ Qremote,
+ Qlocal,
+ Qlisten,
+
+ MAXPROTO = 4
+};
+#define TYPE(x) ((int)((x).path & 0xf))
+#define CONV(x) ((int)(((x).path >> 4)&0xfff))
+#define PROTO(x) ((int)(((x).path >> 16)&0xff))
+#define QID(p, c, y) (((p)<<16) | ((c)<<4) | (y))
+
+typedef struct Proto Proto;
+typedef struct Conv Conv;
+struct Conv
+{
+ int x;
+ Ref r;
+ int sfd;
+ int perm;
+ char owner[KNAMELEN];
+ char* state;
+ ulong laddr;
+ ushort lport;
+ ulong raddr;
+ ushort rport;
+ int restricted;
+ char cerr[KNAMELEN];
+ Proto* p;
+};
+
+struct Proto
+{
+ Lock l;
+ int x;
+ int stype;
+ char name[KNAMELEN];
+ int nc;
+ int maxconv;
+ Conv** conv;
+ Qid qid;
+};
+
+static int np;
+static Proto proto[MAXPROTO];
+int eipfmt(Fmt*);
+
+static Conv* protoclone(Proto*, char*, int);
+static void setladdr(Conv*);
+
+int
+ipgen(Chan *c, char *nname, Dirtab *d, int nd, int s, Dir *dp)
+{
+ Qid q;
+ Conv *cv;
+ char *p;
+
+ USED(nname);
+ q.vers = 0;
+ q.type = 0;
+ switch(TYPE(c->qid)) {
+ case Qtopdir:
+ if(s >= 1+np)
+ return -1;
+
+ if(s == 0){
+ q.path = QID(s, 0, Qcs);
+ devdir(c, q, "cs", 0, "network", 0666, dp);
+ }else{
+ s--;
+ q.path = QID(s, 0, Qprotodir);
+ q.type = QTDIR;
+ devdir(c, q, proto[s].name, 0, "network", DMDIR|0555, dp);
+ }
+ return 1;
+ case Qprotodir:
+ if(s < proto[PROTO(c->qid)].nc) {
+ cv = proto[PROTO(c->qid)].conv[s];
+ sprint(up->genbuf, "%d", s);
+ q.path = QID(PROTO(c->qid), s, Qconvdir);
+ q.type = QTDIR;
+ devdir(c, q, up->genbuf, 0, cv->owner, DMDIR|0555, dp);
+ return 1;
+ }
+ s -= proto[PROTO(c->qid)].nc;
+ switch(s) {
+ default:
+ return -1;
+ case 0:
+ p = "clone";
+ q.path = QID(PROTO(c->qid), 0, Qclonus);
+ break;
+ }
+ devdir(c, q, p, 0, "network", 0555, dp);
+ return 1;
+ case Qconvdir:
+ cv = proto[PROTO(c->qid)].conv[CONV(c->qid)];
+ switch(s) {
+ default:
+ return -1;
+ case 0:
+ q.path = QID(PROTO(c->qid), CONV(c->qid), Qdata);
+ devdir(c, q, "data", 0, cv->owner, cv->perm, dp);
+ return 1;
+ case 1:
+ q.path = QID(PROTO(c->qid), CONV(c->qid), Qctl);
+ devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp);
+ return 1;
+ case 2:
+ p = "status";
+ q.path = QID(PROTO(c->qid), CONV(c->qid), Qstatus);
+ break;
+ case 3:
+ p = "remote";
+ q.path = QID(PROTO(c->qid), CONV(c->qid), Qremote);
+ break;
+ case 4:
+ p = "local";
+ q.path = QID(PROTO(c->qid), CONV(c->qid), Qlocal);
+ break;
+ case 5:
+ p = "listen";
+ q.path = QID(PROTO(c->qid), CONV(c->qid), Qlisten);
+ break;
+ }
+ devdir(c, q, p, 0, cv->owner, 0444, dp);
+ return 1;
+ }
+ return -1;
+}
+
+static void
+newproto(char *name, int type, int maxconv)
+{
+ int l;
+ Proto *p;
+
+ if(np >= MAXPROTO) {
+ print("no %s: increase MAXPROTO", name);
+ return;
+ }
+
+ p = &proto[np];
+ strcpy(p->name, name);
+ p->stype = type;
+ p->qid.path = QID(np, 0, Qprotodir);
+ p->qid.type = QTDIR;
+ p->x = np++;
+ p->maxconv = maxconv;
+ l = sizeof(Conv*)*(p->maxconv+1);
+ p->conv = mallocz(l, 1);
+ if(p->conv == 0)
+ panic("no memory");
+}
+
+void
+ipinit(void)
+{
+ osipinit();
+
+ newproto("udp", S_UDP, 10);
+ newproto("tcp", S_TCP, 30);
+
+ fmtinstall('I', eipfmt);
+ fmtinstall('E', eipfmt);
+
+}
+
+Chan *
+ipattach(char *spec)
+{
+ Chan *c;
+
+ c = devattach('I', spec);
+ c->qid.path = QID(0, 0, Qtopdir);
+ c->qid.type = QTDIR;
+ c->qid.vers = 0;
+ return c;
+}
+
+static Walkqid*
+ipwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, 0, 0, ipgen);
+}
+
+int
+ipstat(Chan *c, uchar *dp, int n)
+{
+ return devstat(c, dp, n, 0, 0, ipgen);
+}
+
+Chan *
+ipopen(Chan *c, int omode)
+{
+ Proto *p;
+ ulong raddr;
+ ushort rport;
+ int perm, sfd;
+ Conv *cv, *lcv;
+
+ omode &= 3;
+ perm = 0;
+ 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 Qprotodir:
+ case Qconvdir:
+ case Qstatus:
+ case Qremote:
+ case Qlocal:
+ if(omode != OREAD)
+ error(Eperm);
+ break;
+ case Qclonus:
+ p = &proto[PROTO(c->qid)];
+ cv = protoclone(p, up->user, -1);
+ if(cv == 0)
+ error(Enodev);
+ c->qid.path = QID(p->x, cv->x, Qctl);
+ c->qid.vers = 0;
+ break;
+ case Qdata:
+ case Qctl:
+ p = &proto[PROTO(c->qid)];
+ lock(&p->l);
+ cv = p->conv[CONV(c->qid)];
+ lock(&cv->r.lk);
+ if((perm & (cv->perm>>6)) != perm) {
+ if(strcmp(up->user, cv->owner) != 0 ||
+ (perm & cv->perm) != perm) {
+ unlock(&cv->r.lk);
+ unlock(&p->l);
+ error(Eperm);
+ }
+ }
+ cv->r.ref++;
+ if(cv->r.ref == 1) {
+ memmove(cv->owner, up->user, KNAMELEN);
+ cv->perm = 0660;
+ }
+ unlock(&cv->r.lk);
+ unlock(&p->l);
+ break;
+ case Qlisten:
+ p = &proto[PROTO(c->qid)];
+ lcv = p->conv[CONV(c->qid)];
+ sfd = so_accept(lcv->sfd, &raddr, &rport);
+ cv = protoclone(p, up->user, sfd);
+ if(cv == 0) {
+ close(sfd);
+ error(Enodev);
+ }
+ cv->raddr = raddr;
+ cv->rport = rport;
+ setladdr(cv);
+ cv->state = "Established";
+ c->qid.path = QID(p->x, cv->x, Qctl);
+ break;
+ }
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+}
+
+void
+ipclose(Chan *c)
+{
+ Conv *cc;
+
+ switch(TYPE(c->qid)) {
+ case Qcs:
+ csclose(c);
+ break;
+ case Qdata:
+ case Qctl:
+ if((c->flag & COPEN) == 0)
+ break;
+ cc = proto[PROTO(c->qid)].conv[CONV(c->qid)];
+ if(decref(&cc->r) != 0)
+ break;
+ strcpy(cc->owner, "network");
+ cc->perm = 0666;
+ cc->state = "Closed";
+ cc->laddr = 0;
+ cc->raddr = 0;
+ cc->lport = 0;
+ cc->rport = 0;
+ close(cc->sfd);
+ break;
+ }
+}
+
+long
+ipread(Chan *ch, void *a, long n, vlong offset)
+{
+ int r;
+ Conv *c;
+ Proto *x;
+ uchar ip[4];
+ char buf[128], *p;
+
+/*print("ipread %s %lux\n", c2name(ch), (long)ch->qid.path);*/
+ p = a;
+ switch(TYPE(ch->qid)) {
+ default:
+ error(Eperm);
+ case Qcs:
+ return csread(ch, a, n, offset);
+ case Qprotodir:
+ case Qtopdir:
+ case Qconvdir:
+ return devdirread(ch, a, n, 0, 0, ipgen);
+ case Qctl:
+ sprint(buf, "%d", CONV(ch->qid));
+ return readstr(offset, p, n, buf);
+ case Qremote:
+ c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
+ hnputl(ip, c->raddr);
+ sprint(buf, "%I!%d\n", ip, c->rport);
+ return readstr(offset, p, n, buf);
+ case Qlocal:
+ c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
+ hnputl(ip, c->laddr);
+ sprint(buf, "%I!%d\n", ip, c->lport);
+ return readstr(offset, p, n, buf);
+ case Qstatus:
+ x = &proto[PROTO(ch->qid)];
+ c = x->conv[CONV(ch->qid)];
+ sprint(buf, "%s/%d %d %s \n",
+ c->p->name, c->x, c->r.ref, c->state);
+ return readstr(offset, p, n, buf);
+ case Qdata:
+ c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
+ r = so_recv(c->sfd, a, n, 0);
+ if(r < 0){
+ oserrstr();
+ nexterror();
+ }
+ return r;
+ }
+}
+
+static void
+setladdr(Conv *c)
+{
+ so_getsockname(c->sfd, &c->laddr, &c->lport);
+}
+
+static void
+setlport(Conv *c)
+{
+ if(c->restricted == 0 && c->lport == 0)
+ return;
+
+ so_bind(c->sfd, c->restricted, c->lport);
+}
+
+static void
+setladdrport(Conv *c, char *str)
+{
+ char *p, addr[4];
+
+ p = strchr(str, '!');
+ if(p == 0) {
+ p = str;
+ c->laddr = 0;
+ }
+ else {
+ *p++ = 0;
+ parseip(addr, str);
+ c->laddr = nhgetl((uchar*)addr);
+ }
+ if(*p == '*')
+ c->lport = 0;
+ else
+ c->lport = atoi(p);
+
+ setlport(c);
+}
+
+static char*
+setraddrport(Conv *c, char *str)
+{
+ char *p, addr[4];
+
+ p = strchr(str, '!');
+ if(p == 0)
+ return "malformed address";
+ *p++ = 0;
+ parseip(addr, str);
+ c->raddr = nhgetl((uchar*)addr);
+ c->rport = atoi(p);
+ p = strchr(p, '!');
+ if(p) {
+ if(strcmp(p, "!r") == 0)
+ c->restricted = 1;
+ }
+ return 0;
+}
+
+long
+ipwrite(Chan *ch, void *a, long n, vlong offset)
+{
+ Conv *c;
+ Proto *x;
+ int r, nf;
+ char *p, *fields[3], buf[128];
+
+ switch(TYPE(ch->qid)) {
+ default:
+ error(Eperm);
+ case Qcs:
+ return cswrite(ch, a, n, offset);
+ case Qctl:
+ x = &proto[PROTO(ch->qid)];
+ c = x->conv[CONV(ch->qid)];
+ if(n > sizeof(buf)-1)
+ n = sizeof(buf)-1;
+ memmove(buf, a, n);
+ buf[n] = '\0';
+
+ nf = tokenize(buf, fields, 3);
+ if(strcmp(fields[0], "connect") == 0){
+ switch(nf) {
+ default:
+ error("bad args to connect");
+ case 2:
+ p = setraddrport(c, fields[1]);
+ if(p != 0)
+ error(p);
+ break;
+ case 3:
+ p = setraddrport(c, fields[1]);
+ if(p != 0)
+ error(p);
+ c->lport = atoi(fields[2]);
+ setlport(c);
+ break;
+ }
+ so_connect(c->sfd, c->raddr, c->rport);
+ setladdr(c);
+ c->state = "Established";
+ return n;
+ }
+ if(strcmp(fields[0], "announce") == 0) {
+ switch(nf){
+ default:
+ error("bad args to announce");
+ case 2:
+ setladdrport(c, fields[1]);
+ break;
+ }
+ so_listen(c->sfd);
+ c->state = "Announced";
+ return n;
+ }
+ if(strcmp(fields[0], "bind") == 0){
+ switch(nf){
+ default:
+ error("bad args to bind");
+ case 2:
+ c->lport = atoi(fields[1]);
+ break;
+ }
+ setlport(c);
+ return n;
+ }
+ error("bad control message");
+ case Qdata:
+ x = &proto[PROTO(ch->qid)];
+ c = x->conv[CONV(ch->qid)];
+ r = so_send(c->sfd, a, n, 0);
+ if(r < 0){
+ oserrstr();
+ nexterror();
+ }
+ return r;
+ }
+ return n;
+}
+
+static Conv*
+protoclone(Proto *p, char *user, int nfd)
+{
+ Conv *c, **pp, **ep;
+
+ c = 0;
+ lock(&p->l);
+ if(waserror()) {
+ unlock(&p->l);
+ nexterror();
+ }
+ ep = &p->conv[p->maxconv];
+ for(pp = p->conv; pp < ep; pp++) {
+ c = *pp;
+ if(c == 0) {
+ c = mallocz(sizeof(Conv), 1);
+ if(c == 0)
+ error(Enomem);
+ lock(&c->r.lk);
+ c->r.ref = 1;
+ c->p = p;
+ c->x = pp - p->conv;
+ p->nc++;
+ *pp = c;
+ break;
+ }
+ lock(&c->r.lk);
+ if(c->r.ref == 0) {
+ c->r.ref++;
+ break;
+ }
+ unlock(&c->r.lk);
+ }
+ if(pp >= ep) {
+ unlock(&p->l);
+ poperror();
+ return 0;
+ }
+
+ strcpy(c->owner, user);
+ c->perm = 0660;
+ c->state = "Closed";
+ c->restricted = 0;
+ c->laddr = 0;
+ c->raddr = 0;
+ c->lport = 0;
+ c->rport = 0;
+ c->sfd = nfd;
+ if(nfd == -1)
+ c->sfd = so_socket(p->stype);
+
+ unlock(&c->r.lk);
+ unlock(&p->l);
+ poperror();
+ return c;
+}
+
+enum
+{
+ Isprefix= 16,
+};
+
+uchar prefixvals[256] =
+{
+/*0x00*/ 0 | Isprefix,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x10*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x20*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x30*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x40*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x50*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x60*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x70*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x80*/ 1 | Isprefix,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x90*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0xA0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0xB0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0xC0*/ 2 | Isprefix,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0xD0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0xE0*/ 3 | Isprefix,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0xF0*/ 4 | Isprefix,
+ 0, 0, 0, 0, 0, 0, 0,
+/*0xF8*/ 5 | Isprefix,
+ 0, 0, 0,
+/*0xFC*/ 6 | Isprefix,
+ 0,
+/*0xFE*/ 7 | Isprefix,
+/*0xFF*/ 8 | Isprefix,
+};
+
+int
+eipfmt(Fmt *f)
+{
+ char buf[5*8];
+ static char *efmt = "%.2lux%.2lux%.2lux%.2lux%.2lux%.2lux";
+ static char *ifmt = "%d.%d.%d.%d";
+ uchar *p, ip[16];
+ ulong ul;
+
+ switch(f->r) {
+ case 'E': /* Ethernet address */
+ p = va_arg(f->args, uchar*);
+ snprint(buf, sizeof buf, efmt, p[0], p[1], p[2], p[3], p[4], p[5]);
+ return fmtstrcpy(f, buf);
+
+ case 'I':
+ ul = va_arg(f->args, ulong);
+ hnputl(ip, ul);
+ snprint(buf, sizeof buf, ifmt, ip[0], ip[1], ip[2], ip[3]);
+ return fmtstrcpy(f, buf);
+ }
+ return fmtstrcpy(f, "(eipfmt)");
+}
+
+void
+hnputl(void *p, unsigned long v)
+{
+ unsigned char *a;
+
+ a = p;
+ a[0] = v>>24;
+ a[1] = v>>16;
+ a[2] = v>>8;
+ a[3] = v;
+}
+
+void
+hnputs(void *p, unsigned short v)
+{
+ unsigned char *a;
+
+ a = p;
+ a[0] = v>>8;
+ a[1] = v;
+}
+
+unsigned long
+nhgetl(void *p)
+{
+ unsigned char *a;
+ a = p;
+ return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0);
+}
+
+unsigned short
+nhgets(void *p)
+{
+ unsigned char *a;
+ a = p;
+ return (a[0]<<8)|(a[1]<<0);
+}
+
+#define CLASS(p) ((*(unsigned char*)(p))>>6)
+
+unsigned long
+parseip(char *to, char *from)
+{
+ int i;
+ char *p;
+
+ p = from;
+ memset(to, 0, 4);
+ for(i = 0; i < 4 && *p; i++){
+ to[i] = strtoul(p, &p, 10);
+ if(*p != '.' && *p != 0){
+ memset(to, 0, 4);
+ return 0;
+ }
+ if(*p == '.')
+ p++;
+ }
+ switch(CLASS(to)){
+ case 0: /* class A - 1 byte net */
+ case 1:
+ if(i == 3){
+ to[3] = to[2];
+ to[2] = to[1];
+ to[1] = 0;
+ } else if (i == 2){
+ to[3] = to[1];
+ to[1] = 0;
+ }
+ break;
+ case 2: /* class B - 2 byte net */
+ if(i == 3){
+ to[3] = to[2];
+ to[2] = 0;
+ }
+ break;
+ }
+ return nhgetl(to);
+}
+
+void
+csclose(Chan *c)
+{
+ free(c->aux);
+}
+
+long
+csread(Chan *c, void *a, long n, vlong offset)
+{
+ if(c->aux == nil)
+ return 0;
+ return readstr(offset, a, n, c->aux);
+}
+
+static struct
+{
+ char *name;
+ uint num;
+} tab[] = {
+ "cs", 1,
+ "echo", 7,
+ "discard", 9,
+ "systat", 11,
+ "daytime", 13,
+ "netstat", 15,
+ "chargen", 19,
+ "ftp-data", 20,
+ "ftp", 21,
+ "ssh", 22,
+ "telnet", 23,
+ "smtp", 25,
+ "time", 37,
+ "whois", 43,
+ "dns", 53,
+ "domain", 53,
+ "uucp", 64,
+ "gopher", 70,
+ "rje", 77,
+ "finger", 79,
+ "http", 80,
+ "link", 87,
+ "supdup", 95,
+ "hostnames", 101,
+ "iso-tsap", 102,
+ "x400", 103,
+ "x400-snd", 104,
+ "csnet-ns", 105,
+ "pop-2", 109,
+ "pop3", 110,
+ "portmap", 111,
+ "uucp-path", 117,
+ "nntp", 119,
+ "netbios", 139,
+ "imap4", 143,
+ "NeWS", 144,
+ "print-srv", 170,
+ "z39.50", 210,
+ "fsb", 400,
+ "sysmon", 401,
+ "proxy", 402,
+ "proxyd", 404,
+ "https", 443,
+ "cifs", 445,
+ "ssmtp", 465,
+ "rexec", 512,
+ "login", 513,
+ "shell", 514,
+ "printer", 515,
+ "courier", 530,
+ "cscan", 531,
+ "uucp", 540,
+ "snntp", 563,
+ "9fs", 564,
+ "whoami", 565,
+ "guard", 566,
+ "ticket", 567,
+ "dlsftp", 666,
+ "fmclient", 729,
+ "imaps", 993,
+ "pop3s", 995,
+ "ingreslock", 1524,
+ "pptp", 1723,
+ "nfs", 2049,
+ "webster", 2627,
+ "weather", 3000,
+ "secstore", 5356,
+ "Xdisplay", 6000,
+ "styx", 6666,
+ "mpeg", 6667,
+ "rstyx", 6668,
+ "infdb", 6669,
+ "infsigner", 6671,
+ "infcsigner", 6672,
+ "inflogin", 6673,
+ "bandt", 7330,
+ "face", 32000,
+ "dhashgate", 11978,
+ "exportfs", 17007,
+ "rexexec", 17009,
+ "ncpu", 17010,
+ "cpu", 17013,
+ "glenglenda1", 17020,
+ "glenglenda2", 17021,
+ "glenglenda3", 17022,
+ "glenglenda4", 17023,
+ "glenglenda5", 17024,
+ "glenglenda6", 17025,
+ "glenglenda7", 17026,
+ "glenglenda8", 17027,
+ "glenglenda9", 17028,
+ "glenglenda10", 17029,
+ "flyboy", 17032,
+ "dlsftp", 17033,
+ "venti", 17034,
+ "wiki", 17035,
+ "vica", 17036,
+ 0
+};
+
+static int
+lookupport(char *s)
+{
+ int i;
+ char buf[10], *p;
+
+ i = strtol(s, &p, 0);
+ if(*s && *p == 0)
+ return i;
+
+ i = so_getservbyname(s, "tcp", buf);
+ if(i != -1)
+ return atoi(buf);
+ for(i=0; tab[i].name; i++)
+ if(strcmp(s, tab[i].name) == 0)
+ return tab[i].num;
+ return 0;
+}
+
+static ulong
+lookuphost(char *s)
+{
+ char to[4];
+ ulong ip;
+
+ memset(to, 0, sizeof to);
+ parseip(to, s);
+ ip = nhgetl(to);
+ if(ip != 0)
+ return ip;
+ if((s = hostlookup(s)) == nil)
+ return 0;
+ parseip(to, s);
+ ip = nhgetl(to);
+ free(s);
+ return ip;
+}
+
+long
+cswrite(Chan *c, void *a, long n, vlong offset)
+{
+ char *f[4];
+ char *s, *ns;
+ ulong ip;
+ int nf, port;
+
+ s = malloc(n+1);
+ if(s == nil)
+ error(Enomem);
+ if(waserror()){
+ free(s);
+ nexterror();
+ }
+ memmove(s, a, n);
+ s[n] = 0;
+ nf = getfields(s, f, nelem(f), 0, "!");
+ if(nf != 3)
+ error("can't translate");
+
+ port = lookupport(f[2]);
+ if(port <= 0)
+ error("no translation for port found");
+
+ ip = lookuphost(f[1]);
+ if(ip == 0)
+ error("no translation for host found");
+
+ ns = smprint("/net/%s/clone %I!%d", f[0], ip, port);
+ if(ns == nil)
+ error(Enomem);
+ free(c->aux);
+ c->aux = ns;
+ poperror();
+ free(s);
+ return n;
+}
+
+Dev ipdevtab =
+{
+ 'I',
+ "ip",
+
+ devreset,
+ ipinit,
+ devshutdown,
+ ipattach,
+ ipwalk,
+ ipstat,
+ ipopen,
+ devcreate,
+ ipclose,
+ ipread,
+ devbread,
+ ipwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
+
diff --git a/sys/src/cmd/unix/drawterm/kern/devip.h b/sys/src/cmd/unix/drawterm/kern/devip.h
new file mode 100755
index 000000000..950ad398d
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devip.h
@@ -0,0 +1,19 @@
+enum
+{
+ S_TCP,
+ S_UDP
+};
+
+int so_socket(int type);
+void so_connect(int, unsigned long, unsigned short);
+void so_getsockname(int, unsigned long*, unsigned short*);
+void so_bind(int, int, unsigned short);
+void so_listen(int);
+int so_send(int, void*, int, int);
+int so_recv(int, void*, int, int);
+int so_accept(int, unsigned long*, unsigned short*);
+int so_getservbyname(char*, char*, char*);
+int so_gethostbyname(char*, char**, int);
+
+char* hostlookup(char*);
+
diff --git a/sys/src/cmd/unix/drawterm/kern/devlfd.c b/sys/src/cmd/unix/drawterm/kern/devlfd.c
new file mode 100755
index 000000000..7c61e0c1e
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devlfd.c
@@ -0,0 +1,126 @@
+#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->name = newcname("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,
+};
diff --git a/sys/src/cmd/unix/drawterm/kern/devmnt.c b/sys/src/cmd/unix/drawterm/kern/devmnt.c
new file mode 100755
index 000000000..9121bfdb4
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devmnt.c
@@ -0,0 +1,1216 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+/*
+ * References are managed as follows:
+ * The channel to the server - a network connection or pipe - has one
+ * reference for every Chan open on the server. The server channel has
+ * c->mux set to the Mnt used for muxing control to that server. Mnts
+ * have no reference count; they go away when c goes away.
+ * Each channel derived from the mount point has mchan set to c,
+ * and increfs/decrefs mchan to manage references on the server
+ * connection.
+ */
+
+#define MAXRPC (IOHDRSZ+8192)
+
+struct Mntrpc
+{
+ Chan* c; /* Channel for whom we are working */
+ Mntrpc* list; /* Free/pending list */
+ Fcall request; /* Outgoing file system protocol message */
+ Fcall reply; /* Incoming reply */
+ Mnt* m; /* Mount device during rpc */
+ Rendez r; /* Place to hang out */
+ uchar* rpc; /* I/O Data buffer */
+ uint rpclen; /* len of buffer */
+ Block *b; /* reply blocks */
+ char done; /* Rpc completed */
+ uvlong stime; /* start time for mnt statistics */
+ ulong reqlen; /* request length for mnt statistics */
+ ulong replen; /* reply length for mnt statistics */
+ Mntrpc* flushed; /* message this one flushes */
+};
+
+enum
+{
+ TAGSHIFT = 5, /* ulong has to be 32 bits */
+ TAGMASK = (1<<TAGSHIFT)-1,
+ NMASK = (64*1024)>>TAGSHIFT,
+};
+
+struct Mntalloc
+{
+ Lock lk;
+ Mnt* list; /* Mount devices in use */
+ Mnt* mntfree; /* Free list */
+ Mntrpc* rpcfree;
+ int nrpcfree;
+ int nrpcused;
+ ulong id;
+ ulong tagmask[NMASK];
+}mntalloc;
+
+void mattach(Mnt*, Chan*, char*);
+Mnt* mntchk(Chan*);
+void mntdirfix(uchar*, Chan*);
+Mntrpc* mntflushalloc(Mntrpc*, ulong);
+void mntflushfree(Mnt*, Mntrpc*);
+void mntfree(Mntrpc*);
+void mntgate(Mnt*);
+void mntpntfree(Mnt*);
+void mntqrm(Mnt*, Mntrpc*);
+Mntrpc* mntralloc(Chan*, ulong);
+long mntrdwr(int, Chan*, void*, long, vlong);
+int mntrpcread(Mnt*, Mntrpc*);
+void mountio(Mnt*, Mntrpc*);
+void mountmux(Mnt*, Mntrpc*);
+void mountrpc(Mnt*, Mntrpc*);
+int rpcattn(void*);
+Chan* mntchan(void);
+
+char Esbadstat[] = "invalid directory entry received from server";
+char Enoversion[] = "version not established for mount channel";
+
+
+void (*mntstats)(int, Chan*, uvlong, ulong);
+
+static void
+mntreset(void)
+{
+ mntalloc.id = 1;
+ mntalloc.tagmask[0] = 1; /* don't allow 0 as a tag */
+ mntalloc.tagmask[NMASK-1] = 0x80000000UL; /* don't allow NOTAG */
+ fmtinstall('F', fcallfmt);
+ fmtinstall('D', dirfmt);
+/* We can't install %M since eipfmt does and is used in the kernel [sape] */
+
+ cinit();
+}
+
+/*
+ * Version is not multiplexed: message sent only once per connection.
+ */
+long
+mntversion(Chan *c, char *version, int msize, int returnlen)
+{
+ Fcall f;
+ uchar *msg;
+ Mnt *m;
+ char *v;
+ long k, l;
+ uvlong oo;
+ char buf[128];
+
+ qlock(&c->umqlock); /* make sure no one else does this until we've established ourselves */
+ if(waserror()){
+ qunlock(&c->umqlock);
+ nexterror();
+ }
+
+ /* defaults */
+ if(msize == 0)
+ msize = MAXRPC;
+ if(msize > c->iounit && c->iounit != 0)
+ msize = c->iounit;
+ v = version;
+ if(v == nil || v[0] == '\0')
+ v = VERSION9P;
+
+ /* validity */
+ if(msize < 0)
+ error("bad iounit in version call");
+ if(strncmp(v, VERSION9P, strlen(VERSION9P)) != 0)
+ error("bad 9P version specification");
+
+ m = c->mux;
+
+ if(m != nil){
+ qunlock(&c->umqlock);
+ poperror();
+
+ strecpy(buf, buf+sizeof buf, m->version);
+ k = strlen(buf);
+ if(strncmp(buf, v, k) != 0){
+ snprint(buf, sizeof buf, "incompatible 9P versions %s %s", m->version, v);
+ error(buf);
+ }
+ if(returnlen > 0){
+ if(returnlen < k)
+ error(Eshort);
+ memmove(version, buf, k);
+ }
+ return k;
+ }
+
+ f.type = Tversion;
+ f.tag = NOTAG;
+ f.msize = msize;
+ f.version = v;
+ msg = malloc(8192+IOHDRSZ);
+ if(msg == nil)
+ exhausted("version memory");
+ if(waserror()){
+ free(msg);
+ nexterror();
+ }
+ k = convS2M(&f, msg, 8192+IOHDRSZ);
+ if(k == 0)
+ error("bad fversion conversion on send");
+
+ lock(&c->ref.lk);
+ oo = c->offset;
+ c->offset += k;
+ unlock(&c->ref.lk);
+
+ l = devtab[c->type]->write(c, msg, k, oo);
+
+ if(l < k){
+ lock(&c->ref.lk);
+ c->offset -= k - l;
+ unlock(&c->ref.lk);
+ error("short write in fversion");
+ }
+
+ /* message sent; receive and decode reply */
+ k = devtab[c->type]->read(c, msg, 8192+IOHDRSZ, c->offset);
+ if(k <= 0)
+ error("EOF receiving fversion reply");
+
+ lock(&c->ref.lk);
+ c->offset += k;
+ unlock(&c->ref.lk);
+
+ l = convM2S(msg, k, &f);
+ if(l != k)
+ error("bad fversion conversion on reply");
+ if(f.type != Rversion){
+ if(f.type == Rerror)
+ error(f.ename);
+ error("unexpected reply type in fversion");
+ }
+ if(f.msize > msize)
+ error("server tries to increase msize in fversion");
+ if(f.msize<256 || f.msize>1024*1024)
+ error("nonsense value of msize in fversion");
+ k = strlen(f.version);
+ if(strncmp(f.version, v, k) != 0)
+ error("bad 9P version returned from server");
+
+ /* now build Mnt associated with this connection */
+ lock(&mntalloc.lk);
+ m = mntalloc.mntfree;
+ if(m != 0)
+ mntalloc.mntfree = m->list;
+ else {
+ m = malloc(sizeof(Mnt));
+ if(m == 0) {
+ unlock(&mntalloc.lk);
+ exhausted("mount devices");
+ }
+ }
+ m->list = mntalloc.list;
+ mntalloc.list = m;
+ m->version = nil;
+ kstrdup(&m->version, f.version);
+ m->id = mntalloc.id++;
+ m->q = qopen(10*MAXRPC, 0, 0, nil);
+ m->msize = f.msize;
+ unlock(&mntalloc.lk);
+
+ if(returnlen > 0){
+ if(returnlen < k)
+ error(Eshort);
+ memmove(version, f.version, k);
+ }
+
+ poperror(); /* msg */
+ free(msg);
+
+ lock(&m->lk);
+ m->queue = 0;
+ m->rip = 0;
+
+ c->flag |= CMSG;
+ c->mux = m;
+ m->c = c;
+ unlock(&m->lk);
+
+ poperror(); /* c */
+ qunlock(&c->umqlock);
+
+ return k;
+}
+
+Chan*
+mntauth(Chan *c, char *spec)
+{
+ Mnt *m;
+ Mntrpc *r;
+
+ m = c->mux;
+
+ if(m == nil){
+ mntversion(c, VERSION9P, MAXRPC, 0);
+ m = c->mux;
+ if(m == nil)
+ error(Enoversion);
+ }
+
+ c = mntchan();
+ if(waserror()) {
+ /* Close must not be called since it will
+ * call mnt recursively
+ */
+ chanfree(c);
+ nexterror();
+ }
+
+ r = mntralloc(0, m->msize);
+
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+
+ r->request.type = Tauth;
+ r->request.afid = c->fid;
+ r->request.uname = up->user;
+ r->request.aname = spec;
+ mountrpc(m, r);
+
+ c->qid = r->reply.aqid;
+ c->mchan = m->c;
+ incref(&m->c->ref);
+ c->mqid = c->qid;
+ c->mode = ORDWR;
+
+ poperror(); /* r */
+ mntfree(r);
+
+ poperror(); /* c */
+
+ return c;
+
+}
+
+static Chan*
+mntattach(char *muxattach)
+{
+ Mnt *m;
+ Chan *c;
+ Mntrpc *r;
+ struct bogus{
+ Chan *chan;
+ Chan *authchan;
+ char *spec;
+ int flags;
+ }bogus;
+
+ bogus = *((struct bogus *)muxattach);
+ c = bogus.chan;
+
+ m = c->mux;
+
+ if(m == nil){
+ mntversion(c, nil, 0, 0);
+ m = c->mux;
+ if(m == nil)
+ error(Enoversion);
+ }
+
+ c = mntchan();
+ if(waserror()) {
+ /* Close must not be called since it will
+ * call mnt recursively
+ */
+ chanfree(c);
+ nexterror();
+ }
+
+ r = mntralloc(0, m->msize);
+
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+
+ r->request.type = Tattach;
+ r->request.fid = c->fid;
+ if(bogus.authchan == nil)
+ r->request.afid = NOFID;
+ else
+ r->request.afid = bogus.authchan->fid;
+ r->request.uname = up->user;
+ r->request.aname = bogus.spec;
+ mountrpc(m, r);
+
+ c->qid = r->reply.qid;
+ c->mchan = m->c;
+ incref(&m->c->ref);
+ c->mqid = c->qid;
+
+ poperror(); /* r */
+ mntfree(r);
+
+ poperror(); /* c */
+
+ if(bogus.flags&MCACHE)
+ c->flag |= CCACHE;
+ return c;
+}
+
+Chan*
+mntchan(void)
+{
+ Chan *c;
+
+ c = devattach('M', 0);
+ lock(&mntalloc.lk);
+ c->dev = mntalloc.id++;
+ unlock(&mntalloc.lk);
+
+ if(c->mchan)
+ panic("mntchan non-zero %p", c->mchan);
+ return c;
+}
+
+static Walkqid*
+mntwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ int i, alloc;
+ Mnt *m;
+ Mntrpc *r;
+ Walkqid *wq;
+
+ if(nc != nil)
+ print("mntwalk: nc != nil\n");
+ if(nname > MAXWELEM)
+ error("devmnt: too many name elements");
+ alloc = 0;
+ wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+ if(waserror()){
+ if(alloc && wq->clone!=nil)
+ cclose(wq->clone);
+ free(wq);
+ return nil;
+ }
+
+ alloc = 0;
+ m = mntchk(c);
+ r = mntralloc(c, m->msize);
+ if(nc == nil){
+ nc = devclone(c);
+ /*
+ * Until the other side accepts this fid, we can't mntclose it.
+ * Therefore set type to 0 for now; rootclose is known to be safe.
+ */
+ nc->type = 0;
+ alloc = 1;
+ }
+ wq->clone = nc;
+
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+ r->request.type = Twalk;
+ r->request.fid = c->fid;
+ r->request.newfid = nc->fid;
+ r->request.nwname = nname;
+ memmove(r->request.wname, name, nname*sizeof(char*));
+
+ mountrpc(m, r);
+
+ if(r->reply.nwqid > nname)
+ error("too many QIDs returned by walk");
+ if(r->reply.nwqid < nname){
+ if(alloc)
+ cclose(nc);
+ wq->clone = nil;
+ if(r->reply.nwqid == 0){
+ free(wq);
+ wq = nil;
+ goto Return;
+ }
+ }
+
+ /* move new fid onto mnt device and update its qid */
+ if(wq->clone != nil){
+ if(wq->clone != c){
+ wq->clone->type = c->type;
+ wq->clone->mchan = c->mchan;
+ incref(&c->mchan->ref);
+ }
+ if(r->reply.nwqid > 0)
+ wq->clone->qid = r->reply.wqid[r->reply.nwqid-1];
+ }
+ wq->nqid = r->reply.nwqid;
+ for(i=0; i<wq->nqid; i++)
+ wq->qid[i] = r->reply.wqid[i];
+
+ Return:
+ poperror();
+ mntfree(r);
+ poperror();
+ return wq;
+}
+
+static int
+mntstat(Chan *c, uchar *dp, int n)
+{
+ Mnt *m;
+ Mntrpc *r;
+
+ if(n < BIT16SZ)
+ error(Eshortstat);
+ m = mntchk(c);
+ r = mntralloc(c, m->msize);
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+ r->request.type = Tstat;
+ r->request.fid = c->fid;
+ mountrpc(m, r);
+
+/* r->reply.nstat is 16 bits
+ if(r->reply.nstat >= 1<<16)
+ error("returned stat buffer count too large");
+*/
+
+ if(r->reply.nstat > n){
+ /*
+ * 12/31/2002 RSC
+ *
+ * This should be nstat-2, which is the first two
+ * bytes of the stat buffer. But dirstat and dirfstat
+ * depended on getting the full nstat (they didn't
+ * add BIT16SZ themselves). I fixed dirstat and dirfstat
+ * but am leaving this unchanged for now. After a
+ * few months, once enough of the relevant binaries
+ * have been recompiled for other reasons, we can
+ * change this to nstat-2. Devstat gets this right
+ * (via convD2M).
+ */
+ /* doesn't fit; just patch the count and return */
+ PBIT16((uchar*)dp, r->reply.nstat);
+ n = BIT16SZ;
+ }else{
+ n = r->reply.nstat;
+ memmove(dp, r->reply.stat, n);
+ validstat(dp, n);
+ mntdirfix(dp, c);
+ }
+ poperror();
+ mntfree(r);
+ return n;
+}
+
+static Chan*
+mntopencreate(int type, Chan *c, char *name, int omode, ulong perm)
+{
+ Mnt *m;
+ Mntrpc *r;
+
+ m = mntchk(c);
+ r = mntralloc(c, m->msize);
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+ r->request.type = type;
+ r->request.fid = c->fid;
+ r->request.mode = omode;
+ if(type == Tcreate){
+ r->request.perm = perm;
+ r->request.name = name;
+ }
+ mountrpc(m, r);
+
+ c->qid = r->reply.qid;
+ c->offset = 0;
+ c->mode = openmode(omode);
+ c->iounit = r->reply.iounit;
+ if(c->iounit == 0 || c->iounit > m->msize-IOHDRSZ)
+ c->iounit = m->msize-IOHDRSZ;
+ c->flag |= COPEN;
+ poperror();
+ mntfree(r);
+
+ if(c->flag & CCACHE)
+ copen(c);
+
+ return c;
+}
+
+static Chan*
+mntopen(Chan *c, int omode)
+{
+ return mntopencreate(Topen, c, nil, omode, 0);
+}
+
+static void
+mntcreate(Chan *c, char *name, int omode, ulong perm)
+{
+ mntopencreate(Tcreate, c, name, omode, perm);
+}
+
+static void
+mntclunk(Chan *c, int t)
+{
+ Mnt *m;
+ Mntrpc *r;
+
+ m = mntchk(c);
+ r = mntralloc(c, m->msize);
+ if(waserror()){
+ mntfree(r);
+ nexterror();
+ }
+
+ r->request.type = t;
+ r->request.fid = c->fid;
+ mountrpc(m, r);
+ mntfree(r);
+ poperror();
+}
+
+void
+muxclose(Mnt *m)
+{
+ Mntrpc *q, *r;
+
+ for(q = m->queue; q; q = r) {
+ r = q->list;
+ mntfree(q);
+ }
+ m->id = 0;
+ free(m->version);
+ m->version = nil;
+ mntpntfree(m);
+}
+
+void
+mntpntfree(Mnt *m)
+{
+ Mnt *f, **l;
+ Queue *q;
+
+ lock(&mntalloc.lk);
+ l = &mntalloc.list;
+ for(f = *l; f; f = f->list) {
+ if(f == m) {
+ *l = m->list;
+ break;
+ }
+ l = &f->list;
+ }
+ m->list = mntalloc.mntfree;
+ mntalloc.mntfree = m;
+ q = m->q;
+ unlock(&mntalloc.lk);
+
+ qfree(q);
+}
+
+static void
+mntclose(Chan *c)
+{
+ mntclunk(c, Tclunk);
+}
+
+static void
+mntremove(Chan *c)
+{
+ mntclunk(c, Tremove);
+}
+
+static int
+mntwstat(Chan *c, uchar *dp, int n)
+{
+ Mnt *m;
+ Mntrpc *r;
+
+ m = mntchk(c);
+ r = mntralloc(c, m->msize);
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+ r->request.type = Twstat;
+ r->request.fid = c->fid;
+ r->request.nstat = n;
+ r->request.stat = dp;
+ mountrpc(m, r);
+ poperror();
+ mntfree(r);
+ return n;
+}
+
+static long
+mntread(Chan *c, void *buf, long n, vlong off)
+{
+ uchar *p, *e;
+ int nc, cache, isdir, dirlen;
+
+ isdir = 0;
+ cache = c->flag & CCACHE;
+ if(c->qid.type & QTDIR) {
+ cache = 0;
+ isdir = 1;
+ }
+
+ p = buf;
+ if(cache) {
+ nc = cread(c, buf, n, off);
+ if(nc > 0) {
+ n -= nc;
+ if(n == 0)
+ return nc;
+ p += nc;
+ off += nc;
+ }
+ n = mntrdwr(Tread, c, p, n, off);
+ cupdate(c, p, n, off);
+ return n + nc;
+ }
+
+ n = mntrdwr(Tread, c, buf, n, off);
+ if(isdir) {
+ for(e = &p[n]; p+BIT16SZ < e; p += dirlen){
+ dirlen = BIT16SZ+GBIT16(p);
+ if(p+dirlen > e)
+ break;
+ validstat(p, dirlen);
+ mntdirfix(p, c);
+ }
+ if(p != e)
+ error(Esbadstat);
+ }
+ return n;
+}
+
+static long
+mntwrite(Chan *c, void *buf, long n, vlong off)
+{
+ return mntrdwr(Twrite, c, buf, n, off);
+}
+
+long
+mntrdwr(int type, Chan *c, void *buf, long n, vlong off)
+{
+ Mnt *m;
+ Mntrpc *r;
+ char *uba;
+ int cache;
+ ulong cnt, nr, nreq;
+
+ m = mntchk(c);
+ uba = buf;
+ cnt = 0;
+ cache = c->flag & CCACHE;
+ if(c->qid.type & QTDIR)
+ cache = 0;
+ for(;;) {
+ r = mntralloc(c, m->msize);
+ if(waserror()) {
+ mntfree(r);
+ nexterror();
+ }
+ r->request.type = type;
+ r->request.fid = c->fid;
+ r->request.offset = off;
+ r->request.data = uba;
+ nr = n;
+ if(nr > m->msize-IOHDRSZ)
+ nr = m->msize-IOHDRSZ;
+ r->request.count = nr;
+ mountrpc(m, r);
+ nreq = r->request.count;
+ nr = r->reply.count;
+ if(nr > nreq)
+ nr = nreq;
+
+ if(type == Tread)
+ r->b = bl2mem((uchar*)uba, r->b, nr);
+ else if(cache)
+ cwrite(c, (uchar*)uba, nr, off);
+
+ poperror();
+ mntfree(r);
+ off += nr;
+ uba += nr;
+ cnt += nr;
+ n -= nr;
+ if(nr != nreq || n == 0)
+ break;
+ }
+ return cnt;
+}
+
+void
+mountrpc(Mnt *m, Mntrpc *r)
+{
+ char *sn, *cn;
+ int t;
+
+ r->reply.tag = 0;
+ r->reply.type = Tmax; /* can't ever be a valid message type */
+
+ mountio(m, r);
+
+ t = r->reply.type;
+ switch(t) {
+ case Rerror:
+ error(r->reply.ename);
+ case Rflush:
+ error(Eintr);
+ default:
+ if(t == r->request.type+1)
+ break;
+ sn = "?";
+ if(m->c->name != nil)
+ sn = m->c->name->s;
+ cn = "?";
+ if(r->c != nil && r->c->name != nil)
+ cn = r->c->name->s;
+ print("mnt: proc %lud: mismatch from %s %s rep 0x%lux tag %d fid %d T%d R%d rp %d\n",
+ up->pid, sn, cn,
+ r, r->request.tag, r->request.fid, r->request.type,
+ r->reply.type, r->reply.tag);
+ error(Emountrpc);
+ }
+}
+
+void
+mountio(Mnt *m, Mntrpc *r)
+{
+ int n;
+
+ while(waserror()) {
+ if(m->rip == up)
+ mntgate(m);
+ if(strcmp(up->errstr, Eintr) != 0){
+ mntflushfree(m, r);
+ nexterror();
+ }
+ r = mntflushalloc(r, m->msize);
+ }
+
+ lock(&m->lk);
+ r->m = m;
+ r->list = m->queue;
+ m->queue = r;
+ unlock(&m->lk);
+
+ /* Transmit a file system rpc */
+ if(m->msize == 0)
+ panic("msize");
+ n = convS2M(&r->request, r->rpc, m->msize);
+ if(n < 0)
+ panic("bad message type in mountio");
+ if(devtab[m->c->type]->write(m->c, r->rpc, n, 0) != n)
+ error(Emountrpc);
+ r->stime = fastticks(nil);
+ r->reqlen = n;
+
+ /* Gate readers onto the mount point one at a time */
+ for(;;) {
+ lock(&m->lk);
+ if(m->rip == 0)
+ break;
+ unlock(&m->lk);
+ sleep(&r->r, rpcattn, r);
+ if(r->done){
+ poperror();
+ mntflushfree(m, r);
+ return;
+ }
+ }
+ m->rip = up;
+ unlock(&m->lk);
+ while(r->done == 0) {
+ if(mntrpcread(m, r) < 0)
+ error(Emountrpc);
+ mountmux(m, r);
+ }
+ mntgate(m);
+ poperror();
+ mntflushfree(m, r);
+}
+
+static int
+doread(Mnt *m, int len)
+{
+ Block *b;
+
+ while(qlen(m->q) < len){
+ b = devtab[m->c->type]->bread(m->c, m->msize, 0);
+ if(b == nil)
+ return -1;
+ if(BLEN(b) == 0){
+ freeblist(b);
+ return -1;
+ }
+ qaddlist(m->q, b);
+ }
+ return 0;
+}
+
+int
+mntrpcread(Mnt *m, Mntrpc *r)
+{
+ int i, t, len, hlen;
+ Block *b, **l, *nb;
+
+ r->reply.type = 0;
+ r->reply.tag = 0;
+
+ /* read at least length, type, and tag and pullup to a single block */
+ if(doread(m, BIT32SZ+BIT8SZ+BIT16SZ) < 0)
+ return -1;
+ nb = pullupqueue(m->q, BIT32SZ+BIT8SZ+BIT16SZ);
+
+ /* read in the rest of the message, avoid rediculous (for now) message sizes */
+ len = GBIT32(nb->rp);
+ if(len > m->msize){
+ qdiscard(m->q, qlen(m->q));
+ return -1;
+ }
+ if(doread(m, len) < 0)
+ return -1;
+
+ /* pullup the header (i.e. everything except data) */
+ t = nb->rp[BIT32SZ];
+ switch(t){
+ case Rread:
+ hlen = BIT32SZ+BIT8SZ+BIT16SZ+BIT32SZ;
+ break;
+ default:
+ hlen = len;
+ break;
+ }
+ nb = pullupqueue(m->q, hlen);
+
+ if(convM2S(nb->rp, len, &r->reply) <= 0){
+ /* bad message, dump it */
+ print("mntrpcread: convM2S failed\n");
+ qdiscard(m->q, len);
+ return -1;
+ }
+
+ /* hang the data off of the fcall struct */
+ l = &r->b;
+ *l = nil;
+ do {
+ b = qremove(m->q);
+ if(hlen > 0){
+ b->rp += hlen;
+ len -= hlen;
+ hlen = 0;
+ }
+ i = BLEN(b);
+ if(i <= len){
+ len -= i;
+ *l = b;
+ l = &(b->next);
+ } else {
+ /* split block and put unused bit back */
+ nb = allocb(i-len);
+ memmove(nb->wp, b->rp+len, i-len);
+ b->wp = b->rp+len;
+ nb->wp += i-len;
+ qputback(m->q, nb);
+ *l = b;
+ return 0;
+ }
+ }while(len > 0);
+
+ return 0;
+}
+
+void
+mntgate(Mnt *m)
+{
+ Mntrpc *q;
+
+ lock(&m->lk);
+ m->rip = 0;
+ for(q = m->queue; q; q = q->list) {
+ if(q->done == 0)
+ if(wakeup(&q->r))
+ break;
+ }
+ unlock(&m->lk);
+}
+
+void
+mountmux(Mnt *m, Mntrpc *r)
+{
+ Mntrpc **l, *q;
+
+ lock(&m->lk);
+ l = &m->queue;
+ for(q = *l; q; q = q->list) {
+ /* look for a reply to a message */
+ if(q->request.tag == r->reply.tag) {
+ *l = q->list;
+ if(q != r) {
+ /*
+ * Completed someone else.
+ * Trade pointers to receive buffer.
+ */
+ q->reply = r->reply;
+ q->b = r->b;
+ r->b = nil;
+ }
+ q->done = 1;
+ unlock(&m->lk);
+ if(mntstats != 0)
+ (*mntstats)(q->request.type,
+ m->c, q->stime,
+ q->reqlen + r->replen);
+ if(q != r)
+ wakeup(&q->r);
+ return;
+ }
+ l = &q->list;
+ }
+ unlock(&m->lk);
+ print("unexpected reply tag %ud; type %d\n", r->reply.tag, r->reply.type);
+}
+
+/*
+ * Create a new flush request and chain the previous
+ * requests from it
+ */
+Mntrpc*
+mntflushalloc(Mntrpc *r, ulong iounit)
+{
+ Mntrpc *fr;
+
+ fr = mntralloc(0, iounit);
+
+ fr->request.type = Tflush;
+ if(r->request.type == Tflush)
+ fr->request.oldtag = r->request.oldtag;
+ else
+ fr->request.oldtag = r->request.tag;
+ fr->flushed = r;
+
+ return fr;
+}
+
+/*
+ * Free a chain of flushes. Remove each unanswered
+ * flush and the original message from the unanswered
+ * request queue. Mark the original message as done
+ * and if it hasn't been answered set the reply to to
+ * Rflush.
+ */
+void
+mntflushfree(Mnt *m, Mntrpc *r)
+{
+ Mntrpc *fr;
+
+ while(r){
+ fr = r->flushed;
+ if(!r->done){
+ r->reply.type = Rflush;
+ mntqrm(m, r);
+ }
+ if(fr)
+ mntfree(r);
+ r = fr;
+ }
+}
+
+int
+alloctag(void)
+{
+ int i, j;
+ ulong v;
+
+ for(i = 0; i < NMASK; i++){
+ v = mntalloc.tagmask[i];
+ if(v == ~0)
+ continue;
+ for(j = 0; j < 1<<TAGSHIFT; j++)
+ if((v & (1<<j)) == 0){
+ mntalloc.tagmask[i] |= 1<<j;
+ return (i<<TAGSHIFT) + j;
+ }
+ }
+ panic("no friggin tags left");
+ return NOTAG;
+}
+
+void
+freetag(int t)
+{
+ mntalloc.tagmask[t>>TAGSHIFT] &= ~(1<<(t&TAGMASK));
+}
+
+Mntrpc*
+mntralloc(Chan *c, ulong msize)
+{
+ Mntrpc *new;
+
+ lock(&mntalloc.lk);
+ new = mntalloc.rpcfree;
+ if(new == nil){
+ new = malloc(sizeof(Mntrpc));
+ if(new == nil) {
+ unlock(&mntalloc.lk);
+ exhausted("mount rpc header");
+ }
+ /*
+ * The header is split from the data buffer as
+ * mountmux may swap the buffer with another header.
+ */
+ new->rpc = mallocz(msize, 0);
+ if(new->rpc == nil){
+ free(new);
+ unlock(&mntalloc.lk);
+ exhausted("mount rpc buffer");
+ }
+ new->rpclen = msize;
+ new->request.tag = alloctag();
+ }
+ else {
+ mntalloc.rpcfree = new->list;
+ mntalloc.nrpcfree--;
+ if(new->rpclen < msize){
+ free(new->rpc);
+ new->rpc = mallocz(msize, 0);
+ if(new->rpc == nil){
+ free(new);
+ mntalloc.nrpcused--;
+ unlock(&mntalloc.lk);
+ exhausted("mount rpc buffer");
+ }
+ new->rpclen = msize;
+ }
+ }
+ mntalloc.nrpcused++;
+ unlock(&mntalloc.lk);
+ new->c = c;
+ new->done = 0;
+ new->flushed = nil;
+ new->b = nil;
+ return new;
+}
+
+void
+mntfree(Mntrpc *r)
+{
+ if(r->b != nil)
+ freeblist(r->b);
+ lock(&mntalloc.lk);
+ if(mntalloc.nrpcfree >= 10){
+ free(r->rpc);
+ free(r);
+ freetag(r->request.tag);
+ }
+ else{
+ r->list = mntalloc.rpcfree;
+ mntalloc.rpcfree = r;
+ mntalloc.nrpcfree++;
+ }
+ mntalloc.nrpcused--;
+ unlock(&mntalloc.lk);
+}
+
+void
+mntqrm(Mnt *m, Mntrpc *r)
+{
+ Mntrpc **l, *f;
+
+ lock(&m->lk);
+ r->done = 1;
+
+ l = &m->queue;
+ for(f = *l; f; f = f->list) {
+ if(f == r) {
+ *l = r->list;
+ break;
+ }
+ l = &f->list;
+ }
+ unlock(&m->lk);
+}
+
+Mnt*
+mntchk(Chan *c)
+{
+ Mnt *m;
+
+ /* This routine is mostly vestiges of prior lives; now it's just sanity checking */
+
+ if(c->mchan == nil)
+ panic("mntchk 1: nil mchan c %s\n", c2name(c));
+
+ m = c->mchan->mux;
+
+ if(m == nil)
+ print("mntchk 2: nil mux c %s c->mchan %s \n", c2name(c), c2name(c->mchan));
+
+ /*
+ * Was it closed and reused (was error(Eshutdown); now, it can't happen)
+ */
+ if(m->id == 0 || m->id >= c->dev)
+ panic("mntchk 3: can't happen");
+
+ return m;
+}
+
+/*
+ * Rewrite channel type and dev for in-flight data to
+ * reflect local values. These entries are known to be
+ * the first two in the Dir encoding after the count.
+ */
+void
+mntdirfix(uchar *dirbuf, Chan *c)
+{
+ uint r;
+
+ r = devtab[c->type]->dc;
+ dirbuf += BIT16SZ; /* skip count */
+ PBIT16(dirbuf, r);
+ dirbuf += BIT16SZ;
+ PBIT32(dirbuf, c->dev);
+}
+
+int
+rpcattn(void *v)
+{
+ Mntrpc *r;
+
+ r = v;
+ return r->done || r->m->rip == 0;
+}
+
+Dev mntdevtab = {
+ 'M',
+ "mnt",
+
+ mntreset,
+ devinit,
+ devshutdown,
+ mntattach,
+ mntwalk,
+ mntstat,
+ mntopen,
+ mntcreate,
+ mntclose,
+ mntread,
+ devbread,
+ mntwrite,
+ devbwrite,
+ mntremove,
+ mntwstat,
+};
diff --git a/sys/src/cmd/unix/drawterm/kern/devmouse.c b/sys/src/cmd/unix/drawterm/kern/devmouse.c
new file mode 100755
index 000000000..3ee0c8b6b
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devmouse.c
@@ -0,0 +1,237 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "draw.h"
+#include "memdraw.h"
+#include "screen.h"
+
+int mousequeue = 1;
+
+Mouseinfo mouse;
+Cursorinfo cursor;
+
+static int mousechanged(void*);
+
+enum{
+ Qdir,
+ Qcursor,
+ Qmouse
+};
+
+Dirtab mousedir[]={
+ ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
+ "cursor", {Qcursor}, 0, 0666,
+ "mouse", {Qmouse}, 0, 0666,
+};
+
+#define NMOUSE (sizeof(mousedir)/sizeof(Dirtab))
+
+static Chan*
+mouseattach(char *spec)
+{
+ return devattach('m', spec);
+}
+
+static Walkqid*
+mousewalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, mousedir, NMOUSE, devgen);
+}
+
+static int
+mousestat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, mousedir, NMOUSE, devgen);
+}
+
+static Chan*
+mouseopen(Chan *c, int omode)
+{
+ switch((long)c->qid.path){
+ case Qdir:
+ if(omode != OREAD)
+ error(Eperm);
+ break;
+ case Qmouse:
+ lock(&mouse.lk);
+ if(mouse.open){
+ unlock(&mouse.lk);
+ error(Einuse);
+ }
+ mouse.open = 1;
+ unlock(&mouse.lk);
+ break;
+ }
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+}
+
+void
+mouseclose(Chan *c)
+{
+ if(!(c->flag&COPEN))
+ return;
+
+ switch((long)c->qid.path) {
+ case Qmouse:
+ lock(&mouse.lk);
+ mouse.open = 0;
+ unlock(&mouse.lk);
+ cursorarrow();
+ }
+}
+
+
+long
+mouseread(Chan *c, void *va, long n, vlong offset)
+{
+ char buf[4*12+1];
+ uchar *p;
+ int i, nn;
+ ulong msec;
+/* static int map[8] = {0, 4, 2, 6, 1, 5, 3, 7 }; */
+
+ p = va;
+ switch((long)c->qid.path){
+ case Qdir:
+ return devdirread(c, va, n, mousedir, NMOUSE, devgen);
+
+ case Qcursor:
+ if(offset != 0)
+ return 0;
+ if(n < 2*4+2*2*16)
+ error(Eshort);
+ n = 2*4+2*2*16;
+ lock(&cursor.lk);
+ BPLONG(p+0, cursor.offset.x);
+ BPLONG(p+4, cursor.offset.y);
+ memmove(p+8, cursor.clr, 2*16);
+ memmove(p+40, cursor.set, 2*16);
+ unlock(&cursor.lk);
+ return n;
+
+ case Qmouse:
+ while(mousechanged(0) == 0)
+ sleep(&mouse.r, mousechanged, 0);
+
+ lock(&screen.lk);
+ if(screen.reshaped) {
+ screen.reshaped = 0;
+ sprint(buf, "t%11d %11d", 0, ticks());
+ if(n > 1+2*12)
+ n = 1+2*12;
+ memmove(va, buf, n);
+ unlock(&screen.lk);
+ return n;
+ }
+ unlock(&screen.lk);
+
+ lock(&mouse.lk);
+ i = mouse.ri;
+ nn = (mouse.wi + Mousequeue - i) % Mousequeue;
+ if(nn < 1)
+ panic("empty mouse queue");
+ msec = ticks();
+ while(nn > 1) {
+ if(mouse.queue[i].msec + Mousewindow > msec)
+ break;
+ i = (i+1)%Mousequeue;
+ nn--;
+ }
+ sprint(buf, "m%11d %11d %11d %11d",
+ mouse.queue[i].xy.x,
+ mouse.queue[i].xy.y,
+ mouse.queue[i].buttons,
+ mouse.queue[i].msec);
+ mouse.ri = (i+1)%Mousequeue;
+ unlock(&mouse.lk);
+ if(n > 1+4*12)
+ n = 1+4*12;
+ memmove(va, buf, n);
+ return n;
+ }
+ return 0;
+}
+
+long
+mousewrite(Chan *c, void *va, long n, vlong offset)
+{
+ char *p;
+ Point pt;
+ char buf[64];
+
+ USED(offset);
+
+ p = va;
+ switch((long)c->qid.path){
+ case Qdir:
+ error(Eisdir);
+
+ case Qcursor:
+ if(n < 2*4+2*2*16){
+ cursorarrow();
+ }else{
+ n = 2*4+2*2*16;
+ lock(&cursor.lk);
+ cursor.offset.x = BGLONG(p+0);
+ cursor.offset.y = BGLONG(p+4);
+ memmove(cursor.clr, p+8, 2*16);
+ memmove(cursor.set, p+40, 2*16);
+ unlock(&cursor.lk);
+ setcursor();
+ }
+ return n;
+
+ case Qmouse:
+ if(n > sizeof buf-1)
+ n = sizeof buf -1;
+ memmove(buf, va, n);
+ buf[n] = 0;
+ p = 0;
+ pt.x = strtoul(buf+1, &p, 0);
+ if(p == 0)
+ error(Eshort);
+ pt.y = strtoul(p, 0, 0);
+ if(ptinrect(pt, gscreen->r))
+ mouseset(pt);
+ return n;
+ }
+
+ error(Egreg);
+ return -1;
+}
+
+int
+mousechanged(void *a)
+{
+ USED(a);
+
+ return mouse.ri != mouse.wi || screen.reshaped;
+}
+
+Dev mousedevtab = {
+ 'm',
+ "mouse",
+
+ devreset,
+ devinit,
+ devshutdown,
+ mouseattach,
+ mousewalk,
+ mousestat,
+ mouseopen,
+ devcreate,
+ mouseclose,
+ mouseread,
+ devbread,
+ mousewrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
+
diff --git a/sys/src/cmd/unix/drawterm/kern/devpipe.c b/sys/src/cmd/unix/drawterm/kern/devpipe.c
new file mode 100755
index 000000000..73401f99f
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devpipe.c
@@ -0,0 +1,398 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "netif.h"
+
+typedef struct Pipe Pipe;
+struct Pipe
+{
+ QLock lk;
+ Pipe *next;
+ int ref;
+ ulong path;
+ Queue *q[2];
+ int qref[2];
+};
+
+struct
+{
+ Lock lk;
+ ulong path;
+} pipealloc;
+
+enum
+{
+ Qdir,
+ Qdata0,
+ Qdata1,
+};
+
+Dirtab pipedir[] =
+{
+ ".", {Qdir,0,QTDIR}, 0, DMDIR|0500,
+ "data", {Qdata0}, 0, 0600,
+ "data1", {Qdata1}, 0, 0600,
+};
+#define NPIPEDIR 3
+
+static void
+pipeinit(void)
+{
+ if(conf.pipeqsize == 0){
+ if(conf.nmach > 1)
+ conf.pipeqsize = 256*1024;
+ else
+ conf.pipeqsize = 32*1024;
+ }
+}
+
+/*
+ * create a pipe, no streams are created until an open
+ */
+static Chan*
+pipeattach(char *spec)
+{
+ Pipe *p;
+ Chan *c;
+
+ c = devattach('|', spec);
+ p = malloc(sizeof(Pipe));
+ if(p == 0)
+ exhausted("memory");
+ p->ref = 1;
+
+ p->q[0] = qopen(conf.pipeqsize, 0, 0, 0);
+ if(p->q[0] == 0){
+ free(p);
+ exhausted("memory");
+ }
+ p->q[1] = qopen(conf.pipeqsize, 0, 0, 0);
+ if(p->q[1] == 0){
+ free(p->q[0]);
+ free(p);
+ exhausted("memory");
+ }
+
+ lock(&pipealloc.lk);
+ p->path = ++pipealloc.path;
+ unlock(&pipealloc.lk);
+
+ mkqid(&c->qid, NETQID(2*p->path, Qdir), 0, QTDIR);
+ c->aux = p;
+ c->dev = 0;
+ return c;
+}
+
+static int
+pipegen(Chan *c, char *name, Dirtab *tab, int ntab, int i, Dir *dp)
+{
+ Qid q;
+ int len;
+ Pipe *p;
+
+ USED(name);
+
+ if(i == DEVDOTDOT){
+ devdir(c, c->qid, "#|", 0, eve, DMDIR|0555, dp);
+ return 1;
+ }
+ i++; /* skip . */
+ if(tab==0 || i>=ntab)
+ return -1;
+
+ tab += i;
+ p = c->aux;
+ switch((ulong)tab->qid.path){
+ case Qdata0:
+ len = qlen(p->q[0]);
+ break;
+ case Qdata1:
+ len = qlen(p->q[1]);
+ break;
+ default:
+ len = tab->length;
+ break;
+ }
+ mkqid(&q, NETQID(NETID(c->qid.path), tab->qid.path), 0, QTFILE);
+ devdir(c, q, tab->name, len, eve, tab->perm, dp);
+ return 1;
+}
+
+
+static Walkqid*
+pipewalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ Walkqid *wq;
+ Pipe *p;
+
+ wq = devwalk(c, nc, name, nname, pipedir, NPIPEDIR, pipegen);
+ if(wq != nil && wq->clone != nil && wq->clone != c){
+ p = c->aux;
+ qlock(&p->lk);
+ p->ref++;
+ if(c->flag & COPEN){
+ print("channel open in pipewalk\n");
+ switch(NETTYPE(c->qid.path)){
+ case Qdata0:
+ p->qref[0]++;
+ break;
+ case Qdata1:
+ p->qref[1]++;
+ break;
+ }
+ }
+ qunlock(&p->lk);
+ }
+ return wq;
+}
+
+static int
+pipestat(Chan *c, uchar *db, int n)
+{
+ Pipe *p;
+ Dir dir;
+
+ p = c->aux;
+
+ switch(NETTYPE(c->qid.path)){
+ case Qdir:
+ devdir(c, c->qid, ".", 0, eve, DMDIR|0555, &dir);
+ break;
+ case Qdata0:
+ devdir(c, c->qid, "data", qlen(p->q[0]), eve, 0600, &dir);
+ break;
+ case Qdata1:
+ devdir(c, c->qid, "data1", qlen(p->q[1]), eve, 0600, &dir);
+ break;
+ default:
+ panic("pipestat");
+ }
+ n = convD2M(&dir, db, n);
+ if(n < BIT16SZ)
+ error(Eshortstat);
+ return n;
+}
+
+/*
+ * if the stream doesn't exist, create it
+ */
+static Chan*
+pipeopen(Chan *c, int omode)
+{
+ Pipe *p;
+
+ if(c->qid.type & QTDIR){
+ if(omode != OREAD)
+ error(Ebadarg);
+ c->mode = omode;
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+ }
+
+ p = c->aux;
+ qlock(&p->lk);
+ switch(NETTYPE(c->qid.path)){
+ case Qdata0:
+ p->qref[0]++;
+ break;
+ case Qdata1:
+ p->qref[1]++;
+ break;
+ }
+ qunlock(&p->lk);
+
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ c->iounit = qiomaxatomic;
+ return c;
+}
+
+static void
+pipeclose(Chan *c)
+{
+ Pipe *p;
+
+ p = c->aux;
+ qlock(&p->lk);
+
+ if(c->flag & COPEN){
+ /*
+ * closing either side hangs up the stream
+ */
+ switch(NETTYPE(c->qid.path)){
+ case Qdata0:
+ p->qref[0]--;
+ if(p->qref[0] == 0){
+ qhangup(p->q[1], 0);
+ qclose(p->q[0]);
+ }
+ break;
+ case Qdata1:
+ p->qref[1]--;
+ if(p->qref[1] == 0){
+ qhangup(p->q[0], 0);
+ qclose(p->q[1]);
+ }
+ break;
+ }
+ }
+
+
+ /*
+ * if both sides are closed, they are reusable
+ */
+ if(p->qref[0] == 0 && p->qref[1] == 0){
+ qreopen(p->q[0]);
+ qreopen(p->q[1]);
+ }
+
+ /*
+ * free the structure on last close
+ */
+ p->ref--;
+ if(p->ref == 0){
+ qunlock(&p->lk);
+ free(p->q[0]);
+ free(p->q[1]);
+ free(p);
+ } else
+ qunlock(&p->lk);
+}
+
+static long
+piperead(Chan *c, void *va, long n, vlong offset)
+{
+ Pipe *p;
+
+ USED(offset);
+
+ p = c->aux;
+
+ switch(NETTYPE(c->qid.path)){
+ case Qdir:
+ return devdirread(c, va, n, pipedir, NPIPEDIR, pipegen);
+ case Qdata0:
+ return qread(p->q[0], va, n);
+ case Qdata1:
+ return qread(p->q[1], va, n);
+ default:
+ panic("piperead");
+ }
+ return -1; /* not reached */
+}
+
+static Block*
+pipebread(Chan *c, long n, ulong offset)
+{
+ Pipe *p;
+
+ p = c->aux;
+
+ switch(NETTYPE(c->qid.path)){
+ case Qdata0:
+ return qbread(p->q[0], n);
+ case Qdata1:
+ return qbread(p->q[1], n);
+ }
+
+ return devbread(c, n, offset);
+}
+
+/*
+ * a write to a closed pipe causes a note to be sent to
+ * the process.
+ */
+static long
+pipewrite(Chan *c, void *va, long n, vlong offset)
+{
+ Pipe *p;
+
+ USED(offset);
+ if(!islo())
+ print("pipewrite hi %lux\n", getcallerpc(&c));
+
+ if(waserror()) {
+ /* avoid notes when pipe is a mounted queue */
+ if((c->flag & CMSG) == 0)
+ postnote(up, 1, "sys: write on closed pipe", NUser);
+ nexterror();
+ }
+
+ p = c->aux;
+
+ switch(NETTYPE(c->qid.path)){
+ case Qdata0:
+ n = qwrite(p->q[1], va, n);
+ break;
+
+ case Qdata1:
+ n = qwrite(p->q[0], va, n);
+ break;
+
+ default:
+ panic("pipewrite");
+ }
+
+ poperror();
+ return n;
+}
+
+static long
+pipebwrite(Chan *c, Block *bp, ulong offset)
+{
+ long n;
+ Pipe *p;
+
+ USED(offset);
+
+ if(waserror()) {
+ /* avoid notes when pipe is a mounted queue */
+ if((c->flag & CMSG) == 0)
+ postnote(up, 1, "sys: write on closed pipe", NUser);
+ nexterror();
+ }
+
+ p = c->aux;
+ switch(NETTYPE(c->qid.path)){
+ case Qdata0:
+ n = qbwrite(p->q[1], bp);
+ break;
+
+ case Qdata1:
+ n = qbwrite(p->q[0], bp);
+ break;
+
+ default:
+ n = 0;
+ panic("pipebwrite");
+ }
+
+ poperror();
+ return n;
+}
+
+Dev pipedevtab = {
+ '|',
+ "pipe",
+
+ devreset,
+ pipeinit,
+ devshutdown,
+ pipeattach,
+ pipewalk,
+ pipestat,
+ pipeopen,
+ devcreate,
+ pipeclose,
+ piperead,
+ pipebread,
+ pipewrite,
+ pipebwrite,
+ devremove,
+ devwstat,
+};
diff --git a/sys/src/cmd/unix/drawterm/kern/devroot.c b/sys/src/cmd/unix/drawterm/kern/devroot.c
new file mode 100755
index 000000000..db081ff1f
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devroot.c
@@ -0,0 +1,299 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+enum
+{
+ Qdir = 0,
+ Qboot = 0x1000,
+ Qmnt = 0x2000,
+ Qfactotum,
+
+ Nrootfiles = 32,
+ Nbootfiles = 32,
+ Nmntfiles = 2,
+};
+
+typedef struct Dirlist Dirlist;
+struct Dirlist
+{
+ uint base;
+ Dirtab *dir;
+ uchar **data;
+ int ndir;
+ int mdir;
+};
+
+static Dirtab rootdir[Nrootfiles] = {
+ "#/", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
+ "boot", {Qboot, 0, QTDIR}, 0, DMDIR|0555,
+ "mnt", {Qmnt, 0, QTDIR}, 0, DMDIR|0555,
+};
+static uchar *rootdata[Nrootfiles];
+static Dirlist rootlist =
+{
+ 0,
+ rootdir,
+ rootdata,
+ 3,
+ Nrootfiles
+};
+
+static Dirtab bootdir[Nbootfiles] = {
+ "boot", {Qboot, 0, QTDIR}, 0, DMDIR|0555,
+};
+static uchar *bootdata[Nbootfiles];
+static Dirlist bootlist =
+{
+ Qboot,
+ bootdir,
+ bootdata,
+ 1,
+ Nbootfiles
+};
+
+static uchar *mntdata[Nmntfiles];
+static Dirtab mntdir[Nmntfiles] = {
+ "mnt", {Qmnt, 0, QTDIR}, 0, DMDIR|0555,
+ "factotum", {Qfactotum, 0, QTDIR}, 0, DMDIR|0555,
+};
+static Dirlist mntlist =
+{
+ Qmnt,
+ mntdir,
+ mntdata,
+ 2,
+ Nmntfiles
+};
+
+/*
+ * add a file to the list
+ */
+static void
+addlist(Dirlist *l, char *name, uchar *contents, ulong len, int perm)
+{
+ Dirtab *d;
+
+ if(l->ndir >= l->mdir)
+ panic("too many root files");
+ l->data[l->ndir] = contents;
+ d = &l->dir[l->ndir];
+ strcpy(d->name, name);
+ d->length = len;
+ d->perm = perm;
+ d->qid.type = 0;
+ d->qid.vers = 0;
+ d->qid.path = ++l->ndir + l->base;
+ if(perm & DMDIR)
+ d->qid.type |= QTDIR;
+}
+
+/*
+ * add a root file
+ */
+void
+addbootfile(char *name, uchar *contents, ulong len)
+{
+ addlist(&bootlist, name, contents, len, 0555);
+}
+
+/*
+ * add a root directory
+ */
+static void
+addrootdir(char *name)
+{
+ addlist(&rootlist, name, nil, 0, DMDIR|0555);
+}
+
+static void
+rootreset(void)
+{
+ addrootdir("bin");
+ addrootdir("dev");
+ addrootdir("env");
+ addrootdir("fd");
+ addrootdir("net");
+ addrootdir("net.alt");
+ addrootdir("proc");
+ addrootdir("root");
+ addrootdir("srv");
+}
+
+static Chan*
+rootattach(char *spec)
+{
+ return devattach('/', spec);
+}
+
+static int
+rootgen(Chan *c, char *name, Dirtab *dirt, int ndirt, int s, Dir *dp)
+{
+ int t;
+ Dirtab *d;
+ Dirlist *l;
+
+ USED(dirt);
+ USED(ndirt);
+
+ switch((int)c->qid.path){
+ case Qdir:
+ if(s == DEVDOTDOT){
+ Qid tqiddir = {Qdir, 0, QTDIR};
+ devdir(c, tqiddir, "#/", 0, eve, 0555, dp);
+ return 1;
+ }
+ return devgen(c, name, rootlist.dir, rootlist.ndir, s, dp);
+ case Qmnt:
+ if(s == DEVDOTDOT){
+ Qid tqiddir = {Qdir, 0, QTDIR};
+ devdir(c, tqiddir, "#/", 0, eve, 0555, dp);
+ return 1;
+ }
+ return devgen(c, name, mntlist.dir, mntlist.ndir, s, dp);
+ case Qboot:
+ if(s == DEVDOTDOT){
+ Qid tqiddir = {Qdir, 0, QTDIR};
+ devdir(c, tqiddir, "#/", 0, eve, 0555, dp);
+ return 1;
+ }
+ return devgen(c, name, bootlist.dir, bootlist.ndir, s, dp);
+ default:
+ if(s == DEVDOTDOT){
+ Qid tqiddir = {Qdir, 0, QTDIR};
+ tqiddir.path = c->qid.path&0xF000;
+ devdir(c, tqiddir, "#/", 0, eve, 0555, dp);
+ return 1;
+ }
+ if(s != 0)
+ return -1;
+ switch((int)c->qid.path & 0xF000){
+ case Qdir:
+ t = c->qid.path-1;
+ l = &rootlist;
+ break;
+ case Qboot:
+ t = c->qid.path - Qboot - 1;
+ l = &bootlist;
+ break;
+ case Qmnt:
+ t = c->qid.path - Qmnt - 1;
+ l = &mntlist;
+ break;
+ default:
+ return -1;
+ }
+ if(t >= l->ndir)
+ return -1;
+if(t < 0){
+print("rootgen %llud %d %d\n", c->qid.path, s, t);
+panic("whoops");
+}
+ d = &l->dir[t];
+ devdir(c, d->qid, d->name, d->length, eve, d->perm, dp);
+ return 1;
+ }
+ return -1;
+}
+
+static Walkqid*
+rootwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, nil, 0, rootgen);
+}
+
+static int
+rootstat(Chan *c, uchar *dp, int n)
+{
+ return devstat(c, dp, n, nil, 0, rootgen);
+}
+
+static Chan*
+rootopen(Chan *c, int omode)
+{
+ return devopen(c, omode, nil, 0, devgen);
+}
+
+/*
+ * sysremove() knows this is a nop
+ */
+static void
+rootclose(Chan *c)
+{
+ USED(c);
+}
+
+static long
+rootread(Chan *c, void *buf, long n, vlong off)
+{
+ ulong t;
+ Dirtab *d;
+ Dirlist *l;
+ uchar *data;
+ ulong offset = off;
+
+ t = c->qid.path;
+ switch(t){
+ case Qdir:
+ case Qboot:
+ case Qmnt:
+ return devdirread(c, buf, n, nil, 0, rootgen);
+ }
+
+ if(t&Qboot)
+ l = &bootlist;
+ else if(t&Qmnt)
+ l = &mntlist;
+ else
+ l = &bootlist;
+ t &= 0xFFF;
+ t--;
+
+ if(t >= l->ndir)
+ error(Egreg);
+
+ d = &l->dir[t];
+ data = l->data[t];
+ if(offset >= d->length)
+ return 0;
+ if(offset+n > d->length)
+ n = d->length - offset;
+ memmove(buf, data+offset, n);
+ return n;
+}
+
+static long
+rootwrite(Chan *c, void *v, long n, vlong o)
+{
+ USED(c);
+ USED(v);
+ USED(n);
+ USED(o);
+
+ error(Egreg);
+ return 0;
+}
+
+Dev rootdevtab = {
+ '/',
+ "root",
+
+ rootreset,
+ devinit,
+ devshutdown,
+ rootattach,
+ rootwalk,
+ rootstat,
+ rootopen,
+ devcreate,
+ rootclose,
+ rootread,
+ devbread,
+ rootwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};
+
diff --git a/sys/src/cmd/unix/drawterm/kern/devssl.c b/sys/src/cmd/unix/drawterm/kern/devssl.c
new file mode 100755
index 000000000..3ad021f9f
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devssl.c
@@ -0,0 +1,1517 @@
+/*
+ * devssl - secure sockets layer
+ */
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "libsec.h"
+
+#define NOSPOOKS 1
+
+typedef struct OneWay OneWay;
+struct OneWay
+{
+ QLock q;
+ QLock ctlq;
+
+ void *state; /* encryption state */
+ int slen; /* hash data length */
+ uchar *secret; /* secret */
+ ulong mid; /* message id */
+};
+
+enum
+{
+ /* connection states */
+ Sincomplete= 0,
+ Sclear= 1,
+ Sencrypting= 2,
+ Sdigesting= 4,
+ Sdigenc= Sencrypting|Sdigesting,
+
+ /* encryption algorithms */
+ Noencryption= 0,
+ DESCBC= 1,
+ DESECB= 2,
+ RC4= 3
+};
+
+typedef struct Dstate Dstate;
+struct Dstate
+{
+ Chan *c; /* io channel */
+ uchar state; /* state of connection */
+ int ref; /* serialized by dslock for atomic destroy */
+
+ uchar encryptalg; /* encryption algorithm */
+ ushort blocklen; /* blocking length */
+
+ ushort diglen; /* length of digest */
+ DigestState *(*hf)(uchar*, ulong, uchar*, DigestState*); /* hash func */
+
+ /* for SSL format */
+ int max; /* maximum unpadded data per msg */
+ int maxpad; /* maximum padded data per msg */
+
+ /* input side */
+ OneWay in;
+ Block *processed;
+ Block *unprocessed;
+
+ /* output side */
+ OneWay out;
+
+ /* protections */
+ char *user;
+ int perm;
+};
+
+enum
+{
+ Maxdmsg= 1<<16,
+ Maxdstate= 128, /* must be a power of 2 */
+};
+
+Lock dslock;
+int dshiwat;
+char *dsname[Maxdstate];
+Dstate *dstate[Maxdstate];
+char *encalgs;
+char *hashalgs;
+
+enum{
+ Qtopdir = 1, /* top level directory */
+ Qprotodir,
+ Qclonus,
+ Qconvdir, /* directory for a conversation */
+ Qdata,
+ Qctl,
+ Qsecretin,
+ Qsecretout,
+ Qencalgs,
+ Qhashalgs,
+};
+
+#define TYPE(x) ((x).path & 0xf)
+#define CONV(x) (((x).path >> 5)&(Maxdstate-1))
+#define QID(c, y) (((c)<<5) | (y))
+
+static void ensure(Dstate*, Block**, int);
+static void consume(Block**, uchar*, int);
+static void setsecret(OneWay*, uchar*, int);
+static Block* encryptb(Dstate*, Block*, int);
+static Block* decryptb(Dstate*, Block*);
+static Block* digestb(Dstate*, Block*, int);
+static void checkdigestb(Dstate*, Block*);
+static Chan* buftochan(char*);
+static void sslhangup(Dstate*);
+static Dstate* dsclone(Chan *c);
+static void dsnew(Chan *c, Dstate **);
+static long sslput(Dstate *s, Block * volatile b);
+
+char *sslnames[] = {
+ /* unused */ 0,
+ /* topdir */ 0,
+ /* protodir */ 0,
+ "clone",
+ /* convdir */ 0,
+ "data",
+ "ctl",
+ "secretin",
+ "secretout",
+ "encalgs",
+ "hashalgs",
+};
+
+static int
+sslgen(Chan *c, char *n, Dirtab *d, int nd, int s, Dir *dp)
+{
+ Qid q;
+ Dstate *ds;
+ char name[16], *p, *nm;
+ int ft;
+
+ USED(n);
+ USED(nd);
+ USED(d);
+
+ q.type = QTFILE;
+ q.vers = 0;
+
+ ft = TYPE(c->qid);
+ switch(ft) {
+ case Qtopdir:
+ if(s == DEVDOTDOT){
+ q.path = QID(0, Qtopdir);
+ q.type = QTDIR;
+ devdir(c, q, "#D", 0, eve, 0555, dp);
+ return 1;
+ }
+ if(s > 0)
+ return -1;
+ q.path = QID(0, Qprotodir);
+ q.type = QTDIR;
+ devdir(c, q, "ssl", 0, eve, 0555, dp);
+ return 1;
+ case Qprotodir:
+ if(s == DEVDOTDOT){
+ q.path = QID(0, Qtopdir);
+ q.type = QTDIR;
+ devdir(c, q, ".", 0, eve, 0555, dp);
+ return 1;
+ }
+ if(s < dshiwat) {
+ q.path = QID(s, Qconvdir);
+ q.type = QTDIR;
+ ds = dstate[s];
+ if(ds != 0)
+ nm = ds->user;
+ else
+ nm = eve;
+ if(dsname[s] == nil){
+ sprint(name, "%d", s);
+ kstrdup(&dsname[s], name);
+ }
+ devdir(c, q, dsname[s], 0, nm, 0555, dp);
+ return 1;
+ }
+ if(s > dshiwat)
+ return -1;
+ q.path = QID(0, Qclonus);
+ devdir(c, q, "clone", 0, eve, 0555, dp);
+ return 1;
+ case Qconvdir:
+ if(s == DEVDOTDOT){
+ q.path = QID(0, Qprotodir);
+ q.type = QTDIR;
+ devdir(c, q, "ssl", 0, eve, 0555, dp);
+ return 1;
+ }
+ ds = dstate[CONV(c->qid)];
+ if(ds != 0)
+ nm = ds->user;
+ else
+ nm = eve;
+ switch(s) {
+ default:
+ return -1;
+ case 0:
+ q.path = QID(CONV(c->qid), Qctl);
+ p = "ctl";
+ break;
+ case 1:
+ q.path = QID(CONV(c->qid), Qdata);
+ p = "data";
+ break;
+ case 2:
+ q.path = QID(CONV(c->qid), Qsecretin);
+ p = "secretin";
+ break;
+ case 3:
+ q.path = QID(CONV(c->qid), Qsecretout);
+ p = "secretout";
+ break;
+ case 4:
+ q.path = QID(CONV(c->qid), Qencalgs);
+ p = "encalgs";
+ break;
+ case 5:
+ q.path = QID(CONV(c->qid), Qhashalgs);
+ p = "hashalgs";
+ break;
+ }
+ devdir(c, q, p, 0, nm, 0660, dp);
+ return 1;
+ case Qclonus:
+ devdir(c, c->qid, sslnames[TYPE(c->qid)], 0, eve, 0555, dp);
+ return 1;
+ default:
+ ds = dstate[CONV(c->qid)];
+ if(ds != 0)
+ nm = ds->user;
+ else
+ nm = eve;
+ devdir(c, c->qid, sslnames[TYPE(c->qid)], 0, nm, 0660, dp);
+ return 1;
+ }
+ return -1;
+}
+
+static Chan*
+sslattach(char *spec)
+{
+ Chan *c;
+
+ c = devattach('D', spec);
+ c->qid.path = QID(0, Qtopdir);
+ c->qid.vers = 0;
+ c->qid.type = QTDIR;
+ return c;
+}
+
+static Walkqid*
+sslwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, nil, 0, sslgen);
+}
+
+static int
+sslstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, nil, 0, sslgen);
+}
+
+static Chan*
+sslopen(Chan *c, int omode)
+{
+ Dstate *s, **pp;
+ int perm;
+ int ft;
+
+ perm = 0;
+ omode &= 3;
+ switch(omode) {
+ case OREAD:
+ perm = 4;
+ break;
+ case OWRITE:
+ perm = 2;
+ break;
+ case ORDWR:
+ perm = 6;
+ break;
+ }
+
+ ft = TYPE(c->qid);
+ switch(ft) {
+ default:
+ panic("sslopen");
+ case Qtopdir:
+ case Qprotodir:
+ case Qconvdir:
+ if(omode != OREAD)
+ error(Eperm);
+ break;
+ case Qclonus:
+ s = dsclone(c);
+ if(s == 0)
+ error(Enodev);
+ break;
+ case Qctl:
+ case Qdata:
+ case Qsecretin:
+ case Qsecretout:
+ if(waserror()) {
+ unlock(&dslock);
+ nexterror();
+ }
+ lock(&dslock);
+ pp = &dstate[CONV(c->qid)];
+ s = *pp;
+ if(s == 0)
+ dsnew(c, pp);
+ else {
+ if((perm & (s->perm>>6)) != perm
+ && (strcmp(up->user, s->user) != 0
+ || (perm & s->perm) != perm))
+ error(Eperm);
+
+ s->ref++;
+ }
+ unlock(&dslock);
+ poperror();
+ break;
+ case Qencalgs:
+ case Qhashalgs:
+ if(omode != OREAD)
+ error(Eperm);
+ break;
+ }
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+}
+
+static int
+sslwstat(Chan *c, uchar *db, int n)
+{
+ Dir *dir;
+ Dstate *s;
+ int m;
+
+ s = dstate[CONV(c->qid)];
+ if(s == 0)
+ error(Ebadusefd);
+ if(strcmp(s->user, up->user) != 0)
+ error(Eperm);
+
+ dir = smalloc(sizeof(Dir)+n);
+ m = convM2D(db, n, &dir[0], (char*)&dir[1]);
+ if(m == 0){
+ free(dir);
+ error(Eshortstat);
+ }
+
+ if(!emptystr(dir->uid))
+ kstrdup(&s->user, dir->uid);
+ if(dir->mode != ~0)
+ s->perm = dir->mode;
+
+ free(dir);
+ return m;
+}
+
+static void
+sslclose(Chan *c)
+{
+ Dstate *s;
+ int ft;
+
+ ft = TYPE(c->qid);
+ switch(ft) {
+ case Qctl:
+ case Qdata:
+ case Qsecretin:
+ case Qsecretout:
+ if((c->flag & COPEN) == 0)
+ break;
+
+ s = dstate[CONV(c->qid)];
+ if(s == 0)
+ break;
+
+ lock(&dslock);
+ if(--s->ref > 0) {
+ unlock(&dslock);
+ break;
+ }
+ dstate[CONV(c->qid)] = 0;
+ unlock(&dslock);
+
+ if(s->user != nil)
+ free(s->user);
+ sslhangup(s);
+ if(s->c)
+ cclose(s->c);
+ if(s->in.secret)
+ free(s->in.secret);
+ if(s->out.secret)
+ free(s->out.secret);
+ if(s->in.state)
+ free(s->in.state);
+ if(s->out.state)
+ free(s->out.state);
+ free(s);
+
+ }
+}
+
+/*
+ * make sure we have at least 'n' bytes in list 'l'
+ */
+static void
+ensure(Dstate *s, Block **l, int n)
+{
+ int sofar, i;
+ Block *b, *bl;
+
+ sofar = 0;
+ for(b = *l; b; b = b->next){
+ sofar += BLEN(b);
+ if(sofar >= n)
+ return;
+ l = &b->next;
+ }
+
+ while(sofar < n){
+ bl = devtab[s->c->type]->bread(s->c, Maxdmsg, 0);
+ if(bl == 0)
+ nexterror();
+ *l = bl;
+ i = 0;
+ for(b = bl; b; b = b->next){
+ i += BLEN(b);
+ l = &b->next;
+ }
+ if(i == 0)
+ error(Ehungup);
+ sofar += i;
+ }
+}
+
+/*
+ * copy 'n' bytes from 'l' into 'p' and free
+ * the bytes in 'l'
+ */
+static void
+consume(Block **l, uchar *p, int n)
+{
+ Block *b;
+ int i;
+
+ for(; *l && n > 0; n -= i){
+ b = *l;
+ i = BLEN(b);
+ if(i > n)
+ i = n;
+ memmove(p, b->rp, i);
+ b->rp += i;
+ p += i;
+ if(BLEN(b) < 0)
+ panic("consume");
+ if(BLEN(b))
+ break;
+ *l = b->next;
+ freeb(b);
+ }
+}
+
+/*
+ * give back n bytes
+static void
+regurgitate(Dstate *s, uchar *p, int n)
+{
+ Block *b;
+
+ if(n <= 0)
+ return;
+ b = s->unprocessed;
+ if(s->unprocessed == nil || b->rp - b->base < n) {
+ b = allocb(n);
+ memmove(b->wp, p, n);
+ b->wp += n;
+ b->next = s->unprocessed;
+ s->unprocessed = b;
+ } else {
+ b->rp -= n;
+ memmove(b->rp, p, n);
+ }
+}
+ */
+
+/*
+ * remove at most n bytes from the queue, if discard is set
+ * dump the remainder
+ */
+static Block*
+qtake(Block **l, int n, int discard)
+{
+ Block *nb, *b, *first;
+ int i;
+
+ first = *l;
+ for(b = first; b; b = b->next){
+ i = BLEN(b);
+ if(i == n){
+ if(discard){
+ freeblist(b->next);
+ *l = 0;
+ } else
+ *l = b->next;
+ b->next = 0;
+ return first;
+ } else if(i > n){
+ i -= n;
+ if(discard){
+ freeblist(b->next);
+ b->wp -= i;
+ *l = 0;
+ } else {
+ nb = allocb(i);
+ memmove(nb->wp, b->rp+n, i);
+ nb->wp += i;
+ b->wp -= i;
+ nb->next = b->next;
+ *l = nb;
+ }
+ b->next = 0;
+ if(BLEN(b) < 0)
+ panic("qtake");
+ return first;
+ } else
+ n -= i;
+ if(BLEN(b) < 0)
+ panic("qtake");
+ }
+ *l = 0;
+ return first;
+}
+
+/*
+ * We can't let Eintr's lose data since the program
+ * doing the read may be able to handle it. The only
+ * places Eintr is possible is during the read's in consume.
+ * Therefore, we make sure we can always put back the bytes
+ * consumed before the last ensure.
+ */
+static Block*
+sslbread(Chan *c, long n, ulong o)
+{
+ Dstate * volatile s;
+ Block *b;
+ uchar consumed[3], *p;
+ int toconsume;
+ int len, pad;
+
+ USED(o);
+ s = dstate[CONV(c->qid)];
+ if(s == 0)
+ panic("sslbread");
+ if(s->state == Sincomplete)
+ error(Ebadusefd);
+
+ qlock(&s->in.q);
+ if(waserror()){
+ qunlock(&s->in.q);
+ nexterror();
+ }
+
+ if(s->processed == 0){
+ /*
+ * Read in the whole message. Until we've got it all,
+ * it stays on s->unprocessed, so that if we get Eintr,
+ * we'll pick up where we left off.
+ */
+ ensure(s, &s->unprocessed, 3);
+ s->unprocessed = pullupblock(s->unprocessed, 2);
+ p = s->unprocessed->rp;
+ if(p[0] & 0x80){
+ len = ((p[0] & 0x7f)<<8) | p[1];
+ ensure(s, &s->unprocessed, len);
+ pad = 0;
+ toconsume = 2;
+ } else {
+ s->unprocessed = pullupblock(s->unprocessed, 3);
+ len = ((p[0] & 0x3f)<<8) | p[1];
+ pad = p[2];
+ if(pad > len){
+ print("pad %d buf len %d\n", pad, len);
+ error("bad pad in ssl message");
+ }
+ toconsume = 3;
+ }
+ ensure(s, &s->unprocessed, toconsume+len);
+
+ /* skip header */
+ consume(&s->unprocessed, consumed, toconsume);
+
+ /* grab the next message and decode/decrypt it */
+ b = qtake(&s->unprocessed, len, 0);
+
+ if(blocklen(b) != len)
+ print("devssl: sslbread got wrong count %d != %d", blocklen(b), len);
+
+ if(waserror()){
+ qunlock(&s->in.ctlq);
+ if(b != nil)
+ freeb(b);
+ nexterror();
+ }
+ qlock(&s->in.ctlq);
+ switch(s->state){
+ case Sencrypting:
+ if(b == nil)
+ error("ssl message too short (encrypting)");
+ b = decryptb(s, b);
+ break;
+ case Sdigesting:
+ b = pullupblock(b, s->diglen);
+ if(b == nil)
+ error("ssl message too short (digesting)");
+ checkdigestb(s, b);
+ pullblock(&b, s->diglen);
+ len -= s->diglen;
+ break;
+ case Sdigenc:
+ b = decryptb(s, b);
+ b = pullupblock(b, s->diglen);
+ if(b == nil)
+ error("ssl message too short (dig+enc)");
+ checkdigestb(s, b);
+ pullblock(&b, s->diglen);
+ len -= s->diglen;
+ break;
+ }
+
+ /* remove pad */
+ if(pad)
+ s->processed = qtake(&b, len - pad, 1);
+ else
+ s->processed = b;
+ b = nil;
+ s->in.mid++;
+ qunlock(&s->in.ctlq);
+ poperror();
+ }
+
+ /* return at most what was asked for */
+ b = qtake(&s->processed, n, 0);
+
+ qunlock(&s->in.q);
+ poperror();
+
+ return b;
+}
+
+static long
+sslread(Chan *c, void *a, long n, vlong off)
+{
+ Block * volatile b;
+ Block *nb;
+ uchar *va;
+ int i;
+ char buf[128];
+ ulong offset = off;
+ int ft;
+
+ if(c->qid.type & QTDIR)
+ return devdirread(c, a, n, 0, 0, sslgen);
+
+ ft = TYPE(c->qid);
+ switch(ft) {
+ default:
+ error(Ebadusefd);
+ case Qctl:
+ ft = CONV(c->qid);
+ sprint(buf, "%d", ft);
+ return readstr(offset, a, n, buf);
+ case Qdata:
+ b = sslbread(c, n, offset);
+ break;
+ case Qencalgs:
+ return readstr(offset, a, n, encalgs);
+ break;
+ case Qhashalgs:
+ return readstr(offset, a, n, hashalgs);
+ break;
+ }
+
+ if(waserror()){
+ freeblist(b);
+ nexterror();
+ }
+
+ n = 0;
+ va = a;
+ for(nb = b; nb; nb = nb->next){
+ i = BLEN(nb);
+ memmove(va+n, nb->rp, i);
+ n += i;
+ }
+
+ freeblist(b);
+ poperror();
+
+ return n;
+}
+
+/*
+ * this algorithm doesn't have to be great since we're just
+ * trying to obscure the block fill
+ */
+static void
+randfill(uchar *buf, int len)
+{
+ while(len-- > 0)
+ *buf++ = fastrand();
+}
+
+static long
+sslbwrite(Chan *c, Block *b, ulong o)
+{
+ Dstate * volatile s;
+ long rv;
+
+ USED(o);
+ s = dstate[CONV(c->qid)];
+ if(s == nil)
+ panic("sslbwrite");
+
+ if(s->state == Sincomplete){
+ freeb(b);
+ error(Ebadusefd);
+ }
+
+ /* lock so split writes won't interleave */
+ if(waserror()){
+ qunlock(&s->out.q);
+ nexterror();
+ }
+ qlock(&s->out.q);
+
+ rv = sslput(s, b);
+
+ poperror();
+ qunlock(&s->out.q);
+
+ return rv;
+}
+
+/*
+ * use SSL record format, add in count, digest and/or encrypt.
+ * the write is interruptable. if it is interrupted, we'll
+ * get out of sync with the far side. not much we can do about
+ * it since we don't know if any bytes have been written.
+ */
+static long
+sslput(Dstate *s, Block * volatile b)
+{
+ Block *nb;
+ int h, n, m, pad, rv;
+ uchar *p;
+ int offset;
+
+ if(waserror()){
+iprint("error: %s\n", up->errstr);
+ if(b != nil)
+ free(b);
+ nexterror();
+ }
+
+ rv = 0;
+ while(b != nil){
+ m = n = BLEN(b);
+ h = s->diglen + 2;
+
+ /* trim to maximum block size */
+ pad = 0;
+ if(m > s->max){
+ m = s->max;
+ } else if(s->blocklen != 1){
+ pad = (m + s->diglen)%s->blocklen;
+ if(pad){
+ if(m > s->maxpad){
+ pad = 0;
+ m = s->maxpad;
+ } else {
+ pad = s->blocklen - pad;
+ h++;
+ }
+ }
+ }
+
+ rv += m;
+ if(m != n){
+ nb = allocb(m + h + pad);
+ memmove(nb->wp + h, b->rp, m);
+ nb->wp += m + h;
+ b->rp += m;
+ } else {
+ /* add header space */
+ nb = padblock(b, h);
+ b = 0;
+ }
+ m += s->diglen;
+
+ /* SSL style count */
+ if(pad){
+ nb = padblock(nb, -pad);
+ randfill(nb->wp, pad);
+ nb->wp += pad;
+ m += pad;
+
+ p = nb->rp;
+ p[0] = (m>>8);
+ p[1] = m;
+ p[2] = pad;
+ offset = 3;
+ } else {
+ p = nb->rp;
+ p[0] = (m>>8) | 0x80;
+ p[1] = m;
+ offset = 2;
+ }
+
+ switch(s->state){
+ case Sencrypting:
+ nb = encryptb(s, nb, offset);
+ break;
+ case Sdigesting:
+ nb = digestb(s, nb, offset);
+ break;
+ case Sdigenc:
+ nb = digestb(s, nb, offset);
+ nb = encryptb(s, nb, offset);
+ break;
+ }
+
+ s->out.mid++;
+
+ m = BLEN(nb);
+ devtab[s->c->type]->bwrite(s->c, nb, s->c->offset);
+ s->c->offset += m;
+ }
+
+ poperror();
+ return rv;
+}
+
+static void
+setsecret(OneWay *w, uchar *secret, int n)
+{
+ if(w->secret)
+ free(w->secret);
+
+ w->secret = smalloc(n);
+ memmove(w->secret, secret, n);
+ w->slen = n;
+}
+
+static void
+initDESkey(OneWay *w)
+{
+ if(w->state){
+ free(w->state);
+ w->state = 0;
+ }
+
+ w->state = smalloc(sizeof(DESstate));
+ if(w->slen >= 16)
+ setupDESstate(w->state, w->secret, w->secret+8);
+ else if(w->slen >= 8)
+ setupDESstate(w->state, w->secret, 0);
+ else
+ error("secret too short");
+}
+
+/*
+ * 40 bit DES is the same as 56 bit DES. However,
+ * 16 bits of the key are masked to zero.
+ */
+static void
+initDESkey_40(OneWay *w)
+{
+ uchar key[8];
+
+ if(w->state){
+ free(w->state);
+ w->state = 0;
+ }
+
+ if(w->slen >= 8){
+ memmove(key, w->secret, 8);
+ key[0] &= 0x0f;
+ key[2] &= 0x0f;
+ key[4] &= 0x0f;
+ key[6] &= 0x0f;
+ }
+
+ w->state = malloc(sizeof(DESstate));
+ if(w->slen >= 16)
+ setupDESstate(w->state, key, w->secret+8);
+ else if(w->slen >= 8)
+ setupDESstate(w->state, key, 0);
+ else
+ error("secret too short");
+}
+
+static void
+initRC4key(OneWay *w)
+{
+ if(w->state){
+ free(w->state);
+ w->state = 0;
+ }
+
+ w->state = smalloc(sizeof(RC4state));
+ setupRC4state(w->state, w->secret, w->slen);
+}
+
+/*
+ * 40 bit RC4 is the same as n-bit RC4. However,
+ * we ignore all but the first 40 bits of the key.
+ */
+static void
+initRC4key_40(OneWay *w)
+{
+ if(w->state){
+ free(w->state);
+ w->state = 0;
+ }
+
+ if(w->slen > 5)
+ w->slen = 5;
+
+ w->state = malloc(sizeof(RC4state));
+ setupRC4state(w->state, w->secret, w->slen);
+}
+
+/*
+ * 128 bit RC4 is the same as n-bit RC4. However,
+ * we ignore all but the first 128 bits of the key.
+ */
+static void
+initRC4key_128(OneWay *w)
+{
+ if(w->state){
+ free(w->state);
+ w->state = 0;
+ }
+
+ if(w->slen > 16)
+ w->slen = 16;
+
+ w->state = malloc(sizeof(RC4state));
+ setupRC4state(w->state, w->secret, w->slen);
+}
+
+
+typedef struct Hashalg Hashalg;
+struct Hashalg
+{
+ char *name;
+ int diglen;
+ DigestState *(*hf)(uchar*, ulong, uchar*, DigestState*);
+};
+
+Hashalg hashtab[] =
+{
+ { "md4", MD4dlen, md4, },
+ { "md5", MD5dlen, md5, },
+ { "sha1", SHA1dlen, sha1, },
+ { "sha", SHA1dlen, sha1, },
+ { 0 }
+};
+
+static int
+parsehashalg(char *p, Dstate *s)
+{
+ Hashalg *ha;
+
+ for(ha = hashtab; ha->name; ha++){
+ if(strcmp(p, ha->name) == 0){
+ s->hf = ha->hf;
+ s->diglen = ha->diglen;
+ s->state &= ~Sclear;
+ s->state |= Sdigesting;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+typedef struct Encalg Encalg;
+struct Encalg
+{
+ char *name;
+ int blocklen;
+ int alg;
+ void (*keyinit)(OneWay*);
+};
+
+#ifdef NOSPOOKS
+Encalg encrypttab[] =
+{
+ { "descbc", 8, DESCBC, initDESkey, }, /* DEPRECATED -- use des_56_cbc */
+ { "desecb", 8, DESECB, initDESkey, }, /* DEPRECATED -- use des_56_ecb */
+ { "des_56_cbc", 8, DESCBC, initDESkey, },
+ { "des_56_ecb", 8, DESECB, initDESkey, },
+ { "des_40_cbc", 8, DESCBC, initDESkey_40, },
+ { "des_40_ecb", 8, DESECB, initDESkey_40, },
+ { "rc4", 1, RC4, initRC4key_40, }, /* DEPRECATED -- use rc4_X */
+ { "rc4_256", 1, RC4, initRC4key, },
+ { "rc4_128", 1, RC4, initRC4key_128, },
+ { "rc4_40", 1, RC4, initRC4key_40, },
+ { 0 }
+};
+#else
+Encalg encrypttab[] =
+{
+ { "des_40_cbc", 8, DESCBC, initDESkey_40, },
+ { "des_40_ecb", 8, DESECB, initDESkey_40, },
+ { "rc4", 1, RC4, initRC4key_40, }, /* DEPRECATED -- use rc4_X */
+ { "rc4_40", 1, RC4, initRC4key_40, },
+ { 0 }
+};
+#endif /* NOSPOOKS */
+
+static int
+parseencryptalg(char *p, Dstate *s)
+{
+ Encalg *ea;
+
+ for(ea = encrypttab; ea->name; ea++){
+ if(strcmp(p, ea->name) == 0){
+ s->encryptalg = ea->alg;
+ s->blocklen = ea->blocklen;
+ (*ea->keyinit)(&s->in);
+ (*ea->keyinit)(&s->out);
+ s->state &= ~Sclear;
+ s->state |= Sencrypting;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static long
+sslwrite(Chan *c, void *a, long n, vlong o)
+{
+ Dstate * volatile s;
+ Block * volatile b;
+ int m, t;
+ char *p, *np, *e, buf[128];
+ uchar *x;
+
+ USED(o);
+ s = dstate[CONV(c->qid)];
+ if(s == 0)
+ panic("sslwrite");
+
+ t = TYPE(c->qid);
+ if(t == Qdata){
+ if(s->state == Sincomplete)
+ error(Ebadusefd);
+
+ /* lock should a write gets split over multiple records */
+ if(waserror()){
+ qunlock(&s->out.q);
+ nexterror();
+ }
+ qlock(&s->out.q);
+ p = a;
+if(0) iprint("write %d %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux\n",
+ n, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
+ e = p + n;
+ do {
+ m = e - p;
+ if(m > s->max)
+ m = s->max;
+
+ b = allocb(m);
+ if(waserror()){
+ freeb(b);
+ nexterror();
+ }
+ memmove(b->wp, p, m);
+ poperror();
+ b->wp += m;
+
+ sslput(s, b);
+
+ p += m;
+ } while(p < e);
+ p = a;
+if(0) iprint("wrote %d %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux\n",
+ n, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
+ poperror();
+ qunlock(&s->out.q);
+ return n;
+ }
+
+ /* mutex with operations using what we're about to change */
+ if(waserror()){
+ qunlock(&s->in.ctlq);
+ qunlock(&s->out.q);
+ nexterror();
+ }
+ qlock(&s->in.ctlq);
+ qlock(&s->out.q);
+
+ switch(t){
+ default:
+ panic("sslwrite");
+ case Qsecretin:
+ setsecret(&s->in, a, n);
+ goto out;
+ case Qsecretout:
+ setsecret(&s->out, a, n);
+ goto out;
+ case Qctl:
+ break;
+ }
+
+ if(n >= sizeof(buf))
+ error("arg too long");
+ strncpy(buf, a, n);
+ buf[n] = 0;
+ p = strchr(buf, '\n');
+ if(p)
+ *p = 0;
+ p = strchr(buf, ' ');
+ if(p)
+ *p++ = 0;
+
+ if(strcmp(buf, "fd") == 0){
+ s->c = buftochan(p);
+
+ /* default is clear (msg delimiters only) */
+ s->state = Sclear;
+ s->blocklen = 1;
+ s->diglen = 0;
+ s->maxpad = s->max = (1<<15) - s->diglen - 1;
+ s->in.mid = 0;
+ s->out.mid = 0;
+ } else if(strcmp(buf, "alg") == 0 && p != 0){
+ s->blocklen = 1;
+ s->diglen = 0;
+
+ if(s->c == 0)
+ error("must set fd before algorithm");
+
+ s->state = Sclear;
+ s->maxpad = s->max = (1<<15) - s->diglen - 1;
+ if(strcmp(p, "clear") == 0){
+ goto out;
+ }
+
+ if(s->in.secret && s->out.secret == 0)
+ setsecret(&s->out, s->in.secret, s->in.slen);
+ if(s->out.secret && s->in.secret == 0)
+ setsecret(&s->in, s->out.secret, s->out.slen);
+ if(s->in.secret == 0 || s->out.secret == 0)
+ error("algorithm but no secret");
+
+ s->hf = 0;
+ s->encryptalg = Noencryption;
+ s->blocklen = 1;
+
+ for(;;){
+ np = strchr(p, ' ');
+ if(np)
+ *np++ = 0;
+
+ if(parsehashalg(p, s) < 0)
+ if(parseencryptalg(p, s) < 0)
+ error("bad algorithm");
+
+ if(np == 0)
+ break;
+ p = np;
+ }
+
+ if(s->hf == 0 && s->encryptalg == Noencryption)
+ error("bad algorithm");
+
+ if(s->blocklen != 1){
+ s->max = (1<<15) - s->diglen - 1;
+ s->max -= s->max % s->blocklen;
+ s->maxpad = (1<<14) - s->diglen - 1;
+ s->maxpad -= s->maxpad % s->blocklen;
+ } else
+ s->maxpad = s->max = (1<<15) - s->diglen - 1;
+ } else if(strcmp(buf, "secretin") == 0 && p != 0) {
+ m = (strlen(p)*3)/2;
+ x = smalloc(m);
+ t = dec64(x, m, p, strlen(p));
+ setsecret(&s->in, x, t);
+ free(x);
+ } else if(strcmp(buf, "secretout") == 0 && p != 0) {
+ m = (strlen(p)*3)/2 + 1;
+ x = smalloc(m);
+ t = dec64(x, m, p, strlen(p));
+ setsecret(&s->out, x, t);
+ free(x);
+ } else
+ error(Ebadarg);
+
+out:
+ qunlock(&s->in.ctlq);
+ qunlock(&s->out.q);
+ poperror();
+ return n;
+}
+
+static void
+sslinit(void)
+{
+ struct Encalg *e;
+ struct Hashalg *h;
+ int n;
+ char *cp;
+
+ n = 1;
+ for(e = encrypttab; e->name != nil; e++)
+ n += strlen(e->name) + 1;
+ cp = encalgs = smalloc(n);
+ for(e = encrypttab;;){
+ strcpy(cp, e->name);
+ cp += strlen(e->name);
+ e++;
+ if(e->name == nil)
+ break;
+ *cp++ = ' ';
+ }
+ *cp = 0;
+
+ n = 1;
+ for(h = hashtab; h->name != nil; h++)
+ n += strlen(h->name) + 1;
+ cp = hashalgs = smalloc(n);
+ for(h = hashtab;;){
+ strcpy(cp, h->name);
+ cp += strlen(h->name);
+ h++;
+ if(h->name == nil)
+ break;
+ *cp++ = ' ';
+ }
+ *cp = 0;
+}
+
+Dev ssldevtab = {
+ 'D',
+ "ssl",
+
+ devreset,
+ sslinit,
+ devshutdown,
+ sslattach,
+ sslwalk,
+ sslstat,
+ sslopen,
+ devcreate,
+ sslclose,
+ sslread,
+ sslbread,
+ sslwrite,
+ sslbwrite,
+ devremove,
+ sslwstat,
+};
+
+static Block*
+encryptb(Dstate *s, Block *b, int offset)
+{
+ uchar *p, *ep, *p2, *ip, *eip;
+ DESstate *ds;
+
+ switch(s->encryptalg){
+ case DESECB:
+ ds = s->out.state;
+ ep = b->rp + BLEN(b);
+ for(p = b->rp + offset; p < ep; p += 8)
+ block_cipher(ds->expanded, p, 0);
+ break;
+ case DESCBC:
+ ds = s->out.state;
+ ep = b->rp + BLEN(b);
+ for(p = b->rp + offset; p < ep; p += 8){
+ p2 = p;
+ ip = ds->ivec;
+ for(eip = ip+8; ip < eip; )
+ *p2++ ^= *ip++;
+ block_cipher(ds->expanded, p, 0);
+ memmove(ds->ivec, p, 8);
+ }
+ break;
+ case RC4:
+ rc4(s->out.state, b->rp + offset, BLEN(b) - offset);
+ break;
+ }
+ return b;
+}
+
+static Block*
+decryptb(Dstate *s, Block *bin)
+{
+ Block *b, **l;
+ uchar *p, *ep, *tp, *ip, *eip;
+ DESstate *ds;
+ uchar tmp[8];
+ int i;
+
+ l = &bin;
+ for(b = bin; b; b = b->next){
+ /* make sure we have a multiple of s->blocklen */
+ if(s->blocklen > 1){
+ i = BLEN(b);
+ if(i % s->blocklen){
+ *l = b = pullupblock(b, i + s->blocklen - (i%s->blocklen));
+ if(b == 0)
+ error("ssl encrypted message too short");
+ }
+ }
+ l = &b->next;
+
+ /* decrypt */
+ switch(s->encryptalg){
+ case DESECB:
+ ds = s->in.state;
+ ep = b->rp + BLEN(b);
+ for(p = b->rp; p < ep; p += 8)
+ block_cipher(ds->expanded, p, 1);
+ break;
+ case DESCBC:
+ ds = s->in.state;
+ ep = b->rp + BLEN(b);
+ for(p = b->rp; p < ep;){
+ memmove(tmp, p, 8);
+ block_cipher(ds->expanded, p, 1);
+ tp = tmp;
+ ip = ds->ivec;
+ for(eip = ip+8; ip < eip; ){
+ *p++ ^= *ip;
+ *ip++ = *tp++;
+ }
+ }
+ break;
+ case RC4:
+ rc4(s->in.state, b->rp, BLEN(b));
+ break;
+ }
+ }
+ return bin;
+}
+
+static Block*
+digestb(Dstate *s, Block *b, int offset)
+{
+ uchar *p;
+ DigestState ss;
+ uchar msgid[4];
+ ulong n, h;
+ OneWay *w;
+
+ w = &s->out;
+
+ memset(&ss, 0, sizeof(ss));
+ h = s->diglen + offset;
+ n = BLEN(b) - h;
+
+ /* hash secret + message */
+ (*s->hf)(w->secret, w->slen, 0, &ss);
+ (*s->hf)(b->rp + h, n, 0, &ss);
+
+ /* hash message id */
+ p = msgid;
+ n = w->mid;
+ *p++ = n>>24;
+ *p++ = n>>16;
+ *p++ = n>>8;
+ *p = n;
+ (*s->hf)(msgid, 4, b->rp + offset, &ss);
+
+ return b;
+}
+
+static void
+checkdigestb(Dstate *s, Block *bin)
+{
+ uchar *p;
+ DigestState ss;
+ uchar msgid[4];
+ int n, h;
+ OneWay *w;
+ uchar digest[128];
+ Block *b;
+
+ w = &s->in;
+
+ memset(&ss, 0, sizeof(ss));
+
+ /* hash secret */
+ (*s->hf)(w->secret, w->slen, 0, &ss);
+
+ /* hash message */
+ h = s->diglen;
+ for(b = bin; b; b = b->next){
+ n = BLEN(b) - h;
+ if(n < 0)
+ panic("checkdigestb");
+ (*s->hf)(b->rp + h, n, 0, &ss);
+ h = 0;
+ }
+
+ /* hash message id */
+ p = msgid;
+ n = w->mid;
+ *p++ = n>>24;
+ *p++ = n>>16;
+ *p++ = n>>8;
+ *p = n;
+ (*s->hf)(msgid, 4, digest, &ss);
+
+ if(memcmp(digest, bin->rp, s->diglen) != 0)
+ error("bad digest");
+}
+
+/* get channel associated with an fd */
+static Chan*
+buftochan(char *p)
+{
+ Chan *c;
+ int fd;
+
+ if(p == 0)
+ error(Ebadarg);
+ fd = strtoul(p, 0, 0);
+ if(fd < 0)
+ error(Ebadarg);
+ c = fdtochan(fd, -1, 0, 1); /* error check and inc ref */
+ if(devtab[c->type] == &ssldevtab){
+ cclose(c);
+ error("cannot ssl encrypt devssl files");
+ }
+ return c;
+}
+
+/* hand up a digest connection */
+static void
+sslhangup(Dstate *s)
+{
+ Block *b;
+
+ qlock(&s->in.q);
+ for(b = s->processed; b; b = s->processed){
+ s->processed = b->next;
+ freeb(b);
+ }
+ if(s->unprocessed){
+ freeb(s->unprocessed);
+ s->unprocessed = 0;
+ }
+ s->state = Sincomplete;
+ qunlock(&s->in.q);
+}
+
+static Dstate*
+dsclone(Chan *ch)
+{
+ int i;
+ Dstate *ret;
+
+ if(waserror()) {
+ unlock(&dslock);
+ nexterror();
+ }
+ lock(&dslock);
+ ret = nil;
+ for(i=0; i<Maxdstate; i++){
+ if(dstate[i] == nil){
+ dsnew(ch, &dstate[i]);
+ ret = dstate[i];
+ break;
+ }
+ }
+ unlock(&dslock);
+ poperror();
+ return ret;
+}
+
+static void
+dsnew(Chan *ch, Dstate **pp)
+{
+ Dstate *s;
+ int t;
+
+ *pp = s = malloc(sizeof(*s));
+ if(!s)
+ error(Enomem);
+ if(pp - dstate >= dshiwat)
+ dshiwat++;
+ memset(s, 0, sizeof(*s));
+ s->state = Sincomplete;
+ s->ref = 1;
+ kstrdup(&s->user, up->user);
+ s->perm = 0660;
+ t = TYPE(ch->qid);
+ if(t == Qclonus)
+ t = Qctl;
+ ch->qid.path = QID(pp - dstate, t);
+ ch->qid.vers = 0;
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/devtab.c b/sys/src/cmd/unix/drawterm/kern/devtab.c
new file mode 100755
index 000000000..e16a188fd
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devtab.c
@@ -0,0 +1,35 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+extern Dev consdevtab;
+extern Dev rootdevtab;
+extern Dev pipedevtab;
+extern Dev ssldevtab;
+extern Dev tlsdevtab;
+extern Dev mousedevtab;
+extern Dev drawdevtab;
+extern Dev ipdevtab;
+extern Dev fsdevtab;
+extern Dev mntdevtab;
+extern Dev lfddevtab;
+extern Dev audiodevtab;
+
+Dev *devtab[] = {
+ &rootdevtab,
+ &consdevtab,
+ &pipedevtab,
+ &ssldevtab,
+ &tlsdevtab,
+ &mousedevtab,
+ &drawdevtab,
+ &ipdevtab,
+ &fsdevtab,
+ &mntdevtab,
+ &lfddevtab,
+ &audiodevtab,
+ 0
+};
+
diff --git a/sys/src/cmd/unix/drawterm/kern/devtls.c b/sys/src/cmd/unix/drawterm/kern/devtls.c
new file mode 100755
index 000000000..6f439a241
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/devtls.c
@@ -0,0 +1,2185 @@
+/*
+ * devtls - record layer for transport layer security 1.0 and secure sockets layer 3.0
+ */
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "libsec.h"
+
+typedef struct OneWay OneWay;
+typedef struct Secret Secret;
+typedef struct TlsRec TlsRec;
+typedef struct TlsErrs TlsErrs;
+
+enum {
+ Statlen= 1024, /* max. length of status or stats message */
+ /* buffer limits */
+ MaxRecLen = 1<<14, /* max payload length of a record layer message */
+ MaxCipherRecLen = MaxRecLen + 2048,
+ RecHdrLen = 5,
+ MaxMacLen = SHA1dlen,
+
+ /* protocol versions we can accept */
+ TLSVersion = 0x0301,
+ SSL3Version = 0x0300,
+ ProtocolVersion = 0x0301, /* maximum version we speak */
+ MinProtoVersion = 0x0300, /* limits on version we accept */
+ MaxProtoVersion = 0x03ff,
+
+ /* connection states */
+ SHandshake = 1 << 0, /* doing handshake */
+ SOpen = 1 << 1, /* application data can be sent */
+ SRClose = 1 << 2, /* remote side has closed down */
+ SLClose = 1 << 3, /* sent a close notify alert */
+ SAlert = 1 << 5, /* sending or sent a fatal alert */
+ SError = 1 << 6, /* some sort of error has occured */
+ SClosed = 1 << 7, /* it is all over */
+
+ /* record types */
+ RChangeCipherSpec = 20,
+ RAlert,
+ RHandshake,
+ RApplication,
+
+ SSL2ClientHello = 1,
+ HSSL2ClientHello = 9, /* local convention; see tlshand.c */
+
+ /* alerts */
+ ECloseNotify = 0,
+ EUnexpectedMessage = 10,
+ EBadRecordMac = 20,
+ EDecryptionFailed = 21,
+ ERecordOverflow = 22,
+ EDecompressionFailure = 30,
+ EHandshakeFailure = 40,
+ ENoCertificate = 41,
+ EBadCertificate = 42,
+ EUnsupportedCertificate = 43,
+ ECertificateRevoked = 44,
+ ECertificateExpired = 45,
+ ECertificateUnknown = 46,
+ EIllegalParameter = 47,
+ EUnknownCa = 48,
+ EAccessDenied = 49,
+ EDecodeError = 50,
+ EDecryptError = 51,
+ EExportRestriction = 60,
+ EProtocolVersion = 70,
+ EInsufficientSecurity = 71,
+ EInternalError = 80,
+ EUserCanceled = 90,
+ ENoRenegotiation = 100,
+
+ EMAX = 256
+};
+
+struct Secret
+{
+ char *encalg; /* name of encryption alg */
+ char *hashalg; /* name of hash alg */
+ int (*enc)(Secret*, uchar*, int);
+ int (*dec)(Secret*, uchar*, int);
+ int (*unpad)(uchar*, int, int);
+ DigestState *(*mac)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+ int block; /* encryption block len, 0 if none */
+ int maclen;
+ void *enckey;
+ uchar mackey[MaxMacLen];
+};
+
+struct OneWay
+{
+ QLock io; /* locks io access */
+ QLock seclock; /* locks secret paramaters */
+ ulong seq;
+ Secret *sec; /* cipher in use */
+ Secret *new; /* cipher waiting for enable */
+};
+
+struct TlsRec
+{
+ Chan *c; /* io channel */
+ int ref; /* serialized by tdlock for atomic destroy */
+ int version; /* version of the protocol we are speaking */
+ char verset; /* version has been set */
+ char opened; /* opened command every issued? */
+ char err[ERRMAX]; /* error message to return to handshake requests */
+ vlong handin; /* bytes communicated by the record layer */
+ vlong handout;
+ vlong datain;
+ vlong dataout;
+
+ Lock statelk;
+ int state;
+ int debug;
+
+ /* record layer mac functions for different protocol versions */
+ void (*packMac)(Secret*, uchar*, uchar*, uchar*, uchar*, int, uchar*);
+
+ /* input side -- protected by in.io */
+ OneWay in;
+ Block *processed; /* next bunch of application data */
+ Block *unprocessed; /* data read from c but not parsed into records */
+
+ /* handshake queue */
+ Lock hqlock; /* protects hqref, alloc & free of handq, hprocessed */
+ int hqref;
+ Queue *handq; /* queue of handshake messages */
+ Block *hprocessed; /* remainder of last block read from handq */
+ QLock hqread; /* protects reads for hprocessed, handq */
+
+ /* output side */
+ OneWay out;
+
+ /* protections */
+ char *user;
+ int perm;
+};
+
+struct TlsErrs{
+ int err;
+ int sslerr;
+ int tlserr;
+ int fatal;
+ char *msg;
+};
+
+static TlsErrs tlserrs[] = {
+ {ECloseNotify, ECloseNotify, ECloseNotify, 0, "close notify"},
+ {EUnexpectedMessage, EUnexpectedMessage, EUnexpectedMessage, 1, "unexpected message"},
+ {EBadRecordMac, EBadRecordMac, EBadRecordMac, 1, "bad record mac"},
+ {EDecryptionFailed, EIllegalParameter, EDecryptionFailed, 1, "decryption failed"},
+ {ERecordOverflow, EIllegalParameter, ERecordOverflow, 1, "record too long"},
+ {EDecompressionFailure, EDecompressionFailure, EDecompressionFailure, 1, "decompression failed"},
+ {EHandshakeFailure, EHandshakeFailure, EHandshakeFailure, 1, "could not negotiate acceptable security parameters"},
+ {ENoCertificate, ENoCertificate, ECertificateUnknown, 1, "no appropriate certificate available"},
+ {EBadCertificate, EBadCertificate, EBadCertificate, 1, "corrupted or invalid certificate"},
+ {EUnsupportedCertificate, EUnsupportedCertificate, EUnsupportedCertificate, 1, "unsupported certificate type"},
+ {ECertificateRevoked, ECertificateRevoked, ECertificateRevoked, 1, "revoked certificate"},
+ {ECertificateExpired, ECertificateExpired, ECertificateExpired, 1, "expired certificate"},
+ {ECertificateUnknown, ECertificateUnknown, ECertificateUnknown, 1, "unacceptable certificate"},
+ {EIllegalParameter, EIllegalParameter, EIllegalParameter, 1, "illegal parameter"},
+ {EUnknownCa, EHandshakeFailure, EUnknownCa, 1, "unknown certificate authority"},
+ {EAccessDenied, EHandshakeFailure, EAccessDenied, 1, "access denied"},
+ {EDecodeError, EIllegalParameter, EDecodeError, 1, "error decoding message"},
+ {EDecryptError, EIllegalParameter, EDecryptError, 1, "error decrypting message"},
+ {EExportRestriction, EHandshakeFailure, EExportRestriction, 1, "export restriction violated"},
+ {EProtocolVersion, EIllegalParameter, EProtocolVersion, 1, "protocol version not supported"},
+ {EInsufficientSecurity, EHandshakeFailure, EInsufficientSecurity, 1, "stronger security routines required"},
+ {EInternalError, EHandshakeFailure, EInternalError, 1, "internal error"},
+ {EUserCanceled, ECloseNotify, EUserCanceled, 0, "handshake canceled by user"},
+ {ENoRenegotiation, EUnexpectedMessage, ENoRenegotiation, 0, "no renegotiation"},
+};
+
+enum
+{
+ /* max. open tls connections */
+ MaxTlsDevs = 1024
+};
+
+static Lock tdlock;
+static int tdhiwat;
+static int maxtlsdevs = 128;
+static TlsRec **tlsdevs;
+static char **trnames;
+static char *encalgs;
+static char *hashalgs;
+
+enum{
+ Qtopdir = 1, /* top level directory */
+ Qprotodir,
+ Qclonus,
+ Qencalgs,
+ Qhashalgs,
+ Qconvdir, /* directory for a conversation */
+ Qdata,
+ Qctl,
+ Qhand,
+ Qstatus,
+ Qstats,
+};
+
+#define TYPE(x) ((x).path & 0xf)
+#define CONV(x) (((x).path >> 5)&(MaxTlsDevs-1))
+#define QID(c, y) (((c)<<5) | (y))
+
+static void checkstate(TlsRec *, int, int);
+static void ensure(TlsRec*, Block**, int);
+static void consume(Block**, uchar*, int);
+static Chan* buftochan(char*);
+static void tlshangup(TlsRec*);
+static void tlsError(TlsRec*, char *);
+static void alertHand(TlsRec*, char *);
+static TlsRec *newtls(Chan *c);
+static TlsRec *mktlsrec(void);
+static DigestState*sslmac_md5(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s);
+static DigestState*sslmac_sha1(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s);
+static DigestState*nomac(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s);
+static void sslPackMac(Secret *sec, uchar *mackey, uchar *seq, uchar *header, uchar *body, int len, uchar *mac);
+static void tlsPackMac(Secret *sec, uchar *mackey, uchar *seq, uchar *header, uchar *body, int len, uchar *mac);
+static void put64(uchar *p, vlong x);
+static void put32(uchar *p, u32int);
+static void put24(uchar *p, int);
+static void put16(uchar *p, int);
+/* static u32int get32(uchar *p); */
+static int get16(uchar *p);
+static void tlsSetState(TlsRec *tr, int new, int old);
+static void rcvAlert(TlsRec *tr, int err);
+static void sendAlert(TlsRec *tr, int err);
+static void rcvError(TlsRec *tr, int err, char *msg, ...);
+static int rc4enc(Secret *sec, uchar *buf, int n);
+static int des3enc(Secret *sec, uchar *buf, int n);
+static int des3dec(Secret *sec, uchar *buf, int n);
+static int noenc(Secret *sec, uchar *buf, int n);
+static int sslunpad(uchar *buf, int n, int block);
+static int tlsunpad(uchar *buf, int n, int block);
+static void freeSec(Secret *sec);
+static char *tlsstate(int s);
+static void pdump(int, void*, char*);
+
+static char *tlsnames[] = {
+ /* unused */ 0,
+ /* topdir */ 0,
+ /* protodir */ 0,
+ "clone",
+ "encalgs",
+ "hashalgs",
+ /* convdir */ 0,
+ "data",
+ "ctl",
+ "hand",
+ "status",
+ "stats",
+};
+
+static int convdir[] = { Qctl, Qdata, Qhand, Qstatus, Qstats };
+
+static int
+tlsgen(Chan *c, char*unused1, Dirtab *unused2, int unused3, int s, Dir *dp)
+{
+ Qid q;
+ TlsRec *tr;
+ char *name, *nm;
+ int perm, t;
+
+ q.vers = 0;
+ q.type = QTFILE;
+
+ t = TYPE(c->qid);
+ switch(t) {
+ case Qtopdir:
+ if(s == DEVDOTDOT){
+ q.path = QID(0, Qtopdir);
+ q.type = QTDIR;
+ devdir(c, q, "#a", 0, eve, 0555, dp);
+ return 1;
+ }
+ if(s > 0)
+ return -1;
+ q.path = QID(0, Qprotodir);
+ q.type = QTDIR;
+ devdir(c, q, "tls", 0, eve, 0555, dp);
+ return 1;
+ case Qprotodir:
+ if(s == DEVDOTDOT){
+ q.path = QID(0, Qtopdir);
+ q.type = QTDIR;
+ devdir(c, q, ".", 0, eve, 0555, dp);
+ return 1;
+ }
+ if(s < 3){
+ switch(s) {
+ default:
+ return -1;
+ case 0:
+ q.path = QID(0, Qclonus);
+ break;
+ case 1:
+ q.path = QID(0, Qencalgs);
+ break;
+ case 2:
+ q.path = QID(0, Qhashalgs);
+ break;
+ }
+ perm = 0444;
+ if(TYPE(q) == Qclonus)
+ perm = 0555;
+ devdir(c, q, tlsnames[TYPE(q)], 0, eve, perm, dp);
+ return 1;
+ }
+ s -= 3;
+ if(s >= tdhiwat)
+ return -1;
+ q.path = QID(s, Qconvdir);
+ q.type = QTDIR;
+ lock(&tdlock);
+ tr = tlsdevs[s];
+ if(tr != nil)
+ nm = tr->user;
+ else
+ nm = eve;
+ if((name = trnames[s]) == nil){
+ name = trnames[s] = smalloc(16);
+ sprint(name, "%d", s);
+ }
+ devdir(c, q, name, 0, nm, 0555, dp);
+ unlock(&tdlock);
+ return 1;
+ case Qconvdir:
+ if(s == DEVDOTDOT){
+ q.path = QID(0, Qprotodir);
+ q.type = QTDIR;
+ devdir(c, q, "tls", 0, eve, 0555, dp);
+ return 1;
+ }
+ if(s < 0 || s >= nelem(convdir))
+ return -1;
+ lock(&tdlock);
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr != nil){
+ nm = tr->user;
+ perm = tr->perm;
+ }else{
+ perm = 0;
+ nm = eve;
+ }
+ t = convdir[s];
+ if(t == Qstatus || t == Qstats)
+ perm &= 0444;
+ q.path = QID(CONV(c->qid), t);
+ devdir(c, q, tlsnames[t], 0, nm, perm, dp);
+ unlock(&tdlock);
+ return 1;
+ case Qclonus:
+ case Qencalgs:
+ case Qhashalgs:
+ perm = 0444;
+ if(t == Qclonus)
+ perm = 0555;
+ devdir(c, c->qid, tlsnames[t], 0, eve, perm, dp);
+ return 1;
+ default:
+ lock(&tdlock);
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr != nil){
+ nm = tr->user;
+ perm = tr->perm;
+ }else{
+ perm = 0;
+ nm = eve;
+ }
+ if(t == Qstatus || t == Qstats)
+ perm &= 0444;
+ devdir(c, c->qid, tlsnames[t], 0, nm, perm, dp);
+ unlock(&tdlock);
+ return 1;
+ }
+ return -1;
+}
+
+static Chan*
+tlsattach(char *spec)
+{
+ Chan *c;
+
+ c = devattach('a', spec);
+ c->qid.path = QID(0, Qtopdir);
+ c->qid.type = QTDIR;
+ c->qid.vers = 0;
+ return c;
+}
+
+static Walkqid*
+tlswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, nil, 0, tlsgen);
+}
+
+static int
+tlsstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, nil, 0, tlsgen);
+}
+
+static Chan*
+tlsopen(Chan *c, int omode)
+{
+ TlsRec *tr, **pp;
+ int t, perm;
+
+ perm = 0;
+ omode &= 3;
+ switch(omode) {
+ case OREAD:
+ perm = 4;
+ break;
+ case OWRITE:
+ perm = 2;
+ break;
+ case ORDWR:
+ perm = 6;
+ break;
+ }
+
+ t = TYPE(c->qid);
+ switch(t) {
+ default:
+ panic("tlsopen");
+ case Qtopdir:
+ case Qprotodir:
+ case Qconvdir:
+ if(omode != OREAD)
+ error(Eperm);
+ break;
+ case Qclonus:
+ tr = newtls(c);
+ if(tr == nil)
+ error(Enodev);
+ break;
+ case Qctl:
+ case Qdata:
+ case Qhand:
+ case Qstatus:
+ case Qstats:
+ if((t == Qstatus || t == Qstats) && omode != OREAD)
+ error(Eperm);
+ if(waserror()) {
+ unlock(&tdlock);
+ nexterror();
+ }
+ lock(&tdlock);
+ pp = &tlsdevs[CONV(c->qid)];
+ tr = *pp;
+ if(tr == nil)
+ error("must open connection using clone");
+ if((perm & (tr->perm>>6)) != perm
+ && (strcmp(up->user, tr->user) != 0
+ || (perm & tr->perm) != perm))
+ error(Eperm);
+ if(t == Qhand){
+ if(waserror()){
+ unlock(&tr->hqlock);
+ nexterror();
+ }
+ lock(&tr->hqlock);
+ if(tr->handq != nil)
+ error(Einuse);
+ tr->handq = qopen(2 * MaxCipherRecLen, 0, 0, nil);
+ if(tr->handq == nil)
+ error("cannot allocate handshake queue");
+ tr->hqref = 1;
+ unlock(&tr->hqlock);
+ poperror();
+ }
+ tr->ref++;
+ unlock(&tdlock);
+ poperror();
+ break;
+ case Qencalgs:
+ case Qhashalgs:
+ if(omode != OREAD)
+ error(Eperm);
+ break;
+ }
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ c->iounit = qiomaxatomic;
+ return c;
+}
+
+static int
+tlswstat(Chan *c, uchar *dp, int n)
+{
+ Dir *d;
+ TlsRec *tr;
+ int rv;
+
+ d = nil;
+ if(waserror()){
+ free(d);
+ unlock(&tdlock);
+ nexterror();
+ }
+
+ lock(&tdlock);
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr == nil)
+ error(Ebadusefd);
+ if(strcmp(tr->user, up->user) != 0)
+ error(Eperm);
+
+ d = smalloc(n + sizeof *d);
+ rv = convM2D(dp, n, &d[0], (char*) &d[1]);
+ if(rv == 0)
+ error(Eshortstat);
+ if(!emptystr(d->uid))
+ kstrdup(&tr->user, d->uid);
+ if(d->mode != ~0UL)
+ tr->perm = d->mode;
+
+ free(d);
+ poperror();
+ unlock(&tdlock);
+
+ return rv;
+}
+
+static void
+dechandq(TlsRec *tr)
+{
+ lock(&tr->hqlock);
+ if(--tr->hqref == 0){
+ if(tr->handq != nil){
+ qfree(tr->handq);
+ tr->handq = nil;
+ }
+ if(tr->hprocessed != nil){
+ freeb(tr->hprocessed);
+ tr->hprocessed = nil;
+ }
+ }
+ unlock(&tr->hqlock);
+}
+
+static void
+tlsclose(Chan *c)
+{
+ TlsRec *tr;
+ int t;
+
+ t = TYPE(c->qid);
+ switch(t) {
+ case Qctl:
+ case Qdata:
+ case Qhand:
+ case Qstatus:
+ case Qstats:
+ if((c->flag & COPEN) == 0)
+ break;
+
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr == nil)
+ break;
+
+ if(t == Qhand)
+ dechandq(tr);
+
+ lock(&tdlock);
+ if(--tr->ref > 0) {
+ unlock(&tdlock);
+ return;
+ }
+ tlsdevs[CONV(c->qid)] = nil;
+ unlock(&tdlock);
+
+ if(tr->c != nil && !waserror()){
+ checkstate(tr, 0, SOpen|SHandshake|SRClose);
+ sendAlert(tr, ECloseNotify);
+ poperror();
+ }
+ tlshangup(tr);
+ if(tr->c != nil)
+ cclose(tr->c);
+ freeSec(tr->in.sec);
+ freeSec(tr->in.new);
+ freeSec(tr->out.sec);
+ freeSec(tr->out.new);
+ free(tr->user);
+ free(tr);
+ break;
+ }
+}
+
+/*
+ * make sure we have at least 'n' bytes in list 'l'
+ */
+static void
+ensure(TlsRec *s, Block **l, int n)
+{
+ int sofar, i;
+ Block *b, *bl;
+
+ sofar = 0;
+ for(b = *l; b; b = b->next){
+ sofar += BLEN(b);
+ if(sofar >= n)
+ return;
+ l = &b->next;
+ }
+
+ while(sofar < n){
+ bl = devtab[s->c->type]->bread(s->c, MaxCipherRecLen + RecHdrLen, 0);
+ if(bl == 0)
+ error(Ehungup);
+ *l = bl;
+ i = 0;
+ for(b = bl; b; b = b->next){
+ i += BLEN(b);
+ l = &b->next;
+ }
+ if(i == 0)
+ error(Ehungup);
+ sofar += i;
+ }
+if(s->debug) pprint("ensure read %d\n", sofar);
+}
+
+/*
+ * copy 'n' bytes from 'l' into 'p' and free
+ * the bytes in 'l'
+ */
+static void
+consume(Block **l, uchar *p, int n)
+{
+ Block *b;
+ int i;
+
+ for(; *l && n > 0; n -= i){
+ b = *l;
+ i = BLEN(b);
+ if(i > n)
+ i = n;
+ memmove(p, b->rp, i);
+ b->rp += i;
+ p += i;
+ if(BLEN(b) < 0)
+ panic("consume");
+ if(BLEN(b))
+ break;
+ *l = b->next;
+ freeb(b);
+ }
+}
+
+/*
+ * give back n bytes
+ */
+static void
+regurgitate(TlsRec *s, uchar *p, int n)
+{
+ Block *b;
+
+ if(n <= 0)
+ return;
+ b = s->unprocessed;
+ if(s->unprocessed == nil || b->rp - b->base < n) {
+ b = allocb(n);
+ memmove(b->wp, p, n);
+ b->wp += n;
+ b->next = s->unprocessed;
+ s->unprocessed = b;
+ } else {
+ b->rp -= n;
+ memmove(b->rp, p, n);
+ }
+}
+
+/*
+ * remove at most n bytes from the queue
+ */
+static Block*
+qgrab(Block **l, int n)
+{
+ Block *bb, *b;
+ int i;
+
+ b = *l;
+ if(BLEN(b) == n){
+ *l = b->next;
+ b->next = nil;
+ return b;
+ }
+
+ i = 0;
+ for(bb = b; bb != nil && i < n; bb = bb->next)
+ i += BLEN(bb);
+ if(i > n)
+ i = n;
+
+ bb = allocb(i);
+ consume(l, bb->wp, i);
+ bb->wp += i;
+ return bb;
+}
+
+static void
+tlsclosed(TlsRec *tr, int new)
+{
+ lock(&tr->statelk);
+ if(tr->state == SOpen || tr->state == SHandshake)
+ tr->state = new;
+ else if((new | tr->state) == (SRClose|SLClose))
+ tr->state = SClosed;
+ unlock(&tr->statelk);
+ alertHand(tr, "close notify");
+}
+
+/*
+ * read and process one tls record layer message
+ * must be called with tr->in.io held
+ * We can't let Eintrs lose data, since doing so will get
+ * us out of sync with the sender and break the reliablity
+ * of the channel. Eintr only happens during the reads in
+ * consume. Therefore we put back any bytes consumed before
+ * the last call to ensure.
+ */
+static void
+tlsrecread(TlsRec *tr)
+{
+ OneWay *volatile in;
+ Block *volatile b;
+ uchar *p, seq[8], header[RecHdrLen], hmac[MD5dlen];
+ int volatile nconsumed;
+ int len, type, ver, unpad_len;
+
+ nconsumed = 0;
+ if(waserror()){
+ if(strcmp(up->errstr, Eintr) == 0 && !waserror()){
+ regurgitate(tr, header, nconsumed);
+ poperror();
+ }else
+ tlsError(tr, "channel error");
+ nexterror();
+ }
+ ensure(tr, &tr->unprocessed, RecHdrLen);
+ consume(&tr->unprocessed, header, RecHdrLen);
+if(tr->debug)pprint("consumed %d header\n", RecHdrLen);
+ nconsumed = RecHdrLen;
+
+ if((tr->handin == 0) && (header[0] & 0x80)){
+ /* Cope with an SSL3 ClientHello expressed in SSL2 record format.
+ This is sent by some clients that we must interoperate
+ with, such as Java's JSSE and Microsoft's Internet Explorer. */
+ len = (get16(header) & ~0x8000) - 3;
+ type = header[2];
+ ver = get16(header + 3);
+ if(type != SSL2ClientHello || len < 22)
+ rcvError(tr, EProtocolVersion, "invalid initial SSL2-like message");
+ }else{ /* normal SSL3 record format */
+ type = header[0];
+ ver = get16(header+1);
+ len = get16(header+3);
+ }
+ if(ver != tr->version && (tr->verset || ver < MinProtoVersion || ver > MaxProtoVersion))
+ rcvError(tr, EProtocolVersion, "devtls expected ver=%x%s, saw (len=%d) type=%x ver=%x '%.12s'",
+ tr->version, tr->verset?"/set":"", len, type, ver, (char*)header);
+ if(len > MaxCipherRecLen || len < 0)
+ rcvError(tr, ERecordOverflow, "record message too long %d", len);
+ ensure(tr, &tr->unprocessed, len);
+ nconsumed = 0;
+ poperror();
+
+ /*
+ * If an Eintr happens after this, we'll get out of sync.
+ * Make sure nothing we call can sleep.
+ * Errors are ok, as they kill the connection.
+ * Luckily, allocb won't sleep, it'll just error out.
+ */
+ b = nil;
+ if(waserror()){
+ if(b != nil)
+ freeb(b);
+ tlsError(tr, "channel error");
+ nexterror();
+ }
+ b = qgrab(&tr->unprocessed, len);
+if(tr->debug) pprint("consumed unprocessed %d\n", len);
+
+ in = &tr->in;
+ if(waserror()){
+ qunlock(&in->seclock);
+ nexterror();
+ }
+ qlock(&in->seclock);
+ p = b->rp;
+ if(in->sec != nil) {
+ /* to avoid Canvel-Hiltgen-Vaudenay-Vuagnoux attack, all errors here
+ should look alike, including timing of the response. */
+ unpad_len = (*in->sec->dec)(in->sec, p, len);
+ if(unpad_len >= in->sec->maclen)
+ len = unpad_len - in->sec->maclen;
+if(tr->debug) pprint("decrypted %d\n", unpad_len);
+if(tr->debug) pdump(unpad_len, p, "decrypted:");
+
+ /* update length */
+ put16(header+3, len);
+ put64(seq, in->seq);
+ in->seq++;
+ (*tr->packMac)(in->sec, in->sec->mackey, seq, header, p, len, hmac);
+ if(unpad_len < in->sec->maclen)
+ rcvError(tr, EBadRecordMac, "short record mac");
+ if(memcmp(hmac, p+len, in->sec->maclen) != 0)
+ rcvError(tr, EBadRecordMac, "record mac mismatch");
+ b->wp = b->rp + len;
+ }
+ qunlock(&in->seclock);
+ poperror();
+ if(len < 0)
+ rcvError(tr, EDecodeError, "runt record message");
+
+ switch(type) {
+ default:
+ rcvError(tr, EIllegalParameter, "invalid record message 0x%x", type);
+ break;
+ case RChangeCipherSpec:
+ if(len != 1 || p[0] != 1)
+ rcvError(tr, EDecodeError, "invalid change cipher spec");
+ qlock(&in->seclock);
+ if(in->new == nil){
+ qunlock(&in->seclock);
+ rcvError(tr, EUnexpectedMessage, "unexpected change cipher spec");
+ }
+ freeSec(in->sec);
+ in->sec = in->new;
+ in->new = nil;
+ in->seq = 0;
+ qunlock(&in->seclock);
+ break;
+ case RAlert:
+ if(len != 2)
+ rcvError(tr, EDecodeError, "invalid alert");
+ if(p[0] == 2)
+ rcvAlert(tr, p[1]);
+ if(p[0] != 1)
+ rcvError(tr, EIllegalParameter, "invalid alert fatal code");
+
+ /*
+ * propate non-fatal alerts to handshaker
+ */
+ if(p[1] == ECloseNotify) {
+ tlsclosed(tr, SRClose);
+ if(tr->opened)
+ error("tls hungup");
+ error("close notify");
+ }
+ if(p[1] == ENoRenegotiation)
+ alertHand(tr, "no renegotiation");
+ else if(p[1] == EUserCanceled)
+ alertHand(tr, "handshake canceled by user");
+ else
+ rcvError(tr, EIllegalParameter, "invalid alert code");
+ break;
+ case RHandshake:
+ /*
+ * don't worry about dropping the block
+ * qbwrite always queues even if flow controlled and interrupted.
+ *
+ * if there isn't any handshaker, ignore the request,
+ * but notify the other side we are doing so.
+ */
+ lock(&tr->hqlock);
+ if(tr->handq != nil){
+ tr->hqref++;
+ unlock(&tr->hqlock);
+ if(waserror()){
+ dechandq(tr);
+ nexterror();
+ }
+ b = padblock(b, 1);
+ *b->rp = RHandshake;
+ qbwrite(tr->handq, b);
+ b = nil;
+ poperror();
+ dechandq(tr);
+ }else{
+ unlock(&tr->hqlock);
+ if(tr->verset && tr->version != SSL3Version && !waserror()){
+ sendAlert(tr, ENoRenegotiation);
+ poperror();
+ }
+ }
+ break;
+ case SSL2ClientHello:
+ lock(&tr->hqlock);
+ if(tr->handq != nil){
+ tr->hqref++;
+ unlock(&tr->hqlock);
+ if(waserror()){
+ dechandq(tr);
+ nexterror();
+ }
+ /* Pass the SSL2 format data, so that the handshake code can compute
+ the correct checksums. HSSL2ClientHello = HandshakeType 9 is
+ unused in RFC2246. */
+ b = padblock(b, 8);
+ b->rp[0] = RHandshake;
+ b->rp[1] = HSSL2ClientHello;
+ put24(&b->rp[2], len+3);
+ b->rp[5] = SSL2ClientHello;
+ put16(&b->rp[6], ver);
+ qbwrite(tr->handq, b);
+ b = nil;
+ poperror();
+ dechandq(tr);
+ }else{
+ unlock(&tr->hqlock);
+ if(tr->verset && tr->version != SSL3Version && !waserror()){
+ sendAlert(tr, ENoRenegotiation);
+ poperror();
+ }
+ }
+ break;
+ case RApplication:
+ if(!tr->opened)
+ rcvError(tr, EUnexpectedMessage, "application message received before handshake completed");
+ if(BLEN(b) > 0){
+ tr->processed = b;
+ b = nil;
+ }
+ break;
+ }
+ if(b != nil)
+ freeb(b);
+ poperror();
+}
+
+/*
+ * got a fatal alert message
+ */
+static void
+rcvAlert(TlsRec *tr, int err)
+{
+ char *s;
+ int i;
+
+ s = "unknown error";
+ for(i=0; i < nelem(tlserrs); i++){
+ if(tlserrs[i].err == err){
+ s = tlserrs[i].msg;
+ break;
+ }
+ }
+if(tr->debug) pprint("rcvAlert: %s\n", s);
+
+ tlsError(tr, s);
+ if(!tr->opened)
+ error(s);
+ error("tls error");
+}
+
+/*
+ * found an error while decoding the input stream
+ */
+static void
+rcvError(TlsRec *tr, int err, char *fmt, ...)
+{
+ char msg[ERRMAX];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(msg, msg+sizeof(msg), fmt, arg);
+ va_end(arg);
+if(tr->debug) pprint("rcvError: %s\n", msg);
+
+ sendAlert(tr, err);
+
+ if(!tr->opened)
+ error(msg);
+ error("tls error");
+}
+
+/*
+ * make sure the next hand operation returns with a 'msg' error
+ */
+static void
+alertHand(TlsRec *tr, char *msg)
+{
+ Block *b;
+ int n;
+
+ lock(&tr->hqlock);
+ if(tr->handq == nil){
+ unlock(&tr->hqlock);
+ return;
+ }
+ tr->hqref++;
+ unlock(&tr->hqlock);
+
+ n = strlen(msg);
+ if(waserror()){
+ dechandq(tr);
+ nexterror();
+ }
+ b = allocb(n + 2);
+ *b->wp++ = RAlert;
+ memmove(b->wp, msg, n + 1);
+ b->wp += n + 1;
+
+ qbwrite(tr->handq, b);
+
+ poperror();
+ dechandq(tr);
+}
+
+static void
+checkstate(TlsRec *tr, int ishand, int ok)
+{
+ int state;
+
+ lock(&tr->statelk);
+ state = tr->state;
+ unlock(&tr->statelk);
+ if(state & ok)
+ return;
+ switch(state){
+ case SHandshake:
+ case SOpen:
+ break;
+ case SError:
+ case SAlert:
+ if(ishand)
+ error(tr->err);
+ error("tls error");
+ case SRClose:
+ case SLClose:
+ case SClosed:
+ error("tls hungup");
+ }
+ error("tls improperly configured");
+}
+
+static Block*
+tlsbread(Chan *c, long n, ulong offset)
+{
+ int ty;
+ Block *b;
+ TlsRec *volatile tr;
+
+ ty = TYPE(c->qid);
+ switch(ty) {
+ default:
+ return devbread(c, n, offset);
+ case Qhand:
+ case Qdata:
+ break;
+ }
+
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr == nil)
+ panic("tlsbread");
+
+ if(waserror()){
+ qunlock(&tr->in.io);
+ nexterror();
+ }
+ qlock(&tr->in.io);
+ if(ty == Qdata){
+ checkstate(tr, 0, SOpen);
+ while(tr->processed == nil)
+ tlsrecread(tr);
+
+ /* return at most what was asked for */
+ b = qgrab(&tr->processed, n);
+if(tr->debug) pprint("consumed processed %d\n", BLEN(b));
+if(tr->debug) pdump(BLEN(b), b->rp, "consumed:");
+ qunlock(&tr->in.io);
+ poperror();
+ tr->datain += BLEN(b);
+ }else{
+ checkstate(tr, 1, SOpen|SHandshake|SLClose);
+
+ /*
+ * it's ok to look at state without the lock
+ * since it only protects reading records,
+ * and we have that tr->in.io held.
+ */
+ while(!tr->opened && tr->hprocessed == nil && !qcanread(tr->handq))
+ tlsrecread(tr);
+
+ qunlock(&tr->in.io);
+ poperror();
+
+ if(waserror()){
+ qunlock(&tr->hqread);
+ nexterror();
+ }
+ qlock(&tr->hqread);
+ if(tr->hprocessed == nil){
+ b = qbread(tr->handq, MaxRecLen + 1);
+ if(*b->rp++ == RAlert){
+ kstrcpy(up->errstr, (char*)b->rp, ERRMAX);
+ freeb(b);
+ nexterror();
+ }
+ tr->hprocessed = b;
+ }
+ b = qgrab(&tr->hprocessed, n);
+ poperror();
+ qunlock(&tr->hqread);
+ tr->handin += BLEN(b);
+ }
+
+ return b;
+}
+
+static long
+tlsread(Chan *c, void *a, long n, vlong off)
+{
+ Block *volatile b;
+ Block *nb;
+ uchar *va;
+ int i, ty;
+ char *buf, *s, *e;
+ ulong offset = off;
+ TlsRec * tr;
+
+ if(c->qid.type & QTDIR)
+ return devdirread(c, a, n, 0, 0, tlsgen);
+
+ tr = tlsdevs[CONV(c->qid)];
+ ty = TYPE(c->qid);
+ switch(ty) {
+ default:
+ error(Ebadusefd);
+ case Qstatus:
+ buf = smalloc(Statlen);
+ qlock(&tr->in.seclock);
+ qlock(&tr->out.seclock);
+ s = buf;
+ e = buf + Statlen;
+ s = seprint(s, e, "State: %s\n", tlsstate(tr->state));
+ s = seprint(s, e, "Version: 0x%x\n", tr->version);
+ if(tr->in.sec != nil)
+ s = seprint(s, e, "EncIn: %s\nHashIn: %s\n", tr->in.sec->encalg, tr->in.sec->hashalg);
+ if(tr->in.new != nil)
+ s = seprint(s, e, "NewEncIn: %s\nNewHashIn: %s\n", tr->in.new->encalg, tr->in.new->hashalg);
+ if(tr->out.sec != nil)
+ s = seprint(s, e, "EncOut: %s\nHashOut: %s\n", tr->out.sec->encalg, tr->out.sec->hashalg);
+ if(tr->out.new != nil)
+ seprint(s, e, "NewEncOut: %s\nNewHashOut: %s\n", tr->out.new->encalg, tr->out.new->hashalg);
+ qunlock(&tr->in.seclock);
+ qunlock(&tr->out.seclock);
+ n = readstr(offset, a, n, buf);
+ free(buf);
+ return n;
+ case Qstats:
+ buf = smalloc(Statlen);
+ s = buf;
+ e = buf + Statlen;
+ s = seprint(s, e, "DataIn: %lld\n", tr->datain);
+ s = seprint(s, e, "DataOut: %lld\n", tr->dataout);
+ s = seprint(s, e, "HandIn: %lld\n", tr->handin);
+ seprint(s, e, "HandOut: %lld\n", tr->handout);
+ n = readstr(offset, a, n, buf);
+ free(buf);
+ return n;
+ case Qctl:
+ buf = smalloc(Statlen);
+ snprint(buf, Statlen, "%llud", CONV(c->qid));
+ n = readstr(offset, a, n, buf);
+ free(buf);
+ return n;
+ case Qdata:
+ case Qhand:
+ b = tlsbread(c, n, offset);
+ break;
+ case Qencalgs:
+ return readstr(offset, a, n, encalgs);
+ case Qhashalgs:
+ return readstr(offset, a, n, hashalgs);
+ }
+
+ if(waserror()){
+ freeblist(b);
+ nexterror();
+ }
+
+ n = 0;
+ va = a;
+ for(nb = b; nb; nb = nb->next){
+ i = BLEN(nb);
+ memmove(va+n, nb->rp, i);
+ n += i;
+ }
+
+ freeblist(b);
+ poperror();
+
+ return n;
+}
+
+/*
+ * write a block in tls records
+ */
+static void
+tlsrecwrite(TlsRec *tr, int type, Block *b)
+{
+ Block *volatile bb;
+ Block *nb;
+ uchar *p, seq[8];
+ OneWay *volatile out;
+ int n, maclen, pad, ok;
+
+ out = &tr->out;
+ bb = b;
+ if(waserror()){
+ qunlock(&out->io);
+ if(bb != nil)
+ freeb(bb);
+ nexterror();
+ }
+ qlock(&out->io);
+if(tr->debug)pprint("send %d\n", BLEN(b));
+if(tr->debug)pdump(BLEN(b), b->rp, "sent:");
+
+
+ ok = SHandshake|SOpen|SRClose;
+ if(type == RAlert)
+ ok |= SAlert;
+ while(bb != nil){
+ checkstate(tr, type != RApplication, ok);
+
+ /*
+ * get at most one maximal record's input,
+ * with padding on the front for header and
+ * back for mac and maximal block padding.
+ */
+ if(waserror()){
+ qunlock(&out->seclock);
+ nexterror();
+ }
+ qlock(&out->seclock);
+ maclen = 0;
+ pad = 0;
+ if(out->sec != nil){
+ maclen = out->sec->maclen;
+ pad = maclen + out->sec->block;
+ }
+ n = BLEN(bb);
+ if(n > MaxRecLen){
+ n = MaxRecLen;
+ nb = allocb(n + pad + RecHdrLen);
+ memmove(nb->wp + RecHdrLen, bb->rp, n);
+ bb->rp += n;
+ }else{
+ /*
+ * carefully reuse bb so it will get freed if we're out of memory
+ */
+ bb = padblock(bb, RecHdrLen);
+ if(pad)
+ nb = padblock(bb, -pad);
+ else
+ nb = bb;
+ bb = nil;
+ }
+
+ p = nb->rp;
+ p[0] = type;
+ put16(p+1, tr->version);
+ put16(p+3, n);
+
+ if(out->sec != nil){
+ put64(seq, out->seq);
+ out->seq++;
+ (*tr->packMac)(out->sec, out->sec->mackey, seq, p, p + RecHdrLen, n, p + RecHdrLen + n);
+ n += maclen;
+
+ /* encrypt */
+ n = (*out->sec->enc)(out->sec, p + RecHdrLen, n);
+ nb->wp = p + RecHdrLen + n;
+
+ /* update length */
+ put16(p+3, n);
+ }
+ if(type == RChangeCipherSpec){
+ if(out->new == nil)
+ error("change cipher without a new cipher");
+ freeSec(out->sec);
+ out->sec = out->new;
+ out->new = nil;
+ out->seq = 0;
+ }
+ qunlock(&out->seclock);
+ poperror();
+
+ /*
+ * if bwrite error's, we assume the block is queued.
+ * if not, we're out of sync with the receiver and will not recover.
+ */
+ if(waserror()){
+ if(strcmp(up->errstr, "interrupted") != 0)
+ tlsError(tr, "channel error");
+ nexterror();
+ }
+ devtab[tr->c->type]->bwrite(tr->c, nb, 0);
+ poperror();
+ }
+ qunlock(&out->io);
+ poperror();
+}
+
+static long
+tlsbwrite(Chan *c, Block *b, ulong offset)
+{
+ int ty;
+ ulong n;
+ TlsRec *tr;
+
+ n = BLEN(b);
+
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr == nil)
+ panic("tlsbread");
+
+ ty = TYPE(c->qid);
+ switch(ty) {
+ default:
+ return devbwrite(c, b, offset);
+ case Qhand:
+ tlsrecwrite(tr, RHandshake, b);
+ tr->handout += n;
+ break;
+ case Qdata:
+ checkstate(tr, 0, SOpen);
+ tlsrecwrite(tr, RApplication, b);
+ tr->dataout += n;
+ break;
+ }
+
+ return n;
+}
+
+typedef struct Hashalg Hashalg;
+struct Hashalg
+{
+ char *name;
+ int maclen;
+ void (*initkey)(Hashalg *, int, Secret *, uchar*);
+};
+
+static void
+initmd5key(Hashalg *ha, int version, Secret *s, uchar *p)
+{
+ s->maclen = ha->maclen;
+ if(version == SSL3Version)
+ s->mac = sslmac_md5;
+ else
+ s->mac = hmac_md5;
+ memmove(s->mackey, p, ha->maclen);
+}
+
+static void
+initclearmac(Hashalg *unused1, int unused2, Secret *s, uchar *unused3)
+{
+ s->maclen = 0;
+ s->mac = nomac;
+}
+
+static void
+initsha1key(Hashalg *ha, int version, Secret *s, uchar *p)
+{
+ s->maclen = ha->maclen;
+ if(version == SSL3Version)
+ s->mac = sslmac_sha1;
+ else
+ s->mac = hmac_sha1;
+ memmove(s->mackey, p, ha->maclen);
+}
+
+static Hashalg hashtab[] =
+{
+ { "clear", 0, initclearmac, },
+ { "md5", MD5dlen, initmd5key, },
+ { "sha1", SHA1dlen, initsha1key, },
+ { 0 }
+};
+
+static Hashalg*
+parsehashalg(char *p)
+{
+ Hashalg *ha;
+
+ for(ha = hashtab; ha->name; ha++)
+ if(strcmp(p, ha->name) == 0)
+ return ha;
+ error("unsupported hash algorithm");
+ return nil;
+}
+
+typedef struct Encalg Encalg;
+struct Encalg
+{
+ char *name;
+ int keylen;
+ int ivlen;
+ void (*initkey)(Encalg *ea, Secret *, uchar*, uchar*);
+};
+
+static void
+initRC4key(Encalg *ea, Secret *s, uchar *p, uchar *unused1)
+{
+ s->enckey = smalloc(sizeof(RC4state));
+ s->enc = rc4enc;
+ s->dec = rc4enc;
+ s->block = 0;
+ setupRC4state(s->enckey, p, ea->keylen);
+}
+
+static void
+initDES3key(Encalg *unused1, Secret *s, uchar *p, uchar *iv)
+{
+ s->enckey = smalloc(sizeof(DES3state));
+ s->enc = des3enc;
+ s->dec = des3dec;
+ s->block = 8;
+ setupDES3state(s->enckey, (uchar(*)[8])p, iv);
+}
+
+static void
+initclearenc(Encalg *unused1, Secret *s, uchar *unused2, uchar *unused3)
+{
+ s->enc = noenc;
+ s->dec = noenc;
+ s->block = 0;
+}
+
+static Encalg encrypttab[] =
+{
+ { "clear", 0, 0, initclearenc },
+ { "rc4_128", 128/8, 0, initRC4key },
+ { "3des_ede_cbc", 3 * 8, 8, initDES3key },
+ { 0 }
+};
+
+static Encalg*
+parseencalg(char *p)
+{
+ Encalg *ea;
+
+ for(ea = encrypttab; ea->name; ea++)
+ if(strcmp(p, ea->name) == 0)
+ return ea;
+ error("unsupported encryption algorithm");
+ return nil;
+}
+
+static long
+tlswrite(Chan *c, void *a, long n, vlong off)
+{
+ Encalg *ea;
+ Hashalg *ha;
+ TlsRec *volatile tr;
+ Secret *volatile tos, *volatile toc;
+ Block *volatile b;
+ Cmdbuf *volatile cb;
+ int m, ty;
+ char *p, *e;
+ uchar *volatile x;
+ ulong offset = off;
+
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr == nil)
+ panic("tlswrite");
+
+ ty = TYPE(c->qid);
+ switch(ty){
+ case Qdata:
+ case Qhand:
+ p = a;
+ e = p + n;
+ do{
+ m = e - p;
+ if(m > MaxRecLen)
+ m = MaxRecLen;
+
+ b = allocb(m);
+ if(waserror()){
+ freeb(b);
+ nexterror();
+ }
+ memmove(b->wp, p, m);
+ poperror();
+ b->wp += m;
+
+ tlsbwrite(c, b, offset);
+
+ p += m;
+ }while(p < e);
+ return n;
+ case Qctl:
+ break;
+ default:
+ error(Ebadusefd);
+ return -1;
+ }
+
+ cb = parsecmd(a, n);
+ if(waserror()){
+ free(cb);
+ nexterror();
+ }
+ if(cb->nf < 1)
+ error("short control request");
+
+ /* mutex with operations using what we're about to change */
+ if(waserror()){
+ qunlock(&tr->in.seclock);
+ qunlock(&tr->out.seclock);
+ nexterror();
+ }
+ qlock(&tr->in.seclock);
+ qlock(&tr->out.seclock);
+
+ if(strcmp(cb->f[0], "fd") == 0){
+ if(cb->nf != 3)
+ error("usage: fd open-fd version");
+ if(tr->c != nil)
+ error(Einuse);
+ m = strtol(cb->f[2], nil, 0);
+ if(m < MinProtoVersion || m > MaxProtoVersion)
+ error("unsupported version");
+ tr->c = buftochan(cb->f[1]);
+ tr->version = m;
+ tlsSetState(tr, SHandshake, SClosed);
+ }else if(strcmp(cb->f[0], "version") == 0){
+ if(cb->nf != 2)
+ error("usage: version vers");
+ if(tr->c == nil)
+ error("must set fd before version");
+ if(tr->verset)
+ error("version already set");
+ m = strtol(cb->f[1], nil, 0);
+ if(m == SSL3Version)
+ tr->packMac = sslPackMac;
+ else if(m == TLSVersion)
+ tr->packMac = tlsPackMac;
+ else
+ error("unsupported version");
+ tr->verset = 1;
+ tr->version = m;
+ }else if(strcmp(cb->f[0], "secret") == 0){
+ if(cb->nf != 5)
+ error("usage: secret hashalg encalg isclient secretdata");
+ if(tr->c == nil || !tr->verset)
+ error("must set fd and version before secrets");
+
+ if(tr->in.new != nil){
+ freeSec(tr->in.new);
+ tr->in.new = nil;
+ }
+ if(tr->out.new != nil){
+ freeSec(tr->out.new);
+ tr->out.new = nil;
+ }
+
+ ha = parsehashalg(cb->f[1]);
+ ea = parseencalg(cb->f[2]);
+
+ p = cb->f[4];
+ m = (strlen(p)*3)/2;
+ x = smalloc(m);
+ tos = nil;
+ toc = nil;
+ if(waserror()){
+ freeSec(tos);
+ freeSec(toc);
+ free(x);
+ nexterror();
+ }
+ m = dec64(x, m, p, strlen(p));
+ if(m < 2 * ha->maclen + 2 * ea->keylen + 2 * ea->ivlen)
+ error("not enough secret data provided");
+
+ tos = smalloc(sizeof(Secret));
+ toc = smalloc(sizeof(Secret));
+ if(!ha->initkey || !ea->initkey)
+ error("misimplemented secret algorithm");
+ (*ha->initkey)(ha, tr->version, tos, &x[0]);
+ (*ha->initkey)(ha, tr->version, toc, &x[ha->maclen]);
+ (*ea->initkey)(ea, tos, &x[2 * ha->maclen], &x[2 * ha->maclen + 2 * ea->keylen]);
+ (*ea->initkey)(ea, toc, &x[2 * ha->maclen + ea->keylen], &x[2 * ha->maclen + 2 * ea->keylen + ea->ivlen]);
+
+ if(!tos->mac || !tos->enc || !tos->dec
+ || !toc->mac || !toc->enc || !toc->dec)
+ error("missing algorithm implementations");
+ if(strtol(cb->f[3], nil, 0) == 0){
+ tr->in.new = tos;
+ tr->out.new = toc;
+ }else{
+ tr->in.new = toc;
+ tr->out.new = tos;
+ }
+ if(tr->version == SSL3Version){
+ toc->unpad = sslunpad;
+ tos->unpad = sslunpad;
+ }else{
+ toc->unpad = tlsunpad;
+ tos->unpad = tlsunpad;
+ }
+ toc->encalg = ea->name;
+ toc->hashalg = ha->name;
+ tos->encalg = ea->name;
+ tos->hashalg = ha->name;
+
+ free(x);
+ poperror();
+ }else if(strcmp(cb->f[0], "changecipher") == 0){
+ if(cb->nf != 1)
+ error("usage: changecipher");
+ if(tr->out.new == nil)
+ error("cannot change cipher spec without setting secret");
+
+ qunlock(&tr->in.seclock);
+ qunlock(&tr->out.seclock);
+ poperror();
+ free(cb);
+ poperror();
+
+ /*
+ * the real work is done as the message is written
+ * so the stream is encrypted in sync.
+ */
+ b = allocb(1);
+ *b->wp++ = 1;
+ tlsrecwrite(tr, RChangeCipherSpec, b);
+ return n;
+ }else if(strcmp(cb->f[0], "opened") == 0){
+ if(cb->nf != 1)
+ error("usage: opened");
+ if(tr->in.sec == nil || tr->out.sec == nil)
+ error("cipher must be configured before enabling data messages");
+ lock(&tr->statelk);
+ if(tr->state != SHandshake && tr->state != SOpen){
+ unlock(&tr->statelk);
+ error("cannot enable data messages");
+ }
+ tr->state = SOpen;
+ unlock(&tr->statelk);
+ tr->opened = 1;
+ }else if(strcmp(cb->f[0], "alert") == 0){
+ if(cb->nf != 2)
+ error("usage: alert n");
+ if(tr->c == nil)
+ error("must set fd before sending alerts");
+ m = strtol(cb->f[1], nil, 0);
+
+ qunlock(&tr->in.seclock);
+ qunlock(&tr->out.seclock);
+ poperror();
+ free(cb);
+ poperror();
+
+ sendAlert(tr, m);
+
+ if(m == ECloseNotify)
+ tlsclosed(tr, SLClose);
+
+ return n;
+ } else if(strcmp(cb->f[0], "debug") == 0){
+ if(cb->nf == 2){
+ if(strcmp(cb->f[1], "on") == 0)
+ tr->debug = 1;
+ else
+ tr->debug = 0;
+ } else
+ tr->debug = 1;
+ } else
+ error(Ebadarg);
+
+ qunlock(&tr->in.seclock);
+ qunlock(&tr->out.seclock);
+ poperror();
+ free(cb);
+ poperror();
+
+ return n;
+}
+
+static void
+tlsinit(void)
+{
+ struct Encalg *e;
+ struct Hashalg *h;
+ int n;
+ char *cp;
+ static int already;
+
+ if(!already){
+ fmtinstall('H', encodefmt);
+ already = 1;
+ }
+
+ tlsdevs = smalloc(sizeof(TlsRec*) * maxtlsdevs);
+ trnames = smalloc((sizeof *trnames) * maxtlsdevs);
+
+ n = 1;
+ for(e = encrypttab; e->name != nil; e++)
+ n += strlen(e->name) + 1;
+ cp = encalgs = smalloc(n);
+ for(e = encrypttab;;){
+ strcpy(cp, e->name);
+ cp += strlen(e->name);
+ e++;
+ if(e->name == nil)
+ break;
+ *cp++ = ' ';
+ }
+ *cp = 0;
+
+ n = 1;
+ for(h = hashtab; h->name != nil; h++)
+ n += strlen(h->name) + 1;
+ cp = hashalgs = smalloc(n);
+ for(h = hashtab;;){
+ strcpy(cp, h->name);
+ cp += strlen(h->name);
+ h++;
+ if(h->name == nil)
+ break;
+ *cp++ = ' ';
+ }
+ *cp = 0;
+}
+
+Dev tlsdevtab = {
+ 'a',
+ "tls",
+
+ devreset,
+ tlsinit,
+ devshutdown,
+ tlsattach,
+ tlswalk,
+ tlsstat,
+ tlsopen,
+ devcreate,
+ tlsclose,
+ tlsread,
+ tlsbread,
+ tlswrite,
+ tlsbwrite,
+ devremove,
+ tlswstat,
+};
+
+/* get channel associated with an fd */
+static Chan*
+buftochan(char *p)
+{
+ Chan *c;
+ int fd;
+
+ if(p == 0)
+ error(Ebadarg);
+ fd = strtoul(p, 0, 0);
+ if(fd < 0)
+ error(Ebadarg);
+ c = fdtochan(fd, -1, 0, 1); /* error check and inc ref */
+ return c;
+}
+
+static void
+sendAlert(TlsRec *tr, int err)
+{
+ Block *b;
+ int i, fatal;
+ char *msg;
+
+if(tr->debug)pprint("sendAlert %d\n", err);
+ fatal = 1;
+ msg = "tls unknown alert";
+ for(i=0; i < nelem(tlserrs); i++) {
+ if(tlserrs[i].err == err) {
+ msg = tlserrs[i].msg;
+ if(tr->version == SSL3Version)
+ err = tlserrs[i].sslerr;
+ else
+ err = tlserrs[i].tlserr;
+ fatal = tlserrs[i].fatal;
+ break;
+ }
+ }
+
+ if(!waserror()){
+ b = allocb(2);
+ *b->wp++ = fatal + 1;
+ *b->wp++ = err;
+ if(fatal)
+ tlsSetState(tr, SAlert, SOpen|SHandshake|SRClose);
+ tlsrecwrite(tr, RAlert, b);
+ poperror();
+ }
+ if(fatal)
+ tlsError(tr, msg);
+}
+
+static void
+tlsError(TlsRec *tr, char *msg)
+{
+ int s;
+
+if(tr->debug)pprint("tleError %s\n", msg);
+ lock(&tr->statelk);
+ s = tr->state;
+ tr->state = SError;
+ if(s != SError){
+ strncpy(tr->err, msg, ERRMAX - 1);
+ tr->err[ERRMAX - 1] = '\0';
+ }
+ unlock(&tr->statelk);
+ if(s != SError)
+ alertHand(tr, msg);
+}
+
+static void
+tlsSetState(TlsRec *tr, int new, int old)
+{
+ lock(&tr->statelk);
+ if(tr->state & old)
+ tr->state = new;
+ unlock(&tr->statelk);
+}
+
+/* hand up a digest connection */
+static void
+tlshangup(TlsRec *tr)
+{
+ Block *b;
+
+ qlock(&tr->in.io);
+ for(b = tr->processed; b; b = tr->processed){
+ tr->processed = b->next;
+ freeb(b);
+ }
+ if(tr->unprocessed != nil){
+ freeb(tr->unprocessed);
+ tr->unprocessed = nil;
+ }
+ qunlock(&tr->in.io);
+
+ tlsSetState(tr, SClosed, ~0);
+}
+
+static TlsRec*
+newtls(Chan *ch)
+{
+ TlsRec **pp, **ep, **np;
+ char **nmp;
+ int t, newmax;
+
+ if(waserror()) {
+ unlock(&tdlock);
+ nexterror();
+ }
+ lock(&tdlock);
+ ep = &tlsdevs[maxtlsdevs];
+ for(pp = tlsdevs; pp < ep; pp++)
+ if(*pp == nil)
+ break;
+ if(pp >= ep) {
+ if(maxtlsdevs >= MaxTlsDevs) {
+ unlock(&tdlock);
+ poperror();
+ return nil;
+ }
+ newmax = 2 * maxtlsdevs;
+ if(newmax > MaxTlsDevs)
+ newmax = MaxTlsDevs;
+ np = smalloc(sizeof(TlsRec*) * newmax);
+ memmove(np, tlsdevs, sizeof(TlsRec*) * maxtlsdevs);
+ tlsdevs = np;
+ pp = &tlsdevs[maxtlsdevs];
+ memset(pp, 0, sizeof(TlsRec*)*(newmax - maxtlsdevs));
+
+ nmp = smalloc(sizeof *nmp * newmax);
+ memmove(nmp, trnames, sizeof *nmp * maxtlsdevs);
+ trnames = nmp;
+
+ maxtlsdevs = newmax;
+ }
+ *pp = mktlsrec();
+ if(pp - tlsdevs >= tdhiwat)
+ tdhiwat++;
+ t = TYPE(ch->qid);
+ if(t == Qclonus)
+ t = Qctl;
+ ch->qid.path = QID(pp - tlsdevs, t);
+ ch->qid.vers = 0;
+ unlock(&tdlock);
+ poperror();
+ return *pp;
+}
+
+static TlsRec *
+mktlsrec(void)
+{
+ TlsRec *tr;
+
+ tr = mallocz(sizeof(*tr), 1);
+ if(tr == nil)
+ error(Enomem);
+ tr->state = SClosed;
+ tr->ref = 1;
+ kstrdup(&tr->user, up->user);
+ tr->perm = 0660;
+ return tr;
+}
+
+static char*
+tlsstate(int s)
+{
+ switch(s){
+ case SHandshake:
+ return "Handshaking";
+ case SOpen:
+ return "Established";
+ case SRClose:
+ return "RemoteClosed";
+ case SLClose:
+ return "LocalClosed";
+ case SAlert:
+ return "Alerting";
+ case SError:
+ return "Errored";
+ case SClosed:
+ return "Closed";
+ }
+ return "Unknown";
+}
+
+static void
+freeSec(Secret *s)
+{
+ if(s != nil){
+ free(s->enckey);
+ free(s);
+ }
+}
+
+static int
+noenc(Secret *unused1, uchar *unused2, int n)
+{
+ return n;
+}
+
+static int
+rc4enc(Secret *sec, uchar *buf, int n)
+{
+ rc4(sec->enckey, buf, n);
+ return n;
+}
+
+static int
+tlsunpad(uchar *buf, int n, int block)
+{
+ int pad, nn;
+
+ pad = buf[n - 1];
+ nn = n - 1 - pad;
+ if(nn <= 0 || n % block)
+ return -1;
+ while(--n > nn)
+ if(pad != buf[n - 1])
+ return -1;
+ return nn;
+}
+
+static int
+sslunpad(uchar *buf, int n, int block)
+{
+ int pad, nn;
+
+ pad = buf[n - 1];
+ nn = n - 1 - pad;
+ if(nn <= 0 || n % block)
+ return -1;
+ return nn;
+}
+
+static int
+blockpad(uchar *buf, int n, int block)
+{
+ int pad, nn;
+
+ nn = n + block;
+ nn -= nn % block;
+ pad = nn - (n + 1);
+ while(n < nn)
+ buf[n++] = pad;
+ return nn;
+}
+
+static int
+des3enc(Secret *sec, uchar *buf, int n)
+{
+ n = blockpad(buf, n, 8);
+ des3CBCencrypt(buf, n, sec->enckey);
+ return n;
+}
+
+static int
+des3dec(Secret *sec, uchar *buf, int n)
+{
+ des3CBCdecrypt(buf, n, sec->enckey);
+ return (*sec->unpad)(buf, n, 8);
+}
+static DigestState*
+nomac(uchar *unused1, ulong unused2, uchar *unused3, ulong unused4,
+ uchar *unused5, DigestState *unused6)
+{
+ return nil;
+}
+
+/*
+ * sslmac: mac calculations for ssl 3.0 only; tls 1.0 uses the standard hmac.
+ */
+static DigestState*
+sslmac_x(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s,
+ DigestState*(*x)(uchar*, ulong, uchar*, DigestState*), int xlen, int padlen)
+{
+ int i;
+ uchar pad[48], innerdigest[20];
+
+ if(xlen > sizeof(innerdigest)
+ || padlen > sizeof(pad))
+ return nil;
+
+ if(klen>64)
+ return nil;
+
+ /* first time through */
+ if(s == nil){
+ for(i=0; i<padlen; i++)
+ pad[i] = 0x36;
+ s = (*x)(key, klen, nil, nil);
+ s = (*x)(pad, padlen, nil, s);
+ if(s == nil)
+ return nil;
+ }
+
+ s = (*x)(p, len, nil, s);
+ if(digest == nil)
+ return s;
+
+ /* last time through */
+ for(i=0; i<padlen; i++)
+ pad[i] = 0x5c;
+ (*x)(nil, 0, innerdigest, s);
+ s = (*x)(key, klen, nil, nil);
+ s = (*x)(pad, padlen, nil, s);
+ (*x)(innerdigest, xlen, digest, s);
+ return nil;
+}
+
+static DigestState*
+sslmac_sha1(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s)
+{
+ return sslmac_x(p, len, key, klen, digest, s, sha1, SHA1dlen, 40);
+}
+
+static DigestState*
+sslmac_md5(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s)
+{
+ return sslmac_x(p, len, key, klen, digest, s, md5, MD5dlen, 48);
+}
+
+static void
+sslPackMac(Secret *sec, uchar *mackey, uchar *seq, uchar *header, uchar *body, int len, uchar *mac)
+{
+ DigestState *s;
+ uchar buf[11];
+
+ memmove(buf, seq, 8);
+ buf[8] = header[0];
+ buf[9] = header[3];
+ buf[10] = header[4];
+
+ s = (*sec->mac)(buf, 11, mackey, sec->maclen, 0, 0);
+ (*sec->mac)(body, len, mackey, sec->maclen, mac, s);
+}
+
+static void
+tlsPackMac(Secret *sec, uchar *mackey, uchar *seq, uchar *header, uchar *body, int len, uchar *mac)
+{
+ DigestState *s;
+ uchar buf[13];
+
+ memmove(buf, seq, 8);
+ memmove(&buf[8], header, 5);
+
+ s = (*sec->mac)(buf, 13, mackey, sec->maclen, 0, 0);
+ (*sec->mac)(body, len, mackey, sec->maclen, mac, s);
+}
+
+static void
+put32(uchar *p, u32int x)
+{
+ p[0] = x>>24;
+ p[1] = x>>16;
+ p[2] = x>>8;
+ p[3] = x;
+}
+
+static void
+put64(uchar *p, vlong x)
+{
+ put32(p, (u32int)(x >> 32));
+ put32(p+4, (u32int)x);
+}
+
+static void
+put24(uchar *p, int x)
+{
+ p[0] = x>>16;
+ p[1] = x>>8;
+ p[2] = x;
+}
+
+static void
+put16(uchar *p, int x)
+{
+ p[0] = x>>8;
+ p[1] = x;
+}
+
+/*
+static u32int
+get32(uchar *p)
+{
+ return (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3];
+}
+*/
+
+static int
+get16(uchar *p)
+{
+ return (p[0]<<8)|p[1];
+}
+
+static char *charmap = "0123456789abcdef";
+
+static void
+pdump(int len, void *a, char *tag)
+{
+ uchar *p;
+ int i;
+ char buf[65+32];
+ char *q;
+
+ p = a;
+ strcpy(buf, tag);
+ while(len > 0){
+ q = buf + strlen(tag);
+ for(i = 0; len > 0 && i < 32; i++){
+ if(*p >= ' ' && *p < 0x7f){
+ *q++ = ' ';
+ *q++ = *p;
+ } else {
+ *q++ = charmap[*p>>4];
+ *q++ = charmap[*p & 0xf];
+ }
+ len--;
+ p++;
+ }
+ *q = 0;
+
+ if(len > 0)
+ pprint("%s...\n", buf);
+ else
+ pprint("%s\n", buf);
+ }
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/error.c b/sys/src/cmd/unix/drawterm/kern/error.c
new file mode 100755
index 000000000..465de475a
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/error.c
@@ -0,0 +1,50 @@
+char Enoerror[] = "no error";
+char Emount[] = "inconsistent mount";
+char Eunmount[] = "not mounted";
+char Eunion[] = "not in union";
+char Emountrpc[] = "mount rpc error";
+char Eshutdown[] = "device shut down";
+char Enocreate[] = "mounted directory forbids creation";
+char Enonexist[] = "file does not exist";
+char Eexist[] = "file already exists";
+char Ebadsharp[] = "unknown device in # filename";
+char Enotdir[] = "not a directory";
+char Eisdir[] = "file is a directory";
+char Ebadchar[] = "bad character in file name";
+char Efilename[] = "file name syntax";
+char Eperm[] = "permission denied";
+char Ebadusefd[] = "inappropriate use of fd";
+char Ebadarg[] = "bad arg in system call";
+char Einuse[] = "device or object already in use";
+char Eio[] = "i/o error";
+char Etoobig[] = "read or write too large";
+char Etoosmall[] = "read or write too small";
+char Enoport[] = "network port not available";
+char Ehungup[] = "i/o on hungup channel";
+char Ebadctl[] = "bad process or channel control request";
+char Enodev[] = "no free devices";
+char Eprocdied[] = "process exited";
+char Enochild[] = "no living children";
+char Eioload[] = "i/o error in demand load";
+char Enovmem[] = "virtual memory allocation failed";
+char Ebadfd[] = "fd out of range or not open";
+char Enofd[] = "no free file descriptors";
+char Eisstream[] = "seek on a stream";
+char Ebadexec[] = "exec header invalid";
+char Etimedout[] = "connection timed out";
+char Econrefused[] = "connection refused";
+char Econinuse[] = "connection in use";
+char Eintr[] = "interrupted";
+char Enomem[] = "kernel allocate failed";
+char Enoswap[] = "swap space full";
+char Esoverlap[] = "segments overlap";
+char Emouseset[] = "mouse type already set";
+char Eshort[] = "i/o count too small";
+char Egreg[] = "ken has left the building";
+char Ebadspec[] = "bad attach specifier";
+char Enoreg[] = "process has no saved registers";
+char Enoattach[] = "mount/attach disallowed";
+char Eshortstat[] = "stat buffer too small";
+char Ebadstat[] = "malformed stat buffer";
+char Enegoff[] = "negative i/o offset";
+char Ecmdargs[] = "wrong #args in control message";
diff --git a/sys/src/cmd/unix/drawterm/kern/error.h b/sys/src/cmd/unix/drawterm/kern/error.h
new file mode 100755
index 000000000..6bb87622e
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/error.h
@@ -0,0 +1,50 @@
+extern char Enoerror[]; /* no error */
+extern char Emount[]; /* inconsistent mount */
+extern char Eunmount[]; /* not mounted */
+extern char Eunion[]; /* not in union */
+extern char Emountrpc[]; /* mount rpc error */
+extern char Eshutdown[]; /* device shut down */
+extern char Enocreate[]; /* mounted directory forbids creation */
+extern char Enonexist[]; /* file does not exist */
+extern char Eexist[]; /* file already exists */
+extern char Ebadsharp[]; /* unknown device in # filename */
+extern char Enotdir[]; /* not a directory */
+extern char Eisdir[]; /* file is a directory */
+extern char Ebadchar[]; /* bad character in file name */
+extern char Efilename[]; /* file name syntax */
+extern char Eperm[]; /* permission denied */
+extern char Ebadusefd[]; /* inappropriate use of fd */
+extern char Ebadarg[]; /* bad arg in system call */
+extern char Einuse[]; /* device or object already in use */
+extern char Eio[]; /* i/o error */
+extern char Etoobig[]; /* read or write too large */
+extern char Etoosmall[]; /* read or write too small */
+extern char Enoport[]; /* network port not available */
+extern char Ehungup[]; /* i/o on hungup channel */
+extern char Ebadctl[]; /* bad process or channel control request */
+extern char Enodev[]; /* no free devices */
+extern char Eprocdied[]; /* process exited */
+extern char Enochild[]; /* no living children */
+extern char Eioload[]; /* i/o error in demand load */
+extern char Enovmem[]; /* virtual memory allocation failed */
+extern char Ebadfd[]; /* fd out of range or not open */
+extern char Enofd[]; /* no free file descriptors */
+extern char Eisstream[]; /* seek on a stream */
+extern char Ebadexec[]; /* exec header invalid */
+extern char Etimedout[]; /* connection timed out */
+extern char Econrefused[]; /* connection refused */
+extern char Econinuse[]; /* connection in use */
+extern char Eintr[]; /* interrupted */
+extern char Enomem[]; /* kernel allocate failed */
+extern char Enoswap[]; /* swap space full */
+extern char Esoverlap[]; /* segments overlap */
+extern char Emouseset[]; /* mouse type already set */
+extern char Eshort[]; /* i/o count too small */
+extern char Egreg[]; /* ken has left the building */
+extern char Ebadspec[]; /* bad attach specifier */
+extern char Enoreg[]; /* process has no saved registers */
+extern char Enoattach[]; /* mount/attach disallowed */
+extern char Eshortstat[]; /* stat buffer too small */
+extern char Ebadstat[]; /* malformed stat buffer */
+extern char Enegoff[]; /* negative i/o offset */
+extern char Ecmdargs[]; /* wrong #args in control message */
diff --git a/sys/src/cmd/unix/drawterm/kern/exportfs.c b/sys/src/cmd/unix/drawterm/kern/exportfs.c
new file mode 100755
index 000000000..46cb90d0c
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/exportfs.c
@@ -0,0 +1,821 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+typedef struct Fid Fid;
+typedef struct Export Export;
+typedef struct Exq Exq;
+
+#define nil ((void*)0)
+
+enum
+{
+ Nfidhash = 1,
+ MAXRPC = MAXMSG+MAXFDATA,
+ MAXDIRREAD = (MAXFDATA/DIRLEN)*DIRLEN
+};
+
+struct Export
+{
+ Ref r;
+ Exq* work;
+ Lock fidlock;
+ Fid* fid[Nfidhash];
+ Chan* root;
+ Chan* io;
+ Pgrp* pgrp;
+ int npart;
+ char part[MAXRPC];
+};
+
+struct Fid
+{
+ Fid* next;
+ Fid** last;
+ Chan* chan;
+ long offset;
+ int fid;
+ int ref; /* fcalls using the fid; locked by Export.Lock */
+ int attached; /* fid attached or cloned but not clunked */
+};
+
+struct Exq
+{
+ Lock lk;
+ int nointr;
+ int noresponse; /* don't respond to this one */
+ Exq* next;
+ int shut; /* has been noted for shutdown */
+ Export* export;
+ void* slave;
+ Fcall rpc;
+ char buf[MAXRPC];
+};
+
+struct
+{
+ Lock l;
+ Qlock qwait;
+ Rendez rwait;
+ Exq *head; /* work waiting for a slave */
+ Exq *tail;
+}exq;
+
+static void exshutdown(Export*);
+static void exflush(Export*, int, int);
+static void exslave(void*);
+static void exfree(Export*);
+static void exportproc(Export*);
+
+static char* Exauth(Export*, Fcall*);
+static char* Exattach(Export*, Fcall*);
+static char* Exclunk(Export*, Fcall*);
+static char* Excreate(Export*, Fcall*);
+static char* Exopen(Export*, Fcall*);
+static char* Exread(Export*, Fcall*);
+static char* Exremove(Export*, Fcall*);
+static char* Exstat(Export*, Fcall*);
+static char* Exwalk(Export*, Fcall*);
+static char* Exwrite(Export*, Fcall*);
+static char* Exwstat(Export*, Fcall*);
+static char* Exversion(Export*, Fcall*);
+
+static char *(*fcalls[Tmax])(Export*, Fcall*);
+
+static char Enofid[] = "no such fid";
+static char Eseekdir[] = "can't seek on a directory";
+static char Ereaddir[] = "unaligned read of a directory";
+static int exdebug = 0;
+
+int
+sysexport(int fd)
+{
+ Chan *c;
+ Export *fs;
+
+ if(waserror())
+ return -1;
+
+ c = fdtochan(fd, ORDWR, 1, 1);
+ poperror();
+ c->flag |= CMSG;
+
+ fs = mallocz(sizeof(Export));
+ fs->r.ref = 1;
+ fs->pgrp = up->pgrp;
+ refinc(&fs->pgrp->r);
+ refinc(&up->slash->r);
+ fs->root = up->slash;
+ refinc(&fs->root->r);
+ fs->root = domount(fs->root);
+ fs->io = c;
+
+ exportproc(fs);
+
+ return 0;
+}
+
+static void
+exportinit(void)
+{
+ lock(&exq.l);
+ if(fcalls[Tversion] != nil) {
+ unlock(&exq.l);
+ return;
+ }
+
+ fmtinstall('F', fcallfmt);
+ fmtinstall('D', dirfmt);
+ fmtinstall('M', dirmodefmt);
+ fcalls[Tversion] = Exversion;
+ fcalls[Tauth] = Exauth;
+ fcalls[Tattach] = Exattach;
+ fcalls[Twalk] = Exwalk;
+ fcalls[Topen] = Exopen;
+ fcalls[Tcreate] = Excreate;
+ fcalls[Tread] = Exread;
+ fcalls[Twrite] = Exwrite;
+ fcalls[Tclunk] = Exclunk;
+ fcalls[Tremove] = Exremove;
+ fcalls[Tstat] = Exstat;
+ fcalls[Twstat] = Exwstat;
+ unlock(&exq.l);
+}
+
+void
+exportproc(Export *fs)
+{
+ Exq *q;
+ char *buf;
+ int n, cn, len;
+
+ exportinit();
+
+ for(;;){
+ q = mallocz(sizeof(Exq));
+ if(q == 0)
+ panic("no memory");
+
+ q->rpc.data = q->buf + MAXMSG;
+
+ buf = q->buf;
+ len = MAXRPC;
+ if(fs->npart) {
+ memmove(buf, fs->part, fs->npart);
+ buf += fs->npart;
+ len -= fs->npart;
+ goto chk;
+ }
+ for(;;) {
+ if(waserror())
+ goto bad;
+
+ n = (*devtab[fs->io->type].read)(fs->io, buf, len, 0);
+ poperror();
+
+ if(n <= 0)
+ goto bad;
+
+ buf += n;
+ len -= n;
+ chk:
+ n = buf - q->buf;
+
+ /* convM2S returns size of correctly decoded message */
+ cn = convM2S(q->buf, &q->rpc, n);
+ if(cn < 0){
+ iprint("bad message type in devmnt\n");
+ goto bad;
+ }
+ if(cn > 0) {
+ n -= cn;
+ if(n < 0){
+ iprint("negative size in devmnt");
+ goto bad;
+ }
+ fs->npart = n;
+ if(n != 0)
+ memmove(fs->part, q->buf+cn, n);
+ break;
+ }
+ }
+ if(exdebug)
+ iprint("export %d <- %F\n", getpid(), &q->rpc);
+
+ if(q->rpc.type == Tflush){
+ exflush(fs, q->rpc.tag, q->rpc.oldtag);
+ free(q);
+ continue;
+ }
+
+ q->export = fs;
+ refinc(&fs->r);
+
+ lock(&exq.l);
+ if(exq.head == nil)
+ exq.head = q;
+ else
+ exq.tail->next = q;
+ q->next = nil;
+ exq.tail = q;
+ unlock(&exq.l);
+ if(exq.qwait.first == nil) {
+ n = thread("exportfs", exslave, nil);
+/* iprint("launch export (pid=%ux)\n", n); */
+ }
+ rendwakeup(&exq.rwait);
+ }
+bad:
+ free(q);
+ exshutdown(fs);
+ exfree(fs);
+}
+
+void
+exflush(Export *fs, int flushtag, int tag)
+{
+ Exq *q, **last;
+ int n;
+ Fcall fc;
+ char buf[MAXMSG];
+
+ /* hasn't been started? */
+ lock(&exq.l);
+ last = &exq.head;
+ for(q = exq.head; q != nil; q = q->next){
+ if(q->export == fs && q->rpc.tag == tag){
+ *last = q->next;
+ unlock(&exq.l);
+ exfree(fs);
+ free(q);
+ goto Respond;
+ }
+ last = &q->next;
+ }
+ unlock(&exq.l);
+
+ /* in progress? */
+ lock(&fs->r.l);
+ for(q = fs->work; q != nil; q = q->next){
+ if(q->rpc.tag == tag && !q->noresponse){
+ lock(&q->lk);
+ q->noresponse = 1;
+ if(!q->nointr)
+ intr(q->slave);
+ unlock(&q->lk);
+ unlock(&fs->r.l);
+ goto Respond;
+ return;
+ }
+ }
+ unlock(&fs->r.l);
+
+if(exdebug) iprint("exflush: did not find rpc: %d\n", tag);
+
+Respond:
+ fc.type = Rflush;
+ fc.tag = flushtag;
+ n = convS2M(&fc, buf);
+if(exdebug) iprint("exflush -> %F\n", &fc);
+ if(!waserror()){
+ (*devtab[fs->io->type].write)(fs->io, buf, n, 0);
+ poperror();
+ }
+}
+
+void
+exshutdown(Export *fs)
+{
+ Exq *q, **last;
+
+ lock(&exq.l);
+ last = &exq.head;
+ for(q = exq.head; q != nil; q = *last){
+ if(q->export == fs){
+ *last = q->next;
+ exfree(fs);
+ free(q);
+ continue;
+ }
+ last = &q->next;
+ }
+ unlock(&exq.l);
+
+ lock(&fs->r.l);
+ q = fs->work;
+ while(q != nil){
+ if(q->shut){
+ q = q->next;
+ continue;
+ }
+ q->shut = 1;
+ unlock(&fs->r.l);
+ /* postnote(q->slave, 1, "interrupted", NUser); */
+ iprint("postnote 2!\n");
+ lock(&fs->r.l);
+ q = fs->work;
+ }
+ unlock(&fs->r.l);
+}
+
+void
+exfree(Export *fs)
+{
+ Fid *f, *n;
+ int i;
+
+ if(refdec(&fs->r) != 0)
+ return;
+ closepgrp(fs->pgrp);
+ cclose(fs->root);
+ cclose(fs->io);
+ for(i = 0; i < Nfidhash; i++){
+ for(f = fs->fid[i]; f != nil; f = n){
+ if(f->chan != nil)
+ cclose(f->chan);
+ n = f->next;
+ free(f);
+ }
+ }
+ free(fs);
+}
+
+int
+exwork(void *a)
+{
+ return exq.head != nil;
+}
+
+void
+exslave(void *a)
+{
+ Export *fs;
+ Exq *q, *t, **last;
+ char *err;
+ int n;
+/*
+ closepgrp(up->pgrp);
+ up->pgrp = nil;
+*/
+ for(;;){
+ qlock(&exq.qwait);
+ rendsleep(&exq.rwait, exwork, nil);
+
+ lock(&exq.l);
+ q = exq.head;
+ if(q == nil) {
+ unlock(&exq.l);
+ qunlock(&exq.qwait);
+ continue;
+ }
+ exq.head = q->next;
+ q->slave = curthread();
+ unlock(&exq.l);
+
+ qunlock(&exq.qwait);
+
+ q->noresponse = 0;
+ q->nointr = 0;
+ fs = q->export;
+ lock(&fs->r.l);
+ q->next = fs->work;
+ fs->work = q;
+ unlock(&fs->r.l);
+
+ up->pgrp = q->export->pgrp;
+
+ if(exdebug > 1)
+ iprint("exslave dispatch %d %F\n", getpid(), &q->rpc);
+
+ if(waserror()){
+ iprint("exslave err %r\n");
+ err = up->errstr;
+ goto Err;
+ }
+ if(q->rpc.type >= Tmax || !fcalls[q->rpc.type])
+ err = "bad fcall type";
+ else
+ err = (*fcalls[q->rpc.type])(fs, &q->rpc);
+
+ poperror();
+ Err:;
+ q->rpc.type++;
+ if(err){
+ q->rpc.type = Rerror;
+ strncpy(q->rpc.ename, err, ERRLEN);
+ }
+ n = convS2M(&q->rpc, q->buf);
+
+ if(exdebug)
+ iprint("exslve %d -> %F\n", getpid(), &q->rpc);
+
+ lock(&q->lk);
+ if(q->noresponse == 0){
+ q->nointr = 1;
+ clearintr();
+ if(!waserror()){
+ (*devtab[fs->io->type].write)(fs->io, q->buf, n, 0);
+ poperror();
+ }
+ }
+ unlock(&q->lk);
+
+ /*
+ * exflush might set noresponse at this point, but
+ * setting noresponse means don't send a response now;
+ * it's okay that we sent a response already.
+ */
+ if(exdebug > 1)
+ iprint("exslave %d written %d\n", getpid(), q->rpc.tag);
+
+ lock(&fs->r.l);
+ last = &fs->work;
+ for(t = fs->work; t != nil; t = t->next){
+ if(t == q){
+ *last = q->next;
+ break;
+ }
+ last = &t->next;
+ }
+ unlock(&fs->r.l);
+
+ exfree(q->export);
+ free(q);
+ }
+ iprint("exslave shut down");
+ threadexit();
+}
+
+Fid*
+Exmkfid(Export *fs, int fid)
+{
+ ulong h;
+ Fid *f, *nf;
+
+ nf = mallocz(sizeof(Fid));
+ if(nf == nil)
+ return nil;
+ lock(&fs->fidlock);
+ h = fid % Nfidhash;
+ for(f = fs->fid[h]; f != nil; f = f->next){
+ if(f->fid == fid){
+ unlock(&fs->fidlock);
+ free(nf);
+ return nil;
+ }
+ }
+
+ nf->next = fs->fid[h];
+ if(nf->next != nil)
+ nf->next->last = &nf->next;
+ nf->last = &fs->fid[h];
+ fs->fid[h] = nf;
+
+ nf->fid = fid;
+ nf->ref = 1;
+ nf->attached = 1;
+ nf->offset = 0;
+ nf->chan = nil;
+ unlock(&fs->fidlock);
+ return nf;
+}
+
+Fid*
+Exgetfid(Export *fs, int fid)
+{
+ Fid *f;
+ ulong h;
+
+ lock(&fs->fidlock);
+ h = fid % Nfidhash;
+ for(f = fs->fid[h]; f; f = f->next) {
+ if(f->fid == fid){
+ if(f->attached == 0)
+ break;
+ f->ref++;
+ unlock(&fs->fidlock);
+ return f;
+ }
+ }
+ unlock(&fs->fidlock);
+ return nil;
+}
+
+void
+Exputfid(Export *fs, Fid *f)
+{
+ lock(&fs->fidlock);
+ f->ref--;
+ if(f->ref == 0 && f->attached == 0){
+ if(f->chan != nil)
+ cclose(f->chan);
+ f->chan = nil;
+ *f->last = f->next;
+ if(f->next != nil)
+ f->next->last = f->last;
+ unlock(&fs->fidlock);
+ free(f);
+ return;
+ }
+ unlock(&fs->fidlock);
+}
+
+char*
+Exsession(Export *e, Fcall *rpc)
+{
+ memset(rpc->authid, 0, sizeof(rpc->authid));
+ memset(rpc->authdom, 0, sizeof(rpc->authdom));
+ memset(rpc->chal, 0, sizeof(rpc->chal));
+ return nil;
+}
+
+char*
+Exauth(Export *e, Fcall *f)
+{
+ return "authentication not required";
+}
+
+char*
+Exattach(Export *fs, Fcall *rpc)
+{
+ Fid *f;
+
+ f = Exmkfid(fs, rpc->fid);
+ if(f == nil)
+ return Einuse;
+ if(waserror()){
+ f->attached = 0;
+ Exputfid(fs, f);
+ return up->errstr;
+ }
+ f->chan = clone(fs->root, nil);
+ poperror();
+ rpc->qid = f->chan->qid;
+ Exputfid(fs, f);
+ return nil;
+}
+
+char*
+Exclone(Export *fs, Fcall *rpc)
+{
+ Fid *f, *nf;
+
+ if(rpc->fid == rpc->newfid)
+ return Einuse;
+ f = Exgetfid(fs, rpc->fid);
+ if(f == nil)
+ return Enofid;
+ nf = Exmkfid(fs, rpc->newfid);
+ if(nf == nil){
+ Exputfid(fs, f);
+ return Einuse;
+ }
+ if(waserror()){
+ Exputfid(fs, f);
+ Exputfid(fs, nf);
+ return up->errstr;
+ }
+ nf->chan = clone(f->chan, nil);
+ poperror();
+ Exputfid(fs, f);
+ Exputfid(fs, nf);
+ return nil;
+}
+
+char*
+Exclunk(Export *fs, Fcall *rpc)
+{
+ Fid *f;
+
+ f = Exgetfid(fs, rpc->fid);
+ if(f != nil){
+ f->attached = 0;
+ Exputfid(fs, f);
+ }
+ return nil;
+}
+
+char*
+Exwalk(Export *fs, Fcall *rpc)
+{
+ Fid *f;
+ Chan *c;
+
+ f = Exgetfid(fs, rpc->fid);
+ if(f == nil)
+ return Enofid;
+ if(waserror()){
+ Exputfid(fs, f);
+ return up->errstr;
+ }
+ c = walk(f->chan, rpc->name, 1);
+ if(c == nil)
+ error(Enonexist);
+ poperror();
+
+ f->chan = c;
+ rpc->qid = c->qid;
+ Exputfid(fs, f);
+ return nil;
+}
+
+char*
+Exopen(Export *fs, Fcall *rpc)
+{
+ Fid *f;
+ Chan *c;
+
+ f = Exgetfid(fs, rpc->fid);
+ if(f == nil)
+ return Enofid;
+ if(waserror()){
+ Exputfid(fs, f);
+ return up->errstr;
+ }
+ c = f->chan;
+ c = (*devtab[c->type].open)(c, rpc->mode);
+ poperror();
+
+ f->chan = c;
+ f->offset = 0;
+ rpc->qid = f->chan->qid;
+ Exputfid(fs, f);
+ return nil;
+}
+
+char*
+Excreate(Export *fs, Fcall *rpc)
+{
+ Fid *f;
+ Chan *c;
+
+ f = Exgetfid(fs, rpc->fid);
+ if(f == nil)
+ return Enofid;
+ if(waserror()){
+ Exputfid(fs, f);
+ return up->errstr;
+ }
+ c = f->chan;
+ if(c->mnt && !(c->flag&CCREATE))
+ c = createdir(c);
+ (*devtab[c->type].create)(c, rpc->name, rpc->mode, rpc->perm);
+ poperror();
+
+ f->chan = c;
+ rpc->qid = f->chan->qid;
+ Exputfid(fs, f);
+ return nil;
+}
+
+char*
+Exread(Export *fs, Fcall *rpc)
+{
+ Fid *f;
+ Chan *c;
+ long off;
+ int dir, n, seek;
+
+ f = Exgetfid(fs, rpc->fid);
+ if(f == nil)
+ return Enofid;
+
+ c = f->chan;
+ dir = c->qid.path & CHDIR;
+ if(dir){
+ rpc->count -= rpc->count%DIRLEN;
+ if(rpc->offset%DIRLEN || rpc->count==0){
+ Exputfid(fs, f);
+ return Ereaddir;
+ }
+ if(f->offset > rpc->offset){
+ Exputfid(fs, f);
+ return Eseekdir;
+ }
+ }
+
+ if(waserror()) {
+ Exputfid(fs, f);
+ return up->errstr;
+ }
+
+ for(;;){
+ n = rpc->count;
+ seek = 0;
+ off = rpc->offset;
+ if(dir && f->offset != off){
+ off = f->offset;
+ n = rpc->offset - off;
+ if(n > MAXDIRREAD)
+ n = MAXDIRREAD;
+ seek = 1;
+ }
+ if(dir && c->mnt != nil)
+ n = unionread(c, rpc->data, n);
+ else{
+ c->offset = off;
+ n = (*devtab[c->type].read)(c, rpc->data, n, off);
+ }
+ if(n == 0 || !seek)
+ break;
+ f->offset = off + n;
+ c->offset += n;
+ }
+ rpc->count = n;
+ poperror();
+ Exputfid(fs, f);
+ return nil;
+}
+
+char*
+Exwrite(Export *fs, Fcall *rpc)
+{
+ Fid *f;
+ Chan *c;
+
+ f = Exgetfid(fs, rpc->fid);
+ if(f == nil)
+ return Enofid;
+ if(waserror()){
+ Exputfid(fs, f);
+ return up->errstr;
+ }
+ c = f->chan;
+ if(c->qid.path & CHDIR)
+ error(Eisdir);
+ rpc->count = (*devtab[c->type].write)(c, rpc->data, rpc->count, rpc->offset);
+ poperror();
+ Exputfid(fs, f);
+ return nil;
+}
+
+char*
+Exstat(Export *fs, Fcall *rpc)
+{
+ Fid *f;
+ Chan *c;
+
+ f = Exgetfid(fs, rpc->fid);
+ if(f == nil)
+ return Enofid;
+ if(waserror()){
+ Exputfid(fs, f);
+ return up->errstr;
+ }
+ c = f->chan;
+ (*devtab[c->type].stat)(c, rpc->stat);
+ poperror();
+ Exputfid(fs, f);
+ return nil;
+}
+
+char*
+Exwstat(Export *fs, Fcall *rpc)
+{
+ Fid *f;
+ Chan *c;
+
+ f = Exgetfid(fs, rpc->fid);
+ if(f == nil)
+ return Enofid;
+ if(waserror()){
+ Exputfid(fs, f);
+ return up->errstr;
+ }
+ c = f->chan;
+ (*devtab[c->type].wstat)(c, rpc->stat);
+ poperror();
+ Exputfid(fs, f);
+ return nil;
+}
+
+char*
+Exremove(Export *fs, Fcall *rpc)
+{
+ Fid *f;
+ Chan *c;
+
+ f = Exgetfid(fs, rpc->fid);
+ if(f == nil)
+ return Enofid;
+ if(waserror()){
+ Exputfid(fs, f);
+ return up->errstr;
+ }
+ c = f->chan;
+ (*devtab[c->type].remove)(c);
+ poperror();
+
+ /*
+ * chan is already clunked by remove.
+ * however, we need to recover the chan,
+ * and follow sysremove's lead in making to point to root.
+ */
+ c->type = 0;
+
+ f->attached = 0;
+ Exputfid(fs, f);
+ return nil;
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/fns.h b/sys/src/cmd/unix/drawterm/kern/fns.h
new file mode 100755
index 000000000..97a935941
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/fns.h
@@ -0,0 +1,392 @@
+#define ROUND(s, sz) (((s)+((sz)-1))&~((sz)-1))
+
+void accounttime(void);
+void addclock0link(void (*)(void), int);
+int addphysseg(Physseg*);
+void addbootfile(char*, uchar*, ulong);
+Block* adjustblock(Block*, int);
+void alarmkproc(void*);
+Block* allocb(int);
+int anyhigher(void);
+int anyready(void);
+Page* auxpage(void);
+Block* bl2mem(uchar*, Block*, int);
+int blocklen(Block*);
+void callwithureg(void(*)(Ureg*));
+char* c2name(Chan*);
+int cangetc(void*);
+int canlock(Lock*);
+int canpage(Proc*);
+int canputc(void*);
+int canqlock(QLock*);
+int canrlock(RWlock*);
+void chandevinit(void);
+void chandevreset(void);
+void chandevshutdown(void);
+void chanfree(Chan*);
+void chanrec(Mnt*);
+void checkalarms(void);
+void checkb(Block*, char*);
+void cinit(void);
+Chan* cclone(Chan*);
+void cclose(Chan*);
+char* clipread(void);
+int clipwrite(char*);
+void closeegrp(Egrp*);
+void closefgrp(Fgrp*);
+void closemount(Mount*);
+void closepgrp(Pgrp*);
+void closergrp(Rgrp*);
+long clrfpintr(void);
+void cmderror(Cmdbuf*, char*);
+int cmount(Chan**, Chan*, int, char*);
+void cnameclose(Cname*);
+void confinit(void);
+void confinit1(int);
+int consactive(void);
+extern void (*consdebug)(void);
+void copen(Chan*);
+Block* concatblock(Block*);
+Block* copyblock(Block*, int);
+void copypage(Page*, Page*);
+int cread(Chan*, uchar*, int, vlong);
+void cunmount(Chan*, Chan*);
+void cupdate(Chan*, uchar*, int, vlong);
+void cwrite(Chan*, uchar*, int, vlong);
+ulong dbgpc(Proc*);
+int decref(Ref*);
+int decrypt(void*, void*, int);
+void delay(int);
+Chan* devattach(int, char*);
+Block* devbread(Chan*, long, ulong);
+long devbwrite(Chan*, Block*, ulong);
+Chan* devclone(Chan*);
+int devconfig(int, char *, DevConf *);
+void devcreate(Chan*, char*, int, ulong);
+void devdir(Chan*, Qid, char*, vlong, char*, long, Dir*);
+long devdirread(Chan*, char*, long, Dirtab*, int, Devgen*);
+Devgen devgen;
+void devinit(void);
+int devno(int, int);
+Chan* devopen(Chan*, int, Dirtab*, int, Devgen*);
+void devpermcheck(char*, ulong, int);
+void devpower(int);
+void devremove(Chan*);
+void devreset(void);
+void devshutdown(void);
+int devstat(Chan*, uchar*, int, Dirtab*, int, Devgen*);
+Walkqid* devwalk(Chan*, Chan*, char**, int, Dirtab*, int, Devgen*);
+int devwstat(Chan*, uchar*, int);
+void drawactive(int);
+void drawcmap(void);
+int drawcanqlock(void);
+void drawqlock(void);
+void drawqunlock(void);
+void dumpaproc(Proc*);
+void dumpqueues(void);
+void dumpregs(Ureg*);
+void dumpstack(void);
+Fgrp* dupfgrp(Fgrp*);
+void duppage(Page*);
+void dupswap(Page*);
+int emptystr(char*);
+int encrypt(void*, void*, int);
+void envcpy(Egrp*, Egrp*);
+int eqchan(Chan*, Chan*, int);
+int eqqid(Qid, Qid);
+void error(char*);
+long execregs(ulong, ulong, ulong);
+void exhausted(char*);
+void exit(int);
+uvlong fastticks(uvlong*);
+int fault(ulong, int);
+void fdclose(int, int);
+Chan* fdtochan(int, int, int, int);
+int fixfault(Segment*, ulong, int, int);
+void flushmmu(void);
+void forkchild(Proc*, Ureg*);
+void forkret(void);
+void free(void*);
+void freeb(Block*);
+void freeblist(Block*);
+int freebroken(void);
+void freepte(Segment*, Pte*);
+void freesegs(int);
+void freesession(Session*);
+ulong getmalloctag(void*);
+ulong getrealloctag(void*);
+void gotolabel(Label*);
+char* getconfenv(void);
+int haswaitq(void*);
+long hostdomainwrite(char*, int);
+long hostownerwrite(char*, int);
+void hzsched(void);
+void iallocinit(void);
+Block* iallocb(int);
+void iallocsummary(void);
+long ibrk(ulong, int);
+void ilock(Lock*);
+void iunlock(Lock*);
+int incref(Ref*);
+void initseg(void);
+int iprint(char*, ...);
+void isdir(Chan*);
+int iseve(void);
+#define islo() (0)
+Segment* isoverlap(Proc*, ulong, int);
+int ispages(void*);
+int isphysseg(char*);
+void ixsummary(void);
+void kbdclock(void);
+int kbdcr2nl(Queue*, int);
+int kbdputc(Queue*, int);
+void kbdrepeat(int);
+long keyread(char*, int, long);
+void kickpager(void);
+void killbig(void);
+int kproc(char*, void(*)(void*), void*);
+void kprocchild(Proc*, void (*)(void*), void*);
+extern void (*kproftimer)(ulong);
+void ksetenv(char*, char*, int);
+void kstrcpy(char*, char*, int);
+void kstrdup(char**, char*);
+long latin1(Rune*, int);
+void lock(Lock*);
+void lockinit(void);
+void logopen(Log*);
+void logclose(Log*);
+char* logctl(Log*, int, char**, Logflag*);
+void logn(Log*, int, void*, int);
+long logread(Log*, void*, ulong, long);
+void log(Log*, int, char*, ...);
+Cmdtab* lookupcmd(Cmdbuf*, Cmdtab*, int);
+void machinit(void);
+void* mallocz(ulong, int);
+#define malloc kmalloc
+void* malloc(ulong);
+void mallocsummary(void);
+Block* mem2bl(uchar*, int);
+void mfreeseg(Segment*, ulong, int);
+void microdelay(int);
+void mkqid(Qid*, vlong, ulong, int);
+void mmurelease(Proc*);
+void mmuswitch(Proc*);
+Chan* mntauth(Chan*, char*);
+void mntdump(void);
+long mntversion(Chan*, char*, int, int);
+void mountfree(Mount*);
+ulong ms2tk(ulong);
+ulong msize(void*);
+ulong ms2tk(ulong);
+uvlong ms2fastticks(ulong);
+void muxclose(Mnt*);
+Chan* namec(char*, int, int, ulong);
+Chan* newchan(void);
+int newfd(Chan*);
+Mhead* newmhead(Chan*);
+Mount* newmount(Mhead*, Chan*, int, char*);
+Page* newpage(int, Segment **, ulong);
+Pgrp* newpgrp(void);
+Rgrp* newrgrp(void);
+Proc* newproc(void);
+char* nextelem(char*, char*);
+void nexterror(void);
+Cname* newcname(char*);
+int notify(Ureg*);
+int nrand(int);
+int okaddr(ulong, ulong, int);
+int openmode(ulong);
+void oserrstr(void);
+void oserror(void);
+Block* packblock(Block*);
+Block* padblock(Block*, int);
+void pagechainhead(Page*);
+void pageinit(void);
+void pagersummary(void);
+void panic(char*, ...);
+Cmdbuf* parsecmd(char *a, int n);
+ulong perfticks(void);
+void pexit(char*, int);
+int preempted(void);
+void printinit(void);
+int procindex(ulong);
+void pgrpcpy(Pgrp*, Pgrp*);
+void pgrpnote(ulong, char*, long, int);
+Pgrp* pgrptab(int);
+void pio(Segment *, ulong, ulong, Page **);
+#define poperror() up->nerrlab--
+void portclock(Ureg*);
+int postnote(Proc*, int, char*, int);
+int pprint(char*, ...);
+void prflush(void);
+ulong procalarm(ulong);
+int proccounter(char *name);
+void procctl(Proc*);
+void procdump(void);
+int procfdprint(Chan*, int, int, char*, int);
+void procinit0(void);
+void procflushseg(Segment*);
+void procpriority(Proc*, int, int);
+Proc* proctab(int);
+void procwired(Proc*, int);
+Pte* ptealloc(void);
+Pte* ptecpy(Pte*);
+int pullblock(Block**, int);
+Block* pullupblock(Block*, int);
+Block* pullupqueue(Queue*, int);
+void putmhead(Mhead*);
+void putmmu(ulong, ulong, Page*);
+void putpage(Page*);
+void putseg(Segment*);
+void putstr(char*);
+void putstrn(char*, int);
+void putswap(Page*);
+ulong pwait(Waitmsg*);
+Label* pwaserror(void);
+void qaddlist(Queue*, Block*);
+Block* qbread(Queue*, int);
+long qbwrite(Queue*, Block*);
+Queue* qbypass(void (*)(void*, Block*), void*);
+int qcanread(Queue*);
+void qclose(Queue*);
+int qconsume(Queue*, void*, int);
+Block* qcopy(Queue*, int, ulong);
+int qdiscard(Queue*, int);
+void qflush(Queue*);
+void qfree(Queue*);
+int qfull(Queue*);
+Block* qget(Queue*);
+void qhangup(Queue*, char*);
+int qisclosed(Queue*);
+void qinit(void);
+int qiwrite(Queue*, void*, int);
+int qlen(Queue*);
+void qlock(QLock*);
+Queue* qopen(int, int, void (*)(void*), void*);
+int qpass(Queue*, Block*);
+int qpassnolim(Queue*, Block*);
+int qproduce(Queue*, void*, int);
+void qputback(Queue*, Block*);
+long qread(Queue*, void*, int);
+Block* qremove(Queue*);
+void qreopen(Queue*);
+void qsetlimit(Queue*, int);
+void qunlock(QLock*);
+int qwindow(Queue*);
+int qwrite(Queue*, void*, int);
+void qnoblock(Queue*, int);
+int rand(void);
+void randominit(void);
+ulong randomread(void*, ulong);
+void rdb(void);
+int readnum(ulong, char*, ulong, ulong, int);
+int readstr(ulong, char*, ulong, char*);
+void ready(Proc*);
+void rebootcmd(int, char**);
+void reboot(void*, void*, ulong);
+void relocateseg(Segment*, ulong);
+void renameuser(char*, char*);
+void resched(char*);
+void resrcwait(char*);
+int return0(void*);
+void rlock(RWlock*);
+long rtctime(void);
+void runlock(RWlock*);
+Proc* runproc(void);
+void savefpregs(FPsave*);
+extern void (*saveintrts)(void);
+void sched(void);
+void scheddump(void);
+void schedinit(void);
+extern void (*screenputs)(char*, int);
+long seconds(void);
+ulong segattach(Proc*, ulong, char *, ulong, ulong);
+void segclock(ulong);
+void segpage(Segment*, Page*);
+void setkernur(Ureg*, Proc*);
+int setlabel(Label*);
+void setmalloctag(void*, uintptr);
+void setrealloctag(void*, ulong);
+void setregisters(Ureg*, char*, char*, int);
+void setswapchan(Chan*);
+long showfilewrite(char*, int);
+char* skipslash(char*);
+void sleep(Rendez*, int(*)(void*), void*);
+void* smalloc(ulong);
+int splhi(void);
+int spllo(void);
+void splx(int);
+void splxpc(int);
+char* srvname(Chan*);
+int swapcount(ulong);
+int swapfull(void);
+void swapinit(void);
+void timeradd(Timer*);
+void timerdel(Timer*);
+void timersinit(void);
+void timerintr(Ureg*, uvlong);
+void timerset(uvlong);
+ulong tk2ms(ulong);
+#define TK2MS(x) ((x)*(1000/HZ))
+vlong todget(vlong*);
+void todfix(void);
+void todsetfreq(vlong);
+void todinit(void);
+void todset(vlong, vlong, int);
+Block* trimblock(Block*, int, int);
+void tsleep(Rendez*, int (*)(void*), void*, int);
+int uartctl(Uart*, char*);
+int uartgetc(void);
+void uartkick(void*);
+void uartmouse(Uart*, int (*)(Queue*, int), int);
+void uartputc(int);
+void uartputs(char*, int);
+void uartrecv(Uart*, char);
+Uart* uartsetup(Uart*);
+int uartstageoutput(Uart*);
+void unbreak(Proc*);
+void uncachepage(Page*);
+long unionread(Chan*, void*, long);
+void unlock(Lock*);
+Proc** uploc(void);
+void userinit(void);
+ulong userpc(void);
+long userwrite(char*, int);
+#define validaddr(a, b, c)
+void validname(char*, int);
+void validstat(uchar*, int);
+void vcacheinval(Page*, ulong);
+void* vmemchr(void*, int, int);
+Proc* wakeup(Rendez*);
+int walk(Chan**, char**, int, int, int*);
+#define waserror() (setjmp(pwaserror()->buf))
+void wlock(RWlock*);
+void wunlock(RWlock*);
+void* xalloc(ulong);
+void* xallocz(ulong, int);
+void xfree(void*);
+void xhole(ulong, ulong);
+void xinit(void);
+int xmerge(void*, void*);
+void* xspanalloc(ulong, int, ulong);
+void xsummary(void);
+void yield(void);
+Segment* data2txt(Segment*);
+Segment* dupseg(Segment**, int, int);
+Segment* newseg(int, ulong, ulong);
+Segment* seg(Proc*, ulong, int);
+void hnputv(void*, vlong);
+void hnputl(void*, ulong);
+void hnputs(void*, ushort);
+vlong nhgetv(void*);
+ulong nhgetl(void*);
+ushort nhgets(void*);
+ulong ticks(void);
+void osproc(Proc*);
+void osnewproc(Proc*);
+void procsleep(void);
+void procwakeup(Proc*);
+void osinit(void);
+void screeninit(void);
+extern void terminit(void);
+
diff --git a/sys/src/cmd/unix/drawterm/kern/netif.h b/sys/src/cmd/unix/drawterm/kern/netif.h
new file mode 100755
index 000000000..06c42aec8
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/netif.h
@@ -0,0 +1,133 @@
+typedef struct Etherpkt Etherpkt;
+typedef struct Netaddr Netaddr;
+typedef struct Netfile Netfile;
+typedef struct Netif Netif;
+
+enum
+{
+ Nmaxaddr= 64,
+ Nmhash= 31,
+
+ Ncloneqid= 1,
+ Naddrqid,
+ N2ndqid,
+ N3rdqid,
+ Ndataqid,
+ Nctlqid,
+ Nstatqid,
+ Ntypeqid,
+ Nifstatqid,
+};
+
+/*
+ * Macros to manage Qid's used for multiplexed devices
+ */
+#define NETTYPE(x) (((ulong)x)&0x1f)
+#define NETID(x) ((((ulong)x))>>5)
+#define NETQID(i,t) ((((ulong)i)<<5)|(t))
+
+/*
+ * one per multiplexed connection
+ */
+struct Netfile
+{
+ QLock lk;
+
+ int inuse;
+ ulong mode;
+ char owner[KNAMELEN];
+
+ int type; /* multiplexor type */
+ int prom; /* promiscuous mode */
+ int scan; /* base station scanning interval */
+ int bridge; /* bridge mode */
+ int headersonly; /* headers only - no data */
+ uchar maddr[8]; /* bitmask of multicast addresses requested */
+ int nmaddr; /* number of multicast addresses */
+
+ Queue *in; /* input buffer */
+};
+
+/*
+ * a network address
+ */
+struct Netaddr
+{
+ Netaddr *next; /* allocation chain */
+ Netaddr *hnext;
+ uchar addr[Nmaxaddr];
+ int ref;
+};
+
+/*
+ * a network interface
+ */
+struct Netif
+{
+ QLock lk;
+
+ /* multiplexing */
+ char name[KNAMELEN]; /* for top level directory */
+ int nfile; /* max number of Netfiles */
+ Netfile **f;
+
+ /* about net */
+ int limit; /* flow control */
+ int alen; /* address length */
+ int mbps; /* megabits per sec */
+ uchar addr[Nmaxaddr];
+ uchar bcast[Nmaxaddr];
+ Netaddr *maddr; /* known multicast addresses */
+ int nmaddr; /* number of known multicast addresses */
+ Netaddr *mhash[Nmhash]; /* hash table of multicast addresses */
+ int prom; /* number of promiscuous opens */
+ int scan; /* number of base station scanners */
+ int all; /* number of -1 multiplexors */
+
+ /* statistics */
+ int misses;
+ int inpackets;
+ int outpackets;
+ int crcs; /* input crc errors */
+ int oerrs; /* output errors */
+ int frames; /* framing errors */
+ int overflows; /* packet overflows */
+ int buffs; /* buffering errors */
+ int soverflows; /* software overflow */
+
+ /* routines for touching the hardware */
+ void *arg;
+ void (*promiscuous)(void*, int);
+ void (*multicast)(void*, uchar*, int);
+ void (*scanbs)(void*, uint); /* scan for base stations */
+};
+
+void netifinit(Netif*, char*, int, ulong);
+Walkqid* netifwalk(Netif*, Chan*, Chan*, char **, int);
+Chan* netifopen(Netif*, Chan*, int);
+void netifclose(Netif*, Chan*);
+long netifread(Netif*, Chan*, void*, long, ulong);
+Block* netifbread(Netif*, Chan*, long, ulong);
+long netifwrite(Netif*, Chan*, void*, long);
+int netifwstat(Netif*, Chan*, uchar*, int);
+int netifstat(Netif*, Chan*, uchar*, int);
+int activemulti(Netif*, uchar*, int);
+
+/*
+ * Ethernet specific
+ */
+enum
+{
+ Eaddrlen= 6,
+ ETHERMINTU = 60, /* minimum transmit size */
+ ETHERMAXTU = 1514, /* maximum transmit size */
+ ETHERHDRSIZE = 14, /* size of an ethernet header */
+};
+
+struct Etherpkt
+{
+ uchar d[Eaddrlen];
+ uchar s[Eaddrlen];
+ uchar type[2];
+ uchar data[1500];
+};
diff --git a/sys/src/cmd/unix/drawterm/kern/parse.c b/sys/src/cmd/unix/drawterm/kern/parse.c
new file mode 100755
index 000000000..8c991f8dc
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/parse.c
@@ -0,0 +1,113 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+/*
+ * Generous estimate of number of fields, including terminal nil pointer
+ */
+static int
+ncmdfield(char *p, int n)
+{
+ int white, nwhite;
+ char *ep;
+ int nf;
+
+ if(p == nil)
+ return 1;
+
+ nf = 0;
+ ep = p+n;
+ white = 1; /* first text will start field */
+ while(p < ep){
+ nwhite = (strchr(" \t\r\n", *p++ & 0xFF) != 0); /* UTF is irrelevant */
+ if(white && !nwhite) /* beginning of field */
+ nf++;
+ white = nwhite;
+ }
+ return nf+1; /* +1 for nil */
+}
+
+/*
+ * parse a command written to a device
+ */
+Cmdbuf*
+parsecmd(char *p, int n)
+{
+ Cmdbuf *volatile cb;
+ int nf;
+ char *sp;
+
+ nf = ncmdfield(p, n);
+
+ /* allocate Cmdbuf plus string pointers plus copy of string including \0 */
+ sp = smalloc(sizeof(*cb) + nf * sizeof(char*) + n + 1);
+ cb = (Cmdbuf*)sp;
+ cb->f = (char**)(&cb[1]);
+ cb->buf = (char*)(&cb->f[nf]);
+
+ if(up!=nil && waserror()){
+ free(cb);
+ nexterror();
+ }
+ memmove(cb->buf, p, n);
+ if(up != nil)
+ poperror();
+
+ /* dump new line and null terminate */
+ if(n > 0 && cb->buf[n-1] == '\n')
+ n--;
+ cb->buf[n] = '\0';
+
+ cb->nf = tokenize(cb->buf, cb->f, nf-1);
+ cb->f[cb->nf] = nil;
+
+ return cb;
+}
+
+/*
+ * Reconstruct original message, for error diagnostic
+ */
+void
+cmderror(Cmdbuf *cb, char *s)
+{
+ int i;
+ char *p, *e;
+
+ p = up->genbuf;
+ e = p+ERRMAX-10;
+ p = seprint(p, e, "%s \"", s);
+ for(i=0; i<cb->nf; i++){
+ if(i > 0)
+ p = seprint(p, e, " ");
+ p = seprint(p, e, "%q", cb->f[i]);
+ }
+ strcpy(p, "\"");
+ error(up->genbuf);
+}
+
+/*
+ * Look up entry in table
+ */
+Cmdtab*
+lookupcmd(Cmdbuf *cb, Cmdtab *ctab, int nctab)
+{
+ int i;
+ Cmdtab *ct;
+
+ if(cb->nf == 0)
+ error("empty control message");
+
+ for(ct = ctab, i=0; i<nctab; i++, ct++){
+ if(strcmp(ct->cmd, "*") !=0) /* wildcard always matches */
+ if(strcmp(ct->cmd, cb->f[0]) != 0)
+ continue;
+ if(ct->narg != 0 && ct->narg != cb->nf)
+ cmderror(cb, Ecmdargs);
+ return ct;
+ }
+
+ cmderror(cb, "unknown control message");
+ return nil;
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/pgrp.c b/sys/src/cmd/unix/drawterm/kern/pgrp.c
new file mode 100755
index 000000000..30b1f3e7b
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/pgrp.c
@@ -0,0 +1,272 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+static Ref pgrpid;
+static Ref mountid;
+
+#ifdef NOTDEF
+void
+pgrpnote(ulong noteid, char *a, long n, int flag)
+{
+ Proc *p, *ep;
+ char buf[ERRMAX];
+
+ if(n >= ERRMAX-1)
+ error(Etoobig);
+
+ memmove(buf, a, n);
+ buf[n] = 0;
+ p = proctab(0);
+ ep = p+conf.nproc;
+ for(; p < ep; p++) {
+ if(p->state == Dead)
+ continue;
+ if(up != p && p->noteid == noteid && p->kp == 0) {
+ qlock(&p->debug);
+ if(p->pid == 0 || p->noteid != noteid){
+ qunlock(&p->debug);
+ continue;
+ }
+ if(!waserror()) {
+ postnote(p, 0, buf, flag);
+ poperror();
+ }
+ qunlock(&p->debug);
+ }
+ }
+}
+#endif
+
+Pgrp*
+newpgrp(void)
+{
+ Pgrp *p;
+
+ p = smalloc(sizeof(Pgrp));
+ p->ref.ref = 1;
+ p->pgrpid = incref(&pgrpid);
+ return p;
+}
+
+Rgrp*
+newrgrp(void)
+{
+ Rgrp *r;
+
+ r = smalloc(sizeof(Rgrp));
+ r->ref.ref = 1;
+ return r;
+}
+
+void
+closergrp(Rgrp *r)
+{
+ if(decref(&r->ref) == 0)
+ free(r);
+}
+
+void
+closepgrp(Pgrp *p)
+{
+ Mhead **h, **e, *f, *next;
+
+ if(decref(&p->ref) != 0)
+ return;
+
+ qlock(&p->debug);
+ wlock(&p->ns);
+ p->pgrpid = -1;
+
+ e = &p->mnthash[MNTHASH];
+ for(h = p->mnthash; h < e; h++) {
+ for(f = *h; f; f = next) {
+ wlock(&f->lock);
+ cclose(f->from);
+ mountfree(f->mount);
+ f->mount = nil;
+ next = f->hash;
+ wunlock(&f->lock);
+ putmhead(f);
+ }
+ }
+ wunlock(&p->ns);
+ qunlock(&p->debug);
+ free(p);
+}
+
+void
+pgrpinsert(Mount **order, Mount *m)
+{
+ Mount *f;
+
+ m->order = 0;
+ if(*order == 0) {
+ *order = m;
+ return;
+ }
+ for(f = *order; f; f = f->order) {
+ if(m->mountid < f->mountid) {
+ m->order = f;
+ *order = m;
+ return;
+ }
+ order = &f->order;
+ }
+ *order = m;
+}
+
+/*
+ * pgrpcpy MUST preserve the mountid allocation order of the parent group
+ */
+void
+pgrpcpy(Pgrp *to, Pgrp *from)
+{
+ int i;
+ Mount *n, *m, **link, *order;
+ Mhead *f, **tom, **l, *mh;
+
+ wlock(&from->ns);
+ order = 0;
+ tom = to->mnthash;
+ for(i = 0; i < MNTHASH; i++) {
+ l = tom++;
+ for(f = from->mnthash[i]; f; f = f->hash) {
+ rlock(&f->lock);
+ mh = newmhead(f->from);
+ *l = mh;
+ l = &mh->hash;
+ link = &mh->mount;
+ for(m = f->mount; m; m = m->next) {
+ n = newmount(mh, m->to, m->mflag, m->spec);
+ m->copy = n;
+ pgrpinsert(&order, m);
+ *link = n;
+ link = &n->next;
+ }
+ runlock(&f->lock);
+ }
+ }
+ /*
+ * Allocate mount ids in the same sequence as the parent group
+ */
+ lock(&mountid.lk);
+ for(m = order; m; m = m->order)
+ m->copy->mountid = mountid.ref++;
+ unlock(&mountid.lk);
+ wunlock(&from->ns);
+}
+
+Fgrp*
+dupfgrp(Fgrp *f)
+{
+ Fgrp *new;
+ Chan *c;
+ int i;
+
+ new = smalloc(sizeof(Fgrp));
+ if(f == nil){
+ new->fd = smalloc(DELTAFD*sizeof(Chan*));
+ new->nfd = DELTAFD;
+ new->ref.ref = 1;
+ return new;
+ }
+
+ lock(&f->ref.lk);
+ /* Make new fd list shorter if possible, preserving quantization */
+ new->nfd = f->maxfd+1;
+ i = new->nfd%DELTAFD;
+ if(i != 0)
+ new->nfd += DELTAFD - i;
+ new->fd = malloc(new->nfd*sizeof(Chan*));
+ if(new->fd == 0){
+ unlock(&f->ref.lk);
+ error("no memory for fgrp");
+ }
+ new->ref.ref = 1;
+
+ new->maxfd = f->maxfd;
+ for(i = 0; i <= f->maxfd; i++) {
+ if((c = f->fd[i])){
+ incref(&c->ref);
+ new->fd[i] = c;
+ }
+ }
+ unlock(&f->ref.lk);
+
+ return new;
+}
+
+void
+closefgrp(Fgrp *f)
+{
+ int i;
+ Chan *c;
+
+ if(f == 0)
+ return;
+
+ if(decref(&f->ref) != 0)
+ return;
+
+ for(i = 0; i <= f->maxfd; i++)
+ if((c = f->fd[i]))
+ cclose(c);
+
+ free(f->fd);
+ free(f);
+}
+
+Mount*
+newmount(Mhead *mh, Chan *to, int flag, char *spec)
+{
+ Mount *m;
+
+ m = smalloc(sizeof(Mount));
+ m->to = to;
+ m->head = mh;
+ incref(&to->ref);
+ m->mountid = incref(&mountid);
+ m->mflag = flag;
+ if(spec != 0)
+ kstrdup(&m->spec, spec);
+
+ return m;
+}
+
+void
+mountfree(Mount *m)
+{
+ Mount *f;
+
+ while(m) {
+ f = m->next;
+ cclose(m->to);
+ m->mountid = 0;
+ free(m->spec);
+ free(m);
+ m = f;
+ }
+}
+
+#ifdef NOTDEF
+void
+resrcwait(char *reason)
+{
+ char *p;
+
+ if(up == 0)
+ panic("resrcwait");
+
+ p = up->psstate;
+ if(reason) {
+ up->psstate = reason;
+ print("%s\n", reason);
+ }
+
+ tsleep(&up->sleep, return0, 0, 300);
+ up->psstate = p;
+}
+#endif
diff --git a/sys/src/cmd/unix/drawterm/kern/posix.c b/sys/src/cmd/unix/drawterm/kern/posix.c
new file mode 100755
index 000000000..069d6531f
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/posix.c
@@ -0,0 +1,225 @@
+/*
+ * Posix generic OS implementation for drawterm.
+ */
+
+#include "u.h"
+
+#ifndef _XOPEN_SOURCE /* for Apple and OpenBSD; not sure if needed */
+#define _XOPEN_SOURCE 500
+#endif
+
+#include <pthread.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/select.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+
+typedef struct Oproc Oproc;
+struct Oproc
+{
+ int nsleep;
+ int nwakeup;
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+};
+
+static pthread_key_t prdakey;
+
+Proc*
+_getproc(void)
+{
+ void *v;
+
+ if((v = pthread_getspecific(prdakey)) == nil)
+ panic("cannot getspecific");
+ return v;
+}
+
+void
+_setproc(Proc *p)
+{
+ if(pthread_setspecific(prdakey, p) != 0)
+ panic("cannot setspecific");
+}
+
+void
+osinit(void)
+{
+ if(pthread_key_create(&prdakey, 0))
+ panic("cannot pthread_key_create");
+}
+
+#undef pipe
+void
+osnewproc(Proc *p)
+{
+ Oproc *op;
+ pthread_mutexattr_t attr;
+
+ op = (Oproc*)p->oproc;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
+ pthread_mutex_init(&op->mutex, &attr);
+ pthread_mutexattr_destroy(&attr);
+ pthread_cond_init(&op->cond, 0);
+}
+
+void
+osmsleep(int ms)
+{
+ struct timeval tv;
+
+ tv.tv_sec = ms / 1000;
+ tv.tv_usec = (ms % 1000) * 1000; /* micro */
+ if(select(0, NULL, NULL, NULL, &tv) < 0)
+ panic("select");
+}
+
+void
+osyield(void)
+{
+ sched_yield();
+}
+
+void
+oserrstr(void)
+{
+ char *p;
+
+ if((p = strerror(errno)) != nil)
+ strecpy(up->errstr, up->errstr+ERRMAX, p);
+ else
+ snprint(up->errstr, ERRMAX, "unix error %d", errno);
+}
+
+void
+oserror(void)
+{
+ oserrstr();
+ nexterror();
+}
+
+static void* tramp(void*);
+
+void
+osproc(Proc *p)
+{
+ pthread_t pid;
+
+ if(pthread_create(&pid, nil, tramp, p)){
+ oserrstr();
+ panic("osproc: %r");
+ }
+ sched_yield();
+}
+
+static void*
+tramp(void *vp)
+{
+ 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
+procsleep(void)
+{
+ Proc *p;
+ Oproc *op;
+
+ p = up;
+ op = (Oproc*)p->oproc;
+ pthread_mutex_lock(&op->mutex);
+ op->nsleep++;
+ while(op->nsleep > op->nwakeup)
+ pthread_cond_wait(&op->cond, &op->mutex);
+ pthread_mutex_unlock(&op->mutex);
+}
+
+void
+procwakeup(Proc *p)
+{
+ Oproc *op;
+
+ op = (Oproc*)p->oproc;
+ pthread_mutex_lock(&op->mutex);
+ op->nwakeup++;
+ if(op->nwakeup == op->nsleep)
+ pthread_cond_signal(&op->cond);
+ pthread_mutex_unlock(&op->mutex);
+}
+
+int randfd;
+#undef open
+void
+randominit(void)
+{
+#ifdef USE_RANDOM
+ srandom(getpid()+fastticks(nil)+ticks());
+#else
+ if((randfd = open("/dev/urandom", OREAD)) < 0)
+ if((randfd = open("/dev/random", OREAD)) < 0)
+ panic("open /dev/random: %r");
+#endif
+}
+
+#undef read
+ulong
+randomread(void *v, ulong n)
+{
+#ifdef USE_RANDOM
+ int i;
+
+ for(i=0; i<n; i++)
+ ((uchar*)v)[i] = random();
+ return n;
+#else
+ int m;
+
+ if((m = read(randfd, v, n)) != n)
+ panic("short read from /dev/random: %d but %d", n, m);
+ return m;
+#endif
+}
+
+#undef time
+long
+seconds(void)
+{
+ return time(0);
+}
+
+ulong
+ticks(void)
+{
+ static long sec0 = 0, usec0;
+ struct timeval t;
+
+ if(gettimeofday(&t, nil) < 0)
+ return 0;
+ if(sec0 == 0){
+ sec0 = t.tv_sec;
+ usec0 = t.tv_usec;
+ }
+ return (t.tv_sec-sec0)*1000+(t.tv_usec-usec0+500)/1000;
+}
+
+long
+showfilewrite(char *a, int n)
+{
+ error("not implemented");
+ return -1;
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/procinit.c b/sys/src/cmd/unix/drawterm/kern/procinit.c
new file mode 100755
index 000000000..b2299d963
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/procinit.c
@@ -0,0 +1,67 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+Rgrp *thergrp;
+
+void
+procinit0(void)
+{
+ Proc *p;
+
+ p = newproc();
+ p->fgrp = dupfgrp(nil);
+ p->rgrp = newrgrp();
+ p->pgrp = newpgrp();
+ _setproc(p);
+
+ up->slash = namec("#/", Atodir, 0, 0);
+ cnameclose(up->slash->name);
+ up->slash->name = newcname("/");
+ up->dot = cclone(up->slash);
+}
+
+Ref pidref;
+
+Proc*
+newproc(void)
+{
+ Proc *p;
+
+ p = mallocz(sizeof(Proc), 1);
+ p->pid = incref(&pidref);
+ strcpy(p->user, eve);
+ p->syserrstr = p->errbuf0;
+ p->errstr = p->errbuf1;
+ strcpy(p->text, "drawterm");
+ osnewproc(p);
+ return p;
+}
+
+int
+kproc(char *name, void (*fn)(void*), void *arg)
+{
+ Proc *p;
+
+ p = newproc();
+ p->fn = fn;
+ p->arg = arg;
+ p->slash = cclone(up->slash);
+ p->dot = cclone(up->dot);
+ p->rgrp = up->rgrp;
+ if(p->rgrp)
+ incref(&p->rgrp->ref);
+ p->pgrp = up->pgrp;
+ if(up->pgrp)
+ incref(&up->pgrp->ref);
+ p->fgrp = up->fgrp;
+ if(p->fgrp)
+ incref(&p->fgrp->ref);
+ strecpy(p->text, p->text+sizeof p->text, name);
+
+ osproc(p);
+ return p->pid;
+}
+
diff --git a/sys/src/cmd/unix/drawterm/kern/qio.c b/sys/src/cmd/unix/drawterm/kern/qio.c
new file mode 100755
index 000000000..edee200be
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/qio.c
@@ -0,0 +1,1524 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+static ulong padblockcnt;
+static ulong concatblockcnt;
+static ulong pullupblockcnt;
+static ulong copyblockcnt;
+static ulong consumecnt;
+static ulong producecnt;
+static ulong qcopycnt;
+
+static int debugging;
+
+#define QDEBUG if(0)
+
+/*
+ * IO queues
+ */
+struct Queue
+{
+ Lock lk;
+
+ Block* bfirst; /* buffer */
+ Block* blast;
+
+ int len; /* bytes allocated to queue */
+ int dlen; /* data bytes in queue */
+ int limit; /* max bytes in queue */
+ int inilim; /* initial limit */
+ int state;
+ int noblock; /* true if writes return immediately when q full */
+ int eof; /* number of eofs read by user */
+
+ void (*kick)(void*); /* restart output */
+ void (*bypass)(void*, Block*); /* bypass queue altogether */
+ void* arg; /* argument to kick */
+
+ QLock rlock; /* mutex for reading processes */
+ Rendez rr; /* process waiting to read */
+ QLock wlock; /* mutex for writing processes */
+ Rendez wr; /* process waiting to write */
+
+ char err[ERRMAX];
+};
+
+enum
+{
+ Maxatomic = 64*1024,
+};
+
+uint qiomaxatomic = Maxatomic;
+
+void
+ixsummary(void)
+{
+ debugging ^= 1;
+ iallocsummary();
+ print("pad %lud, concat %lud, pullup %lud, copy %lud\n",
+ padblockcnt, concatblockcnt, pullupblockcnt, copyblockcnt);
+ print("consume %lud, produce %lud, qcopy %lud\n",
+ consumecnt, producecnt, qcopycnt);
+}
+
+/*
+ * free a list of blocks
+ */
+void
+freeblist(Block *b)
+{
+ Block *next;
+
+ for(; b != 0; b = next){
+ next = b->next;
+ b->next = 0;
+ freeb(b);
+ }
+}
+
+/*
+ * pad a block to the front (or the back if size is negative)
+ */
+Block*
+padblock(Block *bp, int size)
+{
+ int n;
+ Block *nbp;
+
+ QDEBUG checkb(bp, "padblock 1");
+ if(size >= 0){
+ if(bp->rp - bp->base >= size){
+ bp->rp -= size;
+ return bp;
+ }
+
+ if(bp->next)
+ panic("padblock 0x%p", getcallerpc(&bp));
+ n = BLEN(bp);
+ padblockcnt++;
+ nbp = allocb(size+n);
+ nbp->rp += size;
+ nbp->wp = nbp->rp;
+ memmove(nbp->wp, bp->rp, n);
+ nbp->wp += n;
+ freeb(bp);
+ nbp->rp -= size;
+ } else {
+ size = -size;
+
+ if(bp->next)
+ panic("padblock 0x%p", getcallerpc(&bp));
+
+ if(bp->lim - bp->wp >= size)
+ return bp;
+
+ n = BLEN(bp);
+ padblockcnt++;
+ nbp = allocb(size+n);
+ memmove(nbp->wp, bp->rp, n);
+ nbp->wp += n;
+ freeb(bp);
+ }
+ QDEBUG checkb(nbp, "padblock 1");
+ return nbp;
+}
+
+/*
+ * return count of bytes in a string of blocks
+ */
+int
+blocklen(Block *bp)
+{
+ int len;
+
+ len = 0;
+ while(bp) {
+ len += BLEN(bp);
+ bp = bp->next;
+ }
+ return len;
+}
+
+/*
+ * return count of space in blocks
+ */
+int
+blockalloclen(Block *bp)
+{
+ int len;
+
+ len = 0;
+ while(bp) {
+ len += BALLOC(bp);
+ bp = bp->next;
+ }
+ return len;
+}
+
+/*
+ * copy the string of blocks into
+ * a single block and free the string
+ */
+Block*
+concatblock(Block *bp)
+{
+ int len;
+ Block *nb, *f;
+
+ if(bp->next == 0)
+ return bp;
+
+ nb = allocb(blocklen(bp));
+ for(f = bp; f; f = f->next) {
+ len = BLEN(f);
+ memmove(nb->wp, f->rp, len);
+ nb->wp += len;
+ }
+ concatblockcnt += BLEN(nb);
+ freeblist(bp);
+ QDEBUG checkb(nb, "concatblock 1");
+ return nb;
+}
+
+/*
+ * make sure the first block has at least n bytes
+ */
+Block*
+pullupblock(Block *bp, int n)
+{
+ int i;
+ Block *nbp;
+
+ /*
+ * this should almost always be true, it's
+ * just to avoid every caller checking.
+ */
+ if(BLEN(bp) >= n)
+ return bp;
+
+ /*
+ * if not enough room in the first block,
+ * add another to the front of the list.
+ */
+ if(bp->lim - bp->rp < n){
+ nbp = allocb(n);
+ nbp->next = bp;
+ bp = nbp;
+ }
+
+ /*
+ * copy bytes from the trailing blocks into the first
+ */
+ n -= BLEN(bp);
+ while((nbp = bp->next)){
+ i = BLEN(nbp);
+ if(i > n) {
+ memmove(bp->wp, nbp->rp, n);
+ pullupblockcnt++;
+ bp->wp += n;
+ nbp->rp += n;
+ QDEBUG checkb(bp, "pullupblock 1");
+ return bp;
+ } else {
+ /* shouldn't happen but why crash if it does */
+ if(i < 0){
+ print("pullup negative length packet\n");
+ i = 0;
+ }
+ memmove(bp->wp, nbp->rp, i);
+ pullupblockcnt++;
+ bp->wp += i;
+ bp->next = nbp->next;
+ nbp->next = 0;
+ freeb(nbp);
+ n -= i;
+ if(n == 0){
+ QDEBUG checkb(bp, "pullupblock 2");
+ return bp;
+ }
+ }
+ }
+ freeb(bp);
+ return 0;
+}
+
+/*
+ * make sure the first block has at least n bytes
+ */
+Block*
+pullupqueue(Queue *q, int n)
+{
+ Block *b;
+
+ if(BLEN(q->bfirst) >= n)
+ return q->bfirst;
+ q->bfirst = pullupblock(q->bfirst, n);
+ for(b = q->bfirst; b != nil && b->next != nil; b = b->next)
+ ;
+ q->blast = b;
+ return q->bfirst;
+}
+
+/*
+ * trim to len bytes starting at offset
+ */
+Block *
+trimblock(Block *bp, int offset, int len)
+{
+ ulong l;
+ Block *nb, *startb;
+
+ QDEBUG checkb(bp, "trimblock 1");
+ if(blocklen(bp) < offset+len) {
+ freeblist(bp);
+ return nil;
+ }
+
+ while((l = BLEN(bp)) < offset) {
+ offset -= l;
+ nb = bp->next;
+ bp->next = nil;
+ freeb(bp);
+ bp = nb;
+ }
+
+ startb = bp;
+ bp->rp += offset;
+
+ while((l = BLEN(bp)) < len) {
+ len -= l;
+ bp = bp->next;
+ }
+
+ bp->wp -= (BLEN(bp) - len);
+
+ if(bp->next) {
+ freeblist(bp->next);
+ bp->next = nil;
+ }
+
+ return startb;
+}
+
+/*
+ * copy 'count' bytes into a new block
+ */
+Block*
+copyblock(Block *bp, int count)
+{
+ int l;
+ Block *nbp;
+
+ QDEBUG checkb(bp, "copyblock 0");
+ nbp = allocb(count);
+ for(; count > 0 && bp != 0; bp = bp->next){
+ l = BLEN(bp);
+ if(l > count)
+ l = count;
+ memmove(nbp->wp, bp->rp, l);
+ nbp->wp += l;
+ count -= l;
+ }
+ if(count > 0){
+ memset(nbp->wp, 0, count);
+ nbp->wp += count;
+ }
+ copyblockcnt++;
+ QDEBUG checkb(nbp, "copyblock 1");
+
+ return nbp;
+}
+
+Block*
+adjustblock(Block* bp, int len)
+{
+ int n;
+ Block *nbp;
+
+ if(len < 0){
+ freeb(bp);
+ return nil;
+ }
+
+ if(bp->rp+len > bp->lim){
+ nbp = copyblock(bp, len);
+ freeblist(bp);
+ QDEBUG checkb(nbp, "adjustblock 1");
+
+ return nbp;
+ }
+
+ n = BLEN(bp);
+ if(len > n)
+ memset(bp->wp, 0, len-n);
+ bp->wp = bp->rp+len;
+ QDEBUG checkb(bp, "adjustblock 2");
+
+ return bp;
+}
+
+
+/*
+ * throw away up to count bytes from a
+ * list of blocks. Return count of bytes
+ * thrown away.
+ */
+int
+pullblock(Block **bph, int count)
+{
+ Block *bp;
+ int n, bytes;
+
+ bytes = 0;
+ if(bph == nil)
+ return 0;
+
+ while(*bph != nil && count != 0) {
+ bp = *bph;
+ n = BLEN(bp);
+ if(count < n)
+ n = count;
+ bytes += n;
+ count -= n;
+ bp->rp += n;
+ QDEBUG checkb(bp, "pullblock ");
+ if(BLEN(bp) == 0) {
+ *bph = bp->next;
+ bp->next = nil;
+ freeb(bp);
+ }
+ }
+ return bytes;
+}
+
+/*
+ * get next block from a queue, return null if nothing there
+ */
+Block*
+qget(Queue *q)
+{
+ int dowakeup;
+ Block *b;
+
+ /* sync with qwrite */
+ ilock(&q->lk);
+
+ b = q->bfirst;
+ if(b == nil){
+ q->state |= Qstarve;
+ iunlock(&q->lk);
+ return nil;
+ }
+ q->bfirst = b->next;
+ b->next = 0;
+ q->len -= BALLOC(b);
+ q->dlen -= BLEN(b);
+ QDEBUG checkb(b, "qget");
+
+ /* if writer flow controlled, restart */
+ if((q->state & Qflow) && q->len < q->limit/2){
+ q->state &= ~Qflow;
+ dowakeup = 1;
+ } else
+ dowakeup = 0;
+
+ iunlock(&q->lk);
+
+ if(dowakeup)
+ wakeup(&q->wr);
+
+ return b;
+}
+
+/*
+ * throw away the next 'len' bytes in the queue
+ */
+int
+qdiscard(Queue *q, int len)
+{
+ Block *b;
+ int dowakeup, n, sofar;
+
+ ilock(&q->lk);
+ for(sofar = 0; sofar < len; sofar += n){
+ b = q->bfirst;
+ if(b == nil)
+ break;
+ QDEBUG checkb(b, "qdiscard");
+ n = BLEN(b);
+ if(n <= len - sofar){
+ q->bfirst = b->next;
+ b->next = 0;
+ q->len -= BALLOC(b);
+ q->dlen -= BLEN(b);
+ freeb(b);
+ } else {
+ n = len - sofar;
+ b->rp += n;
+ q->dlen -= n;
+ }
+ }
+
+ /*
+ * if writer flow controlled, restart
+ *
+ * This used to be
+ * q->len < q->limit/2
+ * but it slows down tcp too much for certain write sizes.
+ * I really don't understand it completely. It may be
+ * due to the queue draining so fast that the transmission
+ * stalls waiting for the app to produce more data. - presotto
+ */
+ if((q->state & Qflow) && q->len < q->limit){
+ q->state &= ~Qflow;
+ dowakeup = 1;
+ } else
+ dowakeup = 0;
+
+ iunlock(&q->lk);
+
+ if(dowakeup)
+ wakeup(&q->wr);
+
+ return sofar;
+}
+
+/*
+ * Interrupt level copy out of a queue, return # bytes copied.
+ */
+int
+qconsume(Queue *q, void *vp, int len)
+{
+ Block *b;
+ int n, dowakeup;
+ uchar *p = vp;
+ Block *tofree = nil;
+
+ /* sync with qwrite */
+ ilock(&q->lk);
+
+ for(;;) {
+ b = q->bfirst;
+ if(b == 0){
+ q->state |= Qstarve;
+ iunlock(&q->lk);
+ return -1;
+ }
+ QDEBUG checkb(b, "qconsume 1");
+
+ n = BLEN(b);
+ if(n > 0)
+ break;
+ q->bfirst = b->next;
+ q->len -= BALLOC(b);
+
+ /* remember to free this */
+ b->next = tofree;
+ tofree = b;
+ };
+
+ if(n < len)
+ len = n;
+ memmove(p, b->rp, len);
+ consumecnt += n;
+ b->rp += len;
+ q->dlen -= len;
+
+ /* discard the block if we're done with it */
+ if((q->state & Qmsg) || len == n){
+ q->bfirst = b->next;
+ b->next = 0;
+ q->len -= BALLOC(b);
+ q->dlen -= BLEN(b);
+
+ /* remember to free this */
+ b->next = tofree;
+ tofree = b;
+ }
+
+ /* if writer flow controlled, restart */
+ if((q->state & Qflow) && q->len < q->limit/2){
+ q->state &= ~Qflow;
+ dowakeup = 1;
+ } else
+ dowakeup = 0;
+
+ iunlock(&q->lk);
+
+ if(dowakeup)
+ wakeup(&q->wr);
+
+ if(tofree != nil)
+ freeblist(tofree);
+
+ return len;
+}
+
+int
+qpass(Queue *q, Block *b)
+{
+ int dlen, len, dowakeup;
+
+ /* sync with qread */
+ dowakeup = 0;
+ ilock(&q->lk);
+ if(q->len >= q->limit){
+ freeblist(b);
+ iunlock(&q->lk);
+ return -1;
+ }
+ if(q->state & Qclosed){
+ freeblist(b);
+ iunlock(&q->lk);
+ return BALLOC(b);
+ }
+
+ /* add buffer to queue */
+ if(q->bfirst)
+ q->blast->next = b;
+ else
+ q->bfirst = b;
+ len = BALLOC(b);
+ dlen = BLEN(b);
+ QDEBUG checkb(b, "qpass");
+ while(b->next){
+ b = b->next;
+ QDEBUG checkb(b, "qpass");
+ len += BALLOC(b);
+ dlen += BLEN(b);
+ }
+ q->blast = b;
+ q->len += len;
+ q->dlen += dlen;
+
+ if(q->len >= q->limit/2)
+ q->state |= Qflow;
+
+ if(q->state & Qstarve){
+ q->state &= ~Qstarve;
+ dowakeup = 1;
+ }
+ iunlock(&q->lk);
+
+ if(dowakeup)
+ wakeup(&q->rr);
+
+ return len;
+}
+
+int
+qpassnolim(Queue *q, Block *b)
+{
+ int dlen, len, dowakeup;
+
+ /* sync with qread */
+ dowakeup = 0;
+ ilock(&q->lk);
+
+ if(q->state & Qclosed){
+ freeblist(b);
+ iunlock(&q->lk);
+ return BALLOC(b);
+ }
+
+ /* add buffer to queue */
+ if(q->bfirst)
+ q->blast->next = b;
+ else
+ q->bfirst = b;
+ len = BALLOC(b);
+ dlen = BLEN(b);
+ QDEBUG checkb(b, "qpass");
+ while(b->next){
+ b = b->next;
+ QDEBUG checkb(b, "qpass");
+ len += BALLOC(b);
+ dlen += BLEN(b);
+ }
+ q->blast = b;
+ q->len += len;
+ q->dlen += dlen;
+
+ if(q->len >= q->limit/2)
+ q->state |= Qflow;
+
+ if(q->state & Qstarve){
+ q->state &= ~Qstarve;
+ dowakeup = 1;
+ }
+ iunlock(&q->lk);
+
+ if(dowakeup)
+ wakeup(&q->rr);
+
+ return len;
+}
+
+/*
+ * if the allocated space is way out of line with the used
+ * space, reallocate to a smaller block
+ */
+Block*
+packblock(Block *bp)
+{
+ Block **l, *nbp;
+ int n;
+
+ for(l = &bp; *l; l = &(*l)->next){
+ nbp = *l;
+ n = BLEN(nbp);
+ if((n<<2) < BALLOC(nbp)){
+ *l = allocb(n);
+ memmove((*l)->wp, nbp->rp, n);
+ (*l)->wp += n;
+ (*l)->next = nbp->next;
+ freeb(nbp);
+ }
+ }
+
+ return bp;
+}
+
+int
+qproduce(Queue *q, void *vp, int len)
+{
+ Block *b;
+ int dowakeup;
+ uchar *p = vp;
+
+ /* sync with qread */
+ dowakeup = 0;
+ ilock(&q->lk);
+
+ /* no waiting receivers, room in buffer? */
+ if(q->len >= q->limit){
+ q->state |= Qflow;
+ iunlock(&q->lk);
+ return -1;
+ }
+
+ /* save in buffer */
+ b = iallocb(len);
+ if(b == 0){
+ iunlock(&q->lk);
+ return 0;
+ }
+ memmove(b->wp, p, len);
+ producecnt += len;
+ b->wp += len;
+ if(q->bfirst)
+ q->blast->next = b;
+ else
+ q->bfirst = b;
+ q->blast = b;
+ /* b->next = 0; done by iallocb() */
+ q->len += BALLOC(b);
+ q->dlen += BLEN(b);
+ QDEBUG checkb(b, "qproduce");
+
+ if(q->state & Qstarve){
+ q->state &= ~Qstarve;
+ dowakeup = 1;
+ }
+
+ if(q->len >= q->limit)
+ q->state |= Qflow;
+ iunlock(&q->lk);
+
+ if(dowakeup)
+ wakeup(&q->rr);
+
+ return len;
+}
+
+/*
+ * copy from offset in the queue
+ */
+Block*
+qcopy(Queue *q, int len, ulong offset)
+{
+ int sofar;
+ int n;
+ Block *b, *nb;
+ uchar *p;
+
+ nb = allocb(len);
+
+ ilock(&q->lk);
+
+ /* go to offset */
+ b = q->bfirst;
+ for(sofar = 0; ; sofar += n){
+ if(b == nil){
+ iunlock(&q->lk);
+ return nb;
+ }
+ n = BLEN(b);
+ if(sofar + n > offset){
+ p = b->rp + offset - sofar;
+ n -= offset - sofar;
+ break;
+ }
+ QDEBUG checkb(b, "qcopy");
+ b = b->next;
+ }
+
+ /* copy bytes from there */
+ for(sofar = 0; sofar < len;){
+ if(n > len - sofar)
+ n = len - sofar;
+ memmove(nb->wp, p, n);
+ qcopycnt += n;
+ sofar += n;
+ nb->wp += n;
+ b = b->next;
+ if(b == nil)
+ break;
+ n = BLEN(b);
+ p = b->rp;
+ }
+ iunlock(&q->lk);
+
+ return nb;
+}
+
+/*
+ * called by non-interrupt code
+ */
+Queue*
+qopen(int limit, int msg, void (*kick)(void*), void *arg)
+{
+ Queue *q;
+
+ q = malloc(sizeof(Queue));
+ if(q == 0)
+ return 0;
+
+ q->limit = q->inilim = limit;
+ q->kick = kick;
+ q->arg = arg;
+ q->state = msg;
+
+ q->state |= Qstarve;
+ q->eof = 0;
+ q->noblock = 0;
+
+ return q;
+}
+
+/* open a queue to be bypassed */
+Queue*
+qbypass(void (*bypass)(void*, Block*), void *arg)
+{
+ Queue *q;
+
+ q = malloc(sizeof(Queue));
+ if(q == 0)
+ return 0;
+
+ q->limit = 0;
+ q->arg = arg;
+ q->bypass = bypass;
+ q->state = 0;
+
+ return q;
+}
+
+static int
+notempty(void *a)
+{
+ Queue *q = a;
+
+ return (q->state & Qclosed) || q->bfirst != 0;
+}
+
+/*
+ * wait for the queue to be non-empty or closed.
+ * called with q ilocked.
+ */
+static int
+qwait(Queue *q)
+{
+ /* wait for data */
+ for(;;){
+ if(q->bfirst != nil)
+ break;
+
+ if(q->state & Qclosed){
+ if(++q->eof > 3)
+ return -1;
+ if(*q->err && strcmp(q->err, Ehungup) != 0)
+ return -1;
+ return 0;
+ }
+
+ q->state |= Qstarve; /* flag requesting producer to wake me */
+ iunlock(&q->lk);
+ sleep(&q->rr, notempty, q);
+ ilock(&q->lk);
+ }
+ return 1;
+}
+
+/*
+ * add a block list to a queue
+ */
+void
+qaddlist(Queue *q, Block *b)
+{
+ /* queue the block */
+ if(q->bfirst)
+ q->blast->next = b;
+ else
+ q->bfirst = b;
+ q->len += blockalloclen(b);
+ q->dlen += blocklen(b);
+ while(b->next)
+ b = b->next;
+ q->blast = b;
+}
+
+/*
+ * called with q ilocked
+ */
+Block*
+qremove(Queue *q)
+{
+ Block *b;
+
+ b = q->bfirst;
+ if(b == nil)
+ return nil;
+ q->bfirst = b->next;
+ b->next = nil;
+ q->dlen -= BLEN(b);
+ q->len -= BALLOC(b);
+ QDEBUG checkb(b, "qremove");
+ return b;
+}
+
+/*
+ * copy the contents of a string of blocks into
+ * memory. emptied blocks are freed. return
+ * pointer to first unconsumed block.
+ */
+Block*
+bl2mem(uchar *p, Block *b, int n)
+{
+ int i;
+ Block *next;
+
+ for(; b != nil; b = next){
+ i = BLEN(b);
+ if(i > n){
+ memmove(p, b->rp, n);
+ b->rp += n;
+ return b;
+ }
+ memmove(p, b->rp, i);
+ n -= i;
+ p += i;
+ b->rp += i;
+ next = b->next;
+ freeb(b);
+ }
+ return nil;
+}
+
+/*
+ * copy the contents of memory into a string of blocks.
+ * return nil on error.
+ */
+Block*
+mem2bl(uchar *p, int len)
+{
+ int n;
+ Block *b, *first, **l;
+
+ first = nil;
+ l = &first;
+ if(waserror()){
+ freeblist(first);
+ nexterror();
+ }
+ do {
+ n = len;
+ if(n > Maxatomic)
+ n = Maxatomic;
+
+ *l = b = allocb(n);
+ /* setmalloctag(b, (up->text[0]<<24)|(up->text[1]<<16)|(up->text[2]<<8)|up->text[3]); */
+ memmove(b->wp, p, n);
+ b->wp += n;
+ p += n;
+ len -= n;
+ l = &b->next;
+ } while(len > 0);
+ poperror();
+
+ return first;
+}
+
+/*
+ * put a block back to the front of the queue
+ * called with q ilocked
+ */
+void
+qputback(Queue *q, Block *b)
+{
+ b->next = q->bfirst;
+ if(q->bfirst == nil)
+ q->blast = b;
+ q->bfirst = b;
+ q->len += BALLOC(b);
+ q->dlen += BLEN(b);
+}
+
+/*
+ * flow control, get producer going again
+ * called with q ilocked
+ */
+static void
+qwakeup_iunlock(Queue *q)
+{
+ int dowakeup = 0;
+
+ /* if writer flow controlled, restart */
+ if((q->state & Qflow) && q->len < q->limit/2){
+ q->state &= ~Qflow;
+ dowakeup = 1;
+ }
+
+ iunlock(&q->lk);
+
+ /* wakeup flow controlled writers */
+ if(dowakeup){
+ if(q->kick)
+ q->kick(q->arg);
+ wakeup(&q->wr);
+ }
+}
+
+/*
+ * get next block from a queue (up to a limit)
+ */
+Block*
+qbread(Queue *q, int len)
+{
+ Block *b, *nb;
+ int n;
+
+ qlock(&q->rlock);
+ if(waserror()){
+ qunlock(&q->rlock);
+ nexterror();
+ }
+
+ ilock(&q->lk);
+ switch(qwait(q)){
+ case 0:
+ /* queue closed */
+ iunlock(&q->lk);
+ qunlock(&q->rlock);
+ poperror();
+ return nil;
+ case -1:
+ /* multiple reads on a closed queue */
+ iunlock(&q->lk);
+ error(q->err);
+ }
+
+ /* if we get here, there's at least one block in the queue */
+ b = qremove(q);
+ n = BLEN(b);
+
+ /* split block if it's too big and this is not a message queue */
+ nb = b;
+ if(n > len){
+ if((q->state&Qmsg) == 0){
+ n -= len;
+ b = allocb(n);
+ memmove(b->wp, nb->rp+len, n);
+ b->wp += n;
+ qputback(q, b);
+ }
+ nb->wp = nb->rp + len;
+ }
+
+ /* restart producer */
+ qwakeup_iunlock(q);
+
+ poperror();
+ qunlock(&q->rlock);
+ return nb;
+}
+
+/*
+ * read a queue. if no data is queued, post a Block
+ * and wait on its Rendez.
+ */
+long
+qread(Queue *q, void *vp, int len)
+{
+ Block *b, *first, **l;
+ int m, n;
+
+ qlock(&q->rlock);
+ if(waserror()){
+ qunlock(&q->rlock);
+ nexterror();
+ }
+
+ ilock(&q->lk);
+again:
+ switch(qwait(q)){
+ case 0:
+ /* queue closed */
+ iunlock(&q->lk);
+ qunlock(&q->rlock);
+ poperror();
+ return 0;
+ case -1:
+ /* multiple reads on a closed queue */
+ iunlock(&q->lk);
+ error(q->err);
+ }
+
+ /* if we get here, there's at least one block in the queue */
+ if(q->state & Qcoalesce){
+ /* when coalescing, 0 length blocks just go away */
+ b = q->bfirst;
+ if(BLEN(b) <= 0){
+ freeb(qremove(q));
+ goto again;
+ }
+
+ /* grab the first block plus as many
+ * following blocks as will completely
+ * fit in the read.
+ */
+ n = 0;
+ l = &first;
+ m = BLEN(b);
+ for(;;) {
+ *l = qremove(q);
+ l = &b->next;
+ n += m;
+
+ b = q->bfirst;
+ if(b == nil)
+ break;
+ m = BLEN(b);
+ if(n+m > len)
+ break;
+ }
+ } else {
+ first = qremove(q);
+ n = BLEN(first);
+ }
+
+ /* copy to user space outside of the ilock */
+ iunlock(&q->lk);
+ b = bl2mem(vp, first, len);
+ ilock(&q->lk);
+
+ /* take care of any left over partial block */
+ if(b != nil){
+ n -= BLEN(b);
+ if(q->state & Qmsg)
+ freeb(b);
+ else
+ qputback(q, b);
+ }
+
+ /* restart producer */
+ qwakeup_iunlock(q);
+
+ poperror();
+ qunlock(&q->rlock);
+ return n;
+}
+
+static int
+qnotfull(void *a)
+{
+ Queue *q = a;
+
+ return q->len < q->limit || (q->state & Qclosed);
+}
+
+ulong noblockcnt;
+
+/*
+ * add a block to a queue obeying flow control
+ */
+long
+qbwrite(Queue *q, Block *b)
+{
+ int n, dowakeup;
+
+ n = BLEN(b);
+
+ if(q->bypass){
+ (*q->bypass)(q->arg, b);
+ return n;
+ }
+
+ dowakeup = 0;
+ qlock(&q->wlock);
+ if(waserror()){
+ if(b != nil)
+ freeb(b);
+ qunlock(&q->wlock);
+ nexterror();
+ }
+
+ ilock(&q->lk);
+
+ /* give up if the queue is closed */
+ if(q->state & Qclosed){
+ iunlock(&q->lk);
+ error(q->err);
+ }
+
+ /* if nonblocking, don't queue over the limit */
+ if(q->len >= q->limit){
+ if(q->noblock){
+ iunlock(&q->lk);
+ freeb(b);
+ noblockcnt += n;
+ qunlock(&q->wlock);
+ poperror();
+ return n;
+ }
+ }
+
+ /* queue the block */
+ if(q->bfirst)
+ q->blast->next = b;
+ else
+ q->bfirst = b;
+ q->blast = b;
+ b->next = 0;
+ q->len += BALLOC(b);
+ q->dlen += n;
+ QDEBUG checkb(b, "qbwrite");
+ b = nil;
+
+ /* make sure other end gets awakened */
+ if(q->state & Qstarve){
+ q->state &= ~Qstarve;
+ dowakeup = 1;
+ }
+ iunlock(&q->lk);
+
+ /* get output going again */
+ if(q->kick && (dowakeup || (q->state&Qkick)))
+ q->kick(q->arg);
+
+ /* wakeup anyone consuming at the other end */
+ if(dowakeup){
+ wakeup(&q->rr);
+
+ /* if we just wokeup a higher priority process, let it run */
+ /*
+ p = wakeup(&q->rr);
+ if(p != nil && p->priority > up->priority)
+ sched();
+ */
+ }
+
+ /*
+ * flow control, wait for queue to get below the limit
+ * before allowing the process to continue and queue
+ * more. We do this here so that postnote can only
+ * interrupt us after the data has been queued. This
+ * means that things like 9p flushes and ssl messages
+ * will not be disrupted by software interrupts.
+ *
+ * Note - this is moderately dangerous since a process
+ * that keeps getting interrupted and rewriting will
+ * queue infinite crud.
+ */
+ for(;;){
+ if(q->noblock || qnotfull(q))
+ break;
+
+ ilock(&q->lk);
+ q->state |= Qflow;
+ iunlock(&q->lk);
+ sleep(&q->wr, qnotfull, q);
+ }
+ USED(b);
+
+ qunlock(&q->wlock);
+ poperror();
+ return n;
+}
+
+/*
+ * write to a queue. only Maxatomic bytes at a time is atomic.
+ */
+int
+qwrite(Queue *q, void *vp, int len)
+{
+ int n, sofar;
+ Block *b;
+ uchar *p = vp;
+
+ QDEBUG if(!islo())
+ print("qwrite hi %p\n", getcallerpc(&q));
+
+ sofar = 0;
+ do {
+ n = len-sofar;
+ if(n > Maxatomic)
+ n = Maxatomic;
+
+ b = allocb(n);
+ /* setmalloctag(b, (up->text[0]<<24)|(up->text[1]<<16)|(up->text[2]<<8)|up->text[3]); */
+ if(waserror()){
+ freeb(b);
+ nexterror();
+ }
+ memmove(b->wp, p+sofar, n);
+ poperror();
+ b->wp += n;
+
+ qbwrite(q, b);
+
+ sofar += n;
+ } while(sofar < len && (q->state & Qmsg) == 0);
+
+ return len;
+}
+
+/*
+ * used by print() to write to a queue. Since we may be splhi or not in
+ * a process, don't qlock.
+ */
+int
+qiwrite(Queue *q, void *vp, int len)
+{
+ int n, sofar, dowakeup;
+ Block *b;
+ uchar *p = vp;
+
+ dowakeup = 0;
+
+ sofar = 0;
+ do {
+ n = len-sofar;
+ if(n > Maxatomic)
+ n = Maxatomic;
+
+ b = iallocb(n);
+ if(b == nil)
+ break;
+ memmove(b->wp, p+sofar, n);
+ b->wp += n;
+
+ ilock(&q->lk);
+
+ QDEBUG checkb(b, "qiwrite");
+ if(q->bfirst)
+ q->blast->next = b;
+ else
+ q->bfirst = b;
+ q->blast = b;
+ q->len += BALLOC(b);
+ q->dlen += n;
+
+ if(q->state & Qstarve){
+ q->state &= ~Qstarve;
+ dowakeup = 1;
+ }
+
+ iunlock(&q->lk);
+
+ if(dowakeup){
+ if(q->kick)
+ q->kick(q->arg);
+ wakeup(&q->rr);
+ }
+
+ sofar += n;
+ } while(sofar < len && (q->state & Qmsg) == 0);
+
+ return sofar;
+}
+
+/*
+ * be extremely careful when calling this,
+ * as there is no reference accounting
+ */
+void
+qfree(Queue *q)
+{
+ qclose(q);
+ free(q);
+}
+
+/*
+ * Mark a queue as closed. No further IO is permitted.
+ * All blocks are released.
+ */
+void
+qclose(Queue *q)
+{
+ Block *bfirst;
+
+ if(q == nil)
+ return;
+
+ /* mark it */
+ ilock(&q->lk);
+ q->state |= Qclosed;
+ q->state &= ~(Qflow|Qstarve);
+ strcpy(q->err, Ehungup);
+ bfirst = q->bfirst;
+ q->bfirst = 0;
+ q->len = 0;
+ q->dlen = 0;
+ q->noblock = 0;
+ iunlock(&q->lk);
+
+ /* free queued blocks */
+ freeblist(bfirst);
+
+ /* wake up readers/writers */
+ wakeup(&q->rr);
+ wakeup(&q->wr);
+}
+
+/*
+ * Mark a queue as closed. Wakeup any readers. Don't remove queued
+ * blocks.
+ */
+void
+qhangup(Queue *q, char *msg)
+{
+ /* mark it */
+ ilock(&q->lk);
+ q->state |= Qclosed;
+ if(msg == 0 || *msg == 0)
+ strcpy(q->err, Ehungup);
+ else
+ strncpy(q->err, msg, ERRMAX-1);
+ iunlock(&q->lk);
+
+ /* wake up readers/writers */
+ wakeup(&q->rr);
+ wakeup(&q->wr);
+}
+
+/*
+ * return non-zero if the q is hungup
+ */
+int
+qisclosed(Queue *q)
+{
+ return q->state & Qclosed;
+}
+
+/*
+ * mark a queue as no longer hung up
+ */
+void
+qreopen(Queue *q)
+{
+ ilock(&q->lk);
+ q->state &= ~Qclosed;
+ q->state |= Qstarve;
+ q->eof = 0;
+ q->limit = q->inilim;
+ iunlock(&q->lk);
+}
+
+/*
+ * return bytes queued
+ */
+int
+qlen(Queue *q)
+{
+ return q->dlen;
+}
+
+/*
+ * return space remaining before flow control
+ */
+int
+qwindow(Queue *q)
+{
+ int l;
+
+ l = q->limit - q->len;
+ if(l < 0)
+ l = 0;
+ return l;
+}
+
+/*
+ * return true if we can read without blocking
+ */
+int
+qcanread(Queue *q)
+{
+ return q->bfirst!=0;
+}
+
+/*
+ * change queue limit
+ */
+void
+qsetlimit(Queue *q, int limit)
+{
+ q->limit = limit;
+}
+
+/*
+ * set blocking/nonblocking
+ */
+void
+qnoblock(Queue *q, int onoff)
+{
+ q->noblock = onoff;
+}
+
+/*
+ * flush the output queue
+ */
+void
+qflush(Queue *q)
+{
+ Block *bfirst;
+
+ /* mark it */
+ ilock(&q->lk);
+ bfirst = q->bfirst;
+ q->bfirst = 0;
+ q->len = 0;
+ q->dlen = 0;
+ iunlock(&q->lk);
+
+ /* free queued blocks */
+ freeblist(bfirst);
+
+ /* wake up readers/writers */
+ wakeup(&q->wr);
+}
+
+int
+qfull(Queue *q)
+{
+ return q->state & Qflow;
+}
+
+int
+qstate(Queue *q)
+{
+ return q->state;
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/qlock.c b/sys/src/cmd/unix/drawterm/kern/qlock.c
new file mode 100755
index 000000000..fdbeaeac3
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/qlock.c
@@ -0,0 +1,94 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+
+static void
+queue(Proc **first, Proc **last)
+{
+ Proc *t;
+
+ t = *last;
+ if(t == 0)
+ *first = up;
+ else
+ t->qnext = up;
+ *last = up;
+ up->qnext = 0;
+}
+
+static Proc*
+dequeue(Proc **first, Proc **last)
+{
+ Proc *t;
+
+ t = *first;
+ if(t == 0)
+ return 0;
+ *first = t->qnext;
+ if(*first == 0)
+ *last = 0;
+ return t;
+}
+
+void
+qlock(QLock *q)
+{
+ lock(&q->lk);
+
+ if(q->hold == 0) {
+ q->hold = up;
+ unlock(&q->lk);
+ return;
+ }
+
+ /*
+ * Can't assert this because of RWLock
+ assert(q->hold != up);
+ */
+
+ queue((Proc**)&q->first, (Proc**)&q->last);
+ unlock(&q->lk);
+ procsleep();
+}
+
+int
+canqlock(QLock *q)
+{
+ lock(&q->lk);
+ if(q->hold == 0) {
+ q->hold = up;
+ unlock(&q->lk);
+ return 1;
+ }
+ unlock(&q->lk);
+ return 0;
+}
+
+void
+qunlock(QLock *q)
+{
+ Proc *p;
+
+ lock(&q->lk);
+ /*
+ * Can't assert this because of RWlock
+ assert(q->hold == CT);
+ */
+ p = dequeue((Proc**)&q->first, (Proc**)&q->last);
+ if(p) {
+ q->hold = p;
+ unlock(&q->lk);
+ procwakeup(p);
+ } else {
+ q->hold = 0;
+ unlock(&q->lk);
+ }
+}
+
+int
+holdqlock(QLock *q)
+{
+ return q->hold == up;
+}
+
diff --git a/sys/src/cmd/unix/drawterm/kern/rendez.c b/sys/src/cmd/unix/drawterm/kern/rendez.c
new file mode 100755
index 000000000..6a9ad174e
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/rendez.c
@@ -0,0 +1,90 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+void
+sleep(Rendez *r, int (*f)(void*), void *arg)
+{
+ int s;
+
+ s = splhi();
+
+ lock(&r->lk);
+ lock(&up->rlock);
+ if(r->p){
+ print("double sleep %lud %lud\n", r->p->pid, up->pid);
+ dumpstack();
+ }
+
+ /*
+ * Wakeup only knows there may be something to do by testing
+ * r->p in order to get something to lock on.
+ * Flush that information out to memory in case the sleep is
+ * committed.
+ */
+ r->p = up;
+
+ if((*f)(arg) || up->notepending){
+ /*
+ * if condition happened or a note is pending
+ * never mind
+ */
+ r->p = nil;
+ unlock(&up->rlock);
+ unlock(&r->lk);
+ } else {
+ /*
+ * now we are committed to
+ * change state and call scheduler
+ */
+ up->state = Wakeme;
+ up->r = r;
+
+ /* statistics */
+ /* m->cs++; */
+
+ unlock(&up->rlock);
+ unlock(&r->lk);
+
+ procsleep();
+ }
+
+ if(up->notepending) {
+ up->notepending = 0;
+ splx(s);
+ error(Eintr);
+ }
+
+ splx(s);
+}
+
+Proc*
+wakeup(Rendez *r)
+{
+ Proc *p;
+ int s;
+
+ s = splhi();
+
+ lock(&r->lk);
+ p = r->p;
+
+ if(p != nil){
+ lock(&p->rlock);
+ if(p->state != Wakeme || p->r != r)
+ panic("wakeup: state");
+ r->p = nil;
+ p->r = nil;
+ p->state = Running;
+ procwakeup(p);
+ unlock(&p->rlock);
+ }
+ unlock(&r->lk);
+
+ splx(s);
+
+ return p;
+}
+
diff --git a/sys/src/cmd/unix/drawterm/kern/rwlock.c b/sys/src/cmd/unix/drawterm/kern/rwlock.c
new file mode 100755
index 000000000..3381957a3
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/rwlock.c
@@ -0,0 +1,39 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+void
+rlock(RWlock *l)
+{
+ qlock(&l->x); /* wait here for writers and exclusion */
+ lock(&l->lk);
+ l->readers++;
+ canqlock(&l->k); /* block writers if we are the first reader */
+ unlock(&l->lk);
+ qunlock(&l->x);
+}
+
+void
+runlock(RWlock *l)
+{
+ lock(&l->lk);
+ if(--l->readers == 0) /* last reader out allows writers */
+ qunlock(&l->k);
+ unlock(&l->lk);
+}
+
+void
+wlock(RWlock *l)
+{
+ qlock(&l->x); /* wait here for writers and exclusion */
+ qlock(&l->k); /* wait here for last reader */
+}
+
+void
+wunlock(RWlock *l)
+{
+ qunlock(&l->k);
+ qunlock(&l->x);
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/screen.h b/sys/src/cmd/unix/drawterm/kern/screen.h
new file mode 100755
index 000000000..dfdc12de5
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/screen.h
@@ -0,0 +1,63 @@
+typedef struct Mouseinfo Mouseinfo;
+typedef struct Mousestate Mousestate;
+typedef struct Cursorinfo Cursorinfo;
+typedef struct Screeninfo Screeninfo;
+
+#define Mousequeue 16 /* queue can only have Mousequeue-1 elements */
+#define Mousewindow 500 /* mouse event window in millisec */
+
+struct Mousestate {
+ int buttons;
+ Point xy;
+ ulong msec;
+};
+
+struct Mouseinfo {
+ Lock lk;
+ Mousestate queue[Mousequeue];
+ int ri, wi;
+ int lastb;
+ int trans;
+ int open;
+ Rendez r;
+};
+
+struct Cursorinfo {
+ Lock lk;
+ Point offset;
+ uchar clr[2*16];
+ uchar set[2*16];
+};
+
+struct Screeninfo {
+ Lock lk;
+ Memimage *newsoft;
+ int reshaped;
+ int depth;
+ int dibtype;
+};
+
+extern Memimage *gscreen;
+extern Mouseinfo mouse;
+extern Cursorinfo cursor;
+extern Screeninfo screen;
+
+void screeninit(void);
+void screenload(Rectangle, int, uchar *, Point, int);
+
+void getcolor(ulong, ulong*, ulong*, ulong*);
+void setcolor(ulong, ulong, ulong, ulong);
+
+void refreshrect(Rectangle);
+
+void cursorarrow(void);
+void setcursor(void);
+void mouseset(Point);
+void drawflushr(Rectangle);
+void flushmemscreen(Rectangle);
+uchar *attachscreen(Rectangle*, ulong*, int*, int*, int*, void**);
+
+void drawqlock(void);
+void drawqunlock(void);
+int drawcanqlock(void);
+void terminit(void);
diff --git a/sys/src/cmd/unix/drawterm/kern/sleep.c b/sys/src/cmd/unix/drawterm/kern/sleep.c
new file mode 100755
index 000000000..6a9ad174e
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/sleep.c
@@ -0,0 +1,90 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+void
+sleep(Rendez *r, int (*f)(void*), void *arg)
+{
+ int s;
+
+ s = splhi();
+
+ lock(&r->lk);
+ lock(&up->rlock);
+ if(r->p){
+ print("double sleep %lud %lud\n", r->p->pid, up->pid);
+ dumpstack();
+ }
+
+ /*
+ * Wakeup only knows there may be something to do by testing
+ * r->p in order to get something to lock on.
+ * Flush that information out to memory in case the sleep is
+ * committed.
+ */
+ r->p = up;
+
+ if((*f)(arg) || up->notepending){
+ /*
+ * if condition happened or a note is pending
+ * never mind
+ */
+ r->p = nil;
+ unlock(&up->rlock);
+ unlock(&r->lk);
+ } else {
+ /*
+ * now we are committed to
+ * change state and call scheduler
+ */
+ up->state = Wakeme;
+ up->r = r;
+
+ /* statistics */
+ /* m->cs++; */
+
+ unlock(&up->rlock);
+ unlock(&r->lk);
+
+ procsleep();
+ }
+
+ if(up->notepending) {
+ up->notepending = 0;
+ splx(s);
+ error(Eintr);
+ }
+
+ splx(s);
+}
+
+Proc*
+wakeup(Rendez *r)
+{
+ Proc *p;
+ int s;
+
+ s = splhi();
+
+ lock(&r->lk);
+ p = r->p;
+
+ if(p != nil){
+ lock(&p->rlock);
+ if(p->state != Wakeme || p->r != r)
+ panic("wakeup: state");
+ r->p = nil;
+ p->r = nil;
+ p->state = Running;
+ procwakeup(p);
+ unlock(&p->rlock);
+ }
+ unlock(&r->lk);
+
+ splx(s);
+
+ return p;
+}
+
diff --git a/sys/src/cmd/unix/drawterm/kern/smalloc.c b/sys/src/cmd/unix/drawterm/kern/smalloc.c
new file mode 100755
index 000000000..314f3c850
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/smalloc.c
@@ -0,0 +1,18 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+void*
+smalloc(ulong n)
+{
+ return mallocz(n, 1);
+}
+
+void*
+malloc(ulong n)
+{
+ return mallocz(n, 1);
+}
+
diff --git a/sys/src/cmd/unix/drawterm/kern/stub.c b/sys/src/cmd/unix/drawterm/kern/stub.c
new file mode 100755
index 000000000..d9c690638
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/stub.c
@@ -0,0 +1,171 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+void
+mallocsummary(void)
+{
+}
+
+void
+pagersummary(void)
+{
+}
+
+int
+iseve(void)
+{
+ return 1;
+}
+
+void
+setswapchan(Chan *c)
+{
+ USED(c);
+}
+
+void
+splx(int x)
+{
+ USED(x);
+}
+
+int
+splhi(void)
+{
+ return 0;
+}
+
+int
+spllo(void)
+{
+ return 0;
+}
+
+void
+procdump(void)
+{
+}
+
+void
+scheddump(void)
+{
+}
+
+void
+killbig(void)
+{
+}
+
+void
+dumpstack(void)
+{
+}
+
+void
+xsummary(void)
+{
+}
+
+void
+rebootcmd(int argc, char **argv)
+{
+ USED(argc);
+ USED(argv);
+}
+
+void
+kickpager(void)
+{
+}
+
+int
+userwrite(char *a, int n)
+{
+ error(Eperm);
+ return 0;
+}
+
+vlong
+todget(vlong *p)
+{
+ if(p)
+ *p = 0;
+ return 0;
+}
+
+void
+todset(vlong a, vlong b, int c)
+{
+ USED(a);
+ USED(b);
+ USED(c);
+}
+
+void
+todsetfreq(vlong a)
+{
+ USED(a);
+}
+
+long
+hostdomainwrite(char *a, int n)
+{
+ USED(a);
+ USED(n);
+ error(Eperm);
+ return 0;
+}
+
+long
+hostownerwrite(char *a, int n)
+{
+ USED(a);
+ USED(n);
+ error(Eperm);
+ return 0;
+}
+
+void
+todinit(void)
+{
+}
+
+void
+rdb(void)
+{
+}
+
+void
+setmalloctag(void *v, uintptr tag)
+{
+ USED(v);
+ USED(tag);
+}
+
+int
+postnote(Proc *p, int x, char *msg, int flag)
+{
+ USED(p);
+ USED(x);
+ USED(msg);
+ USED(flag);
+ return 0;
+}
+
+void
+exhausted(char *s)
+{
+ panic("out of %s", s);
+}
+
+uvlong
+fastticks(uvlong *v)
+{
+ if(v)
+ *v = 1;
+ return 0;
+}
+
diff --git a/sys/src/cmd/unix/drawterm/kern/sysfile.c b/sys/src/cmd/unix/drawterm/kern/sysfile.c
new file mode 100755
index 000000000..18dadbcda
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/sysfile.c
@@ -0,0 +1,1244 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "user.h"
+#undef open
+#undef mount
+#undef read
+#undef write
+#undef seek
+#undef stat
+#undef wstat
+#undef remove
+#undef close
+#undef fstat
+#undef fwstat
+
+/*
+ * The sys*() routines needn't poperror() as they return directly to syscall().
+ */
+
+static void
+unlockfgrp(Fgrp *f)
+{
+ int ex;
+
+ ex = f->exceed;
+ f->exceed = 0;
+ unlock(&f->ref.lk);
+ if(ex)
+ pprint("warning: process exceeds %d file descriptors\n", ex);
+}
+
+int
+growfd(Fgrp *f, int fd) /* fd is always >= 0 */
+{
+ Chan **newfd, **oldfd;
+
+ if(fd < f->nfd)
+ return 0;
+ if(fd >= f->nfd+DELTAFD)
+ return -1; /* out of range */
+ /*
+ * Unbounded allocation is unwise; besides, there are only 16 bits
+ * of fid in 9P
+ */
+ if(f->nfd >= 5000){
+ Exhausted:
+ print("no free file descriptors\n");
+ return -1;
+ }
+ newfd = malloc((f->nfd+DELTAFD)*sizeof(Chan*));
+ if(newfd == 0)
+ goto Exhausted;
+ oldfd = f->fd;
+ memmove(newfd, oldfd, f->nfd*sizeof(Chan*));
+ f->fd = newfd;
+ free(oldfd);
+ f->nfd += DELTAFD;
+ if(fd > f->maxfd){
+ if(fd/100 > f->maxfd/100)
+ f->exceed = (fd/100)*100;
+ f->maxfd = fd;
+ }
+ return 1;
+}
+
+/*
+ * this assumes that the fgrp is locked
+ */
+int
+findfreefd(Fgrp *f, int start)
+{
+ int fd;
+
+ for(fd=start; fd<f->nfd; fd++)
+ if(f->fd[fd] == 0)
+ break;
+ if(fd >= f->nfd && growfd(f, fd) < 0)
+ return -1;
+ return fd;
+}
+
+int
+newfd(Chan *c)
+{
+ int fd;
+ Fgrp *f;
+
+ f = up->fgrp;
+ lock(&f->ref.lk);
+ fd = findfreefd(f, 0);
+ if(fd < 0){
+ unlockfgrp(f);
+ return -1;
+ }
+ if(fd > f->maxfd)
+ f->maxfd = fd;
+ f->fd[fd] = c;
+ unlockfgrp(f);
+ return fd;
+}
+
+int
+newfd2(int fd[2], Chan *c[2])
+{
+ Fgrp *f;
+
+ f = up->fgrp;
+ lock(&f->ref.lk);
+ fd[0] = findfreefd(f, 0);
+ if(fd[0] < 0){
+ unlockfgrp(f);
+ return -1;
+ }
+ fd[1] = findfreefd(f, fd[0]+1);
+ if(fd[1] < 0){
+ unlockfgrp(f);
+ return -1;
+ }
+ if(fd[1] > f->maxfd)
+ f->maxfd = fd[1];
+ f->fd[fd[0]] = c[0];
+ f->fd[fd[1]] = c[1];
+ unlockfgrp(f);
+
+ return 0;
+}
+
+Chan*
+fdtochan(int fd, int mode, int chkmnt, int iref)
+{
+ Chan *c;
+ Fgrp *f;
+
+ c = 0;
+ f = up->fgrp;
+
+ lock(&f->ref.lk);
+ if(fd<0 || f->nfd<=fd || (c = f->fd[fd])==0) {
+ unlock(&f->ref.lk);
+ error(Ebadfd);
+ }
+ if(iref)
+ incref(&c->ref);
+ unlock(&f->ref.lk);
+
+ if(chkmnt && (c->flag&CMSG)) {
+ if(iref)
+ cclose(c);
+ error(Ebadusefd);
+ }
+
+ if(mode<0 || c->mode==ORDWR)
+ return c;
+
+ if((mode&OTRUNC) && c->mode==OREAD) {
+ if(iref)
+ cclose(c);
+ error(Ebadusefd);
+ }
+
+ if((mode&~OTRUNC) != c->mode) {
+ if(iref)
+ cclose(c);
+ error(Ebadusefd);
+ }
+
+ return c;
+}
+
+int
+openmode(ulong o)
+{
+ o &= ~(OTRUNC|OCEXEC|ORCLOSE);
+ if(o > OEXEC)
+ error(Ebadarg);
+ if(o == OEXEC)
+ return OREAD;
+ return o;
+}
+
+long
+_sysfd2path(int fd, char *buf, uint nbuf)
+{
+ Chan *c;
+
+ c = fdtochan(fd, -1, 0, 1);
+
+ if(c->name == nil)
+ snprint(buf, nbuf, "<null>");
+ else
+ snprint(buf, nbuf, "%s", c->name->s);
+ cclose(c);
+ return 0;
+}
+
+long
+_syspipe(int fd[2])
+{
+ Chan *c[2];
+ Dev *d;
+ static char *datastr[] = {"data", "data1"};
+
+ d = devtab[devno('|', 0)];
+ c[0] = namec("#|", Atodir, 0, 0);
+ c[1] = 0;
+ fd[0] = -1;
+ fd[1] = -1;
+
+ if(waserror()){
+ cclose(c[0]);
+ if(c[1])
+ cclose(c[1]);
+ nexterror();
+ }
+ c[1] = cclone(c[0]);
+ if(walk(&c[0], datastr+0, 1, 1, nil) < 0)
+ error(Egreg);
+ if(walk(&c[1], datastr+1, 1, 1, nil) < 0)
+ error(Egreg);
+ c[0] = d->open(c[0], ORDWR);
+ c[1] = d->open(c[1], ORDWR);
+ if(newfd2(fd, c) < 0)
+ error(Enofd);
+ poperror();
+
+ return 0;
+}
+
+long
+_sysdup(int fd0, int fd1)
+{
+ int fd;
+ Chan *c, *oc;
+ Fgrp *f = up->fgrp;
+
+ /*
+ * Close after dup'ing, so date > #d/1 works
+ */
+ c = fdtochan(fd0, -1, 0, 1);
+ fd = fd1;
+ if(fd != -1){
+ lock(&f->ref.lk);
+ if(fd<0 || growfd(f, fd)<0) {
+ unlockfgrp(f);
+ cclose(c);
+ error(Ebadfd);
+ }
+ if(fd > f->maxfd)
+ f->maxfd = fd;
+
+ oc = f->fd[fd];
+ f->fd[fd] = c;
+ unlockfgrp(f);
+ if(oc)
+ cclose(oc);
+ }else{
+ if(waserror()) {
+ cclose(c);
+ nexterror();
+ }
+ fd = newfd(c);
+ if(fd < 0)
+ error(Enofd);
+ poperror();
+ }
+
+ return fd;
+}
+
+long
+_sysopen(char *name, int mode)
+{
+ int fd;
+ Chan *c = 0;
+
+ openmode(mode); /* error check only */
+ if(waserror()){
+ if(c)
+ cclose(c);
+ nexterror();
+ }
+ c = namec(name, Aopen, mode, 0);
+ fd = newfd(c);
+ if(fd < 0)
+ error(Enofd);
+ poperror();
+ return fd;
+}
+
+void
+fdclose(int fd, int flag)
+{
+ int i;
+ Chan *c;
+ Fgrp *f = up->fgrp;
+
+ lock(&f->ref.lk);
+ c = f->fd[fd];
+ if(c == 0){
+ /* can happen for users with shared fd tables */
+ unlock(&f->ref.lk);
+ return;
+ }
+ if(flag){
+ if(c==0 || !(c->flag&flag)){
+ unlock(&f->ref.lk);
+ return;
+ }
+ }
+ f->fd[fd] = 0;
+ if(fd == f->maxfd)
+ for(i=fd; --i>=0 && f->fd[i]==0; )
+ f->maxfd = i;
+
+ unlock(&f->ref.lk);
+ cclose(c);
+}
+
+long
+_sysclose(int fd)
+{
+ fdtochan(fd, -1, 0, 0);
+ fdclose(fd, 0);
+
+ return 0;
+}
+
+long
+unionread(Chan *c, void *va, long n)
+{
+ int i;
+ long nr;
+ Mhead *m;
+ Mount *mount;
+
+ qlock(&c->umqlock);
+ m = c->umh;
+ rlock(&m->lock);
+ mount = m->mount;
+ /* bring mount in sync with c->uri and c->umc */
+ for(i = 0; mount != nil && i < c->uri; i++)
+ mount = mount->next;
+
+ nr = 0;
+ while(mount != nil) {
+ /* Error causes component of union to be skipped */
+ if(mount->to && !waserror()) {
+ if(c->umc == nil){
+ c->umc = cclone(mount->to);
+ c->umc = devtab[c->umc->type]->open(c->umc, OREAD);
+ }
+
+ nr = devtab[c->umc->type]->read(c->umc, va, n, c->umc->offset);
+ c->umc->offset += nr;
+ poperror();
+ }
+ if(nr > 0)
+ break;
+
+ /* Advance to next element */
+ c->uri++;
+ if(c->umc) {
+ cclose(c->umc);
+ c->umc = nil;
+ }
+ mount = mount->next;
+ }
+ runlock(&m->lock);
+ qunlock(&c->umqlock);
+ return nr;
+}
+
+static long
+kread(int fd, void *buf, long n, vlong *offp)
+{
+ int dir;
+ Chan *c;
+ vlong off;
+
+ c = fdtochan(fd, OREAD, 1, 1);
+
+ if(waserror()) {
+ cclose(c);
+ nexterror();
+ }
+
+ dir = c->qid.type&QTDIR;
+ /*
+ * The offset is passed through on directories, normally. sysseek complains but
+ * pread is used by servers and e.g. exportfs that shouldn't need to worry about this issue.
+ */
+
+ if(offp == nil) /* use and maintain channel's offset */
+ off = c->offset;
+ else
+ off = *offp;
+
+ if(off < 0)
+ error(Enegoff);
+
+ if(dir && c->umh)
+ n = unionread(c, buf, n);
+ else
+ n = devtab[c->type]->read(c, buf, n, off);
+
+ if(offp == nil){
+ lock(&c->ref.lk);
+ c->offset += n;
+ unlock(&c->ref.lk);
+ }
+
+ poperror();
+ cclose(c);
+
+ return n;
+}
+
+/* name conflicts with netbsd
+long
+_sys_read(int fd, void *buf, long n)
+{
+ return kread(fd, buf, n, nil);
+}
+*/
+
+long
+_syspread(int fd, void *buf, long n, vlong off)
+{
+ if(off == ((uvlong) ~0))
+ return kread(fd, buf, n, nil);
+ return kread(fd, buf, n, &off);
+}
+
+static long
+kwrite(int fd, void *buf, long nn, vlong *offp)
+{
+ Chan *c;
+ long m, n;
+ vlong off;
+
+ n = 0;
+ c = fdtochan(fd, OWRITE, 1, 1);
+ if(waserror()) {
+ if(offp == nil){
+ lock(&c->ref.lk);
+ c->offset -= n;
+ unlock(&c->ref.lk);
+ }
+ cclose(c);
+ nexterror();
+ }
+
+ if(c->qid.type & QTDIR)
+ error(Eisdir);
+
+ n = nn;
+
+ if(offp == nil){ /* use and maintain channel's offset */
+ lock(&c->ref.lk);
+ off = c->offset;
+ c->offset += n;
+ unlock(&c->ref.lk);
+ }else
+ off = *offp;
+
+ if(off < 0)
+ error(Enegoff);
+
+ m = devtab[c->type]->write(c, buf, n, off);
+
+ if(offp == nil && m < n){
+ lock(&c->ref.lk);
+ c->offset -= n - m;
+ unlock(&c->ref.lk);
+ }
+
+ poperror();
+ cclose(c);
+
+ return m;
+}
+
+long
+sys_write(int fd, void *buf, long n)
+{
+ return kwrite(fd, buf, n, nil);
+}
+
+long
+_syspwrite(int fd, void *buf, long n, vlong off)
+{
+ if(off == ((uvlong) ~0))
+ return kwrite(fd, buf, n, nil);
+ return kwrite(fd, buf, n, &off);
+}
+
+static vlong
+_sysseek(int fd, vlong off, int whence)
+{
+ Chan *c;
+ uchar buf[sizeof(Dir)+100];
+ Dir dir;
+ int n;
+
+ c = fdtochan(fd, -1, 1, 1);
+ if(waserror()){
+ cclose(c);
+ nexterror();
+ }
+ if(devtab[c->type]->dc == '|')
+ error(Eisstream);
+
+ switch(whence){
+ case 0:
+ if((c->qid.type & QTDIR) && off != 0)
+ error(Eisdir);
+ if(off < 0)
+ error(Enegoff);
+ c->offset = off;
+ break;
+
+ case 1:
+ if(c->qid.type & QTDIR)
+ error(Eisdir);
+ lock(&c->ref.lk); /* lock for read/write update */
+ off = off + c->offset;
+ if(off < 0)
+ error(Enegoff);
+ c->offset = off;
+ unlock(&c->ref.lk);
+ break;
+
+ case 2:
+ if(c->qid.type & QTDIR)
+ error(Eisdir);
+ n = devtab[c->type]->stat(c, buf, sizeof buf);
+ if(convM2D(buf, n, &dir, nil) == 0)
+ error("internal error: stat error in seek");
+ off = dir.length + off;
+ if(off < 0)
+ error(Enegoff);
+ c->offset = off;
+ break;
+
+ default:
+ error(Ebadarg);
+ }
+ c->uri = 0;
+ c->dri = 0;
+ cclose(c);
+ poperror();
+ return off;
+}
+
+void
+validstat(uchar *s, int n)
+{
+ int m;
+ char buf[64];
+
+ if(statcheck(s, n) < 0)
+ error(Ebadstat);
+ /* verify that name entry is acceptable */
+ s += STATFIXLEN - 4*BIT16SZ; /* location of first string */
+ /*
+ * s now points at count for first string.
+ * if it's too long, let the server decide; this is
+ * only for his protection anyway. otherwise
+ * we'd have to allocate and waserror.
+ */
+ m = GBIT16(s);
+ s += BIT16SZ;
+ if(m+1 > sizeof buf)
+ return;
+ memmove(buf, s, m);
+ buf[m] = '\0';
+ /* name could be '/' */
+ if(strcmp(buf, "/") != 0)
+ validname(buf, 0);
+}
+
+long
+_sysfstat(int fd, void *buf, long n)
+{
+ Chan *c;
+ uint l;
+
+ l = n;
+ validaddr(buf, l, 1);
+ c = fdtochan(fd, -1, 0, 1);
+ if(waserror()) {
+ cclose(c);
+ nexterror();
+ }
+ l = devtab[c->type]->stat(c, buf, l);
+ poperror();
+ cclose(c);
+ return l;
+}
+
+long
+_sysstat(char *name, void *buf, long n)
+{
+ Chan *c;
+ uint l;
+
+ l = n;
+ validaddr(buf, l, 1);
+ validaddr(name, 1, 0);
+ c = namec(name, Aaccess, 0, 0);
+ if(waserror()){
+ cclose(c);
+ nexterror();
+ }
+ l = devtab[c->type]->stat(c, buf, l);
+ poperror();
+ cclose(c);
+ return l;
+}
+
+long
+_syschdir(char *name)
+{
+ Chan *c;
+
+ validaddr(name, 1, 0);
+
+ c = namec(name, Atodir, 0, 0);
+ cclose(up->dot);
+ up->dot = c;
+ return 0;
+}
+
+long
+bindmount(int ismount, int fd, int afd, char* arg0, char* arg1, ulong flag, char* spec)
+{
+ int ret;
+ Chan *c0, *c1, *ac, *bc;
+ struct{
+ Chan *chan;
+ Chan *authchan;
+ char *spec;
+ int flags;
+ }bogus;
+
+ if((flag&~MMASK) || (flag&MORDER)==(MBEFORE|MAFTER))
+ error(Ebadarg);
+
+ bogus.flags = flag & MCACHE;
+
+ if(ismount){
+ if(up->pgrp->noattach)
+ error(Enoattach);
+
+ ac = nil;
+ bc = fdtochan(fd, ORDWR, 0, 1);
+ if(waserror()) {
+ if(ac)
+ cclose(ac);
+ cclose(bc);
+ nexterror();
+ }
+
+ if(afd >= 0)
+ ac = fdtochan(afd, ORDWR, 0, 1);
+
+ bogus.chan = bc;
+ bogus.authchan = ac;
+
+ validaddr((ulong)spec, 1, 0);
+ bogus.spec = spec;
+ if(waserror())
+ error(Ebadspec);
+ validname(spec, 1);
+ poperror();
+
+ ret = devno('M', 0);
+ c0 = devtab[ret]->attach((char*)&bogus);
+
+ poperror();
+ if(ac)
+ cclose(ac);
+ cclose(bc);
+ }else{
+ bogus.spec = 0;
+ validaddr((ulong)arg0, 1, 0);
+ c0 = namec(arg0, Abind, 0, 0);
+ }
+
+ if(waserror()){
+ cclose(c0);
+ nexterror();
+ }
+
+ validaddr((ulong)arg1, 1, 0);
+ c1 = namec(arg1, Amount, 0, 0);
+ if(waserror()){
+ cclose(c1);
+ nexterror();
+ }
+
+ ret = cmount(&c0, c1, flag, bogus.spec);
+
+ poperror();
+ cclose(c1);
+ poperror();
+ cclose(c0);
+ if(ismount)
+ fdclose(fd, 0);
+
+ return ret;
+}
+
+long
+_sysbind(char *old, char *new, int flag)
+{
+ return bindmount(0, -1, -1, old, new, flag, nil);
+}
+
+long
+_sysmount(int fd, int afd, char *new, int flag, char *spec)
+{
+ return bindmount(1, fd, afd, nil, new, flag, spec);
+}
+
+long
+_sysunmount(char *old, char *new)
+{
+ Chan *cmount, *cmounted;
+
+ cmounted = 0;
+
+ cmount = namec(new, Amount, 0, 0);
+
+ if(old) {
+ if(waserror()) {
+ cclose(cmount);
+ nexterror();
+ }
+ validaddr(old, 1, 0);
+ /*
+ * This has to be namec(..., Aopen, ...) because
+ * if arg[0] is something like /srv/cs or /fd/0,
+ * opening it is the only way to get at the real
+ * Chan underneath.
+ */
+ cmounted = namec(old, Aopen, OREAD, 0);
+ poperror();
+ }
+
+ if(waserror()) {
+ cclose(cmount);
+ if(cmounted)
+ cclose(cmounted);
+ nexterror();
+ }
+
+ cunmount(cmount, cmounted);
+ cclose(cmount);
+ if(cmounted)
+ cclose(cmounted);
+ poperror();
+ return 0;
+}
+
+long
+_syscreate(char *name, int mode, ulong perm)
+{
+ int fd;
+ Chan *c = 0;
+
+ openmode(mode&~OEXCL); /* error check only; OEXCL okay here */
+ if(waserror()) {
+ if(c)
+ cclose(c);
+ nexterror();
+ }
+ validaddr(name, 1, 0);
+ c = namec(name, Acreate, mode, perm);
+ fd = newfd(c);
+ if(fd < 0)
+ error(Enofd);
+ poperror();
+ return fd;
+}
+
+long
+_sysremove(char *name)
+{
+ Chan *c;
+
+ c = namec(name, Aremove, 0, 0);
+ if(waserror()){
+ c->type = 0; /* see below */
+ cclose(c);
+ nexterror();
+ }
+ devtab[c->type]->remove(c);
+ /*
+ * Remove clunks the fid, but we need to recover the Chan
+ * so fake it up. rootclose() is known to be a nop.
+ */
+ c->type = 0;
+ poperror();
+ cclose(c);
+ return 0;
+}
+
+long
+_syswstat(char *name, void *buf, long n)
+{
+ Chan *c;
+ uint l;
+
+ l = n;
+ validstat(buf, l);
+ validaddr(name, 1, 0);
+ c = namec(name, Aaccess, 0, 0);
+ if(waserror()){
+ cclose(c);
+ nexterror();
+ }
+ l = devtab[c->type]->wstat(c, buf, l);
+ poperror();
+ cclose(c);
+ return l;
+}
+
+long
+_sysfwstat(int fd, void *buf, long n)
+{
+ Chan *c;
+ uint l;
+
+ l = n;
+ validaddr(buf, l, 0);
+ validstat(buf, l);
+ c = fdtochan(fd, -1, 1, 1);
+ if(waserror()) {
+ cclose(c);
+ nexterror();
+ }
+ l = devtab[c->type]->wstat(c, buf, l);
+ poperror();
+ cclose(c);
+ return l;
+}
+
+
+static void
+starterror(void)
+{
+ assert(up->nerrlab == 0);
+}
+
+static void
+_syserror(void)
+{
+ char *p;
+
+ p = up->syserrstr;
+ up->syserrstr = up->errstr;
+ up->errstr = p;
+}
+
+static void
+enderror(void)
+{
+ assert(up->nerrlab == 1);
+ poperror();
+}
+
+int
+sysbind(char *old, char *new, int flag)
+{
+ int n;
+
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _sysbind(old, new, flag);
+ enderror();
+ return n;
+}
+
+int
+syschdir(char *path)
+{
+ int n;
+
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _syschdir(path);
+ enderror();
+ return n;
+}
+
+int
+sysclose(int fd)
+{
+ int n;
+
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _sysclose(fd);
+ enderror();
+ return n;
+}
+
+int
+syscreate(char *name, int mode, ulong perm)
+{
+ int n;
+
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _syscreate(name, mode, perm);
+ enderror();
+ return n;
+}
+
+int
+sysdup(int fd0, int fd1)
+{
+ int n;
+
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _sysdup(fd0, fd1);
+ enderror();
+ return n;
+}
+
+int
+sysfstat(int fd, uchar *buf, int n)
+{
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _sysfstat(fd, buf, n);
+ enderror();
+ return n;
+}
+
+int
+sysfwstat(int fd, uchar *buf, int n)
+{
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _sysfwstat(fd, buf, n);
+ enderror();
+ return n;
+}
+
+int
+sysmount(int fd, int afd, char *new, int flag, char *spec)
+{
+ int n;
+
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _sysmount(fd, afd, new, flag, spec);
+ enderror();
+ return n;
+}
+
+int
+sysunmount(char *old, char *new)
+{
+ int n;
+
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _sysunmount(old, new);
+ enderror();
+ return n;
+}
+
+int
+sysopen(char *name, int mode)
+{
+ int n;
+
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _sysopen(name, mode);
+ enderror();
+ return n;
+}
+
+int
+syspipe(int *fd)
+{
+ int n;
+
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _syspipe(fd);
+ enderror();
+ return n;
+}
+
+long
+syspread(int fd, void *buf, long n, vlong off)
+{
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _syspread(fd, buf, n, off);
+ enderror();
+ return n;
+}
+
+long
+syspwrite(int fd, void *buf, long n, vlong off)
+{
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _syspwrite(fd, buf, n, off);
+ enderror();
+ return n;
+}
+
+long
+sysread(int fd, void *buf, long n)
+{
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _syspread(fd, buf, n, (uvlong) ~0);
+ enderror();
+ return n;
+}
+
+int
+sysremove(char *path)
+{
+ int n;
+
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _sysremove(path);
+ enderror();
+ return n;
+}
+
+vlong
+sysseek(int fd, vlong off, int whence)
+{
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ off = _sysseek(fd, off, whence);
+ enderror();
+ return off;
+}
+
+int
+sysstat(char *name, uchar *buf, int n)
+{
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _sysstat(name, buf, n);
+ enderror();
+ return n;
+}
+
+long
+syswrite(int fd, void *buf, long n)
+{
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _syspwrite(fd, buf, n, (uvlong) ~0);
+ enderror();
+ return n;
+}
+
+int
+syswstat(char *name, uchar *buf, int n)
+{
+ starterror();
+ if(waserror()){
+ _syserror();
+ return -1;
+ }
+ n = _syswstat(name, buf, n);
+ enderror();
+ return n;
+}
+
+void
+werrstr(char *f, ...)
+{
+ char buf[ERRMAX];
+ va_list arg;
+
+ va_start(arg, f);
+ vsnprint(buf, sizeof buf, f, arg);
+ va_end(arg);
+
+ if(up->nerrlab)
+ strecpy(up->errstr, up->errstr+ERRMAX, buf);
+ else
+ strecpy(up->syserrstr, up->syserrstr+ERRMAX, buf);
+}
+
+int
+__errfmt(Fmt *fmt)
+{
+ if(up->nerrlab)
+ return fmtstrcpy(fmt, up->errstr);
+ else
+ return fmtstrcpy(fmt, up->syserrstr);
+}
+
+int
+errstr(char *buf, uint n)
+{
+ char tmp[ERRMAX];
+ char *p;
+
+ p = up->nerrlab ? up->errstr : up->syserrstr;
+ memmove(tmp, p, ERRMAX);
+ utfecpy(p, p+ERRMAX, buf);
+ utfecpy(buf, buf+n, tmp);
+ return strlen(buf);
+}
+
+int
+rerrstr(char *buf, uint n)
+{
+ char *p;
+
+ p = up->nerrlab ? up->errstr : up->syserrstr;
+ utfecpy(buf, buf+n, p);
+ return strlen(buf);
+}
+
+void*
+_sysrendezvous(void* arg0, void* arg1)
+{
+ void *tag, *val;
+ Proc *p, **l;
+
+ tag = arg0;
+ l = &REND(up->rgrp, (uintptr)tag);
+ up->rendval = (void*)~0;
+
+ lock(&up->rgrp->ref.lk);
+ for(p = *l; p; p = p->rendhash) {
+ if(p->rendtag == tag) {
+ *l = p->rendhash;
+ val = p->rendval;
+ p->rendval = arg1;
+
+ while(p->mach != 0)
+ ;
+ procwakeup(p);
+ unlock(&up->rgrp->ref.lk);
+ return val;
+ }
+ l = &p->rendhash;
+ }
+
+ /* Going to sleep here */
+ up->rendtag = tag;
+ up->rendval = arg1;
+ up->rendhash = *l;
+ *l = up;
+ up->state = Rendezvous;
+ unlock(&up->rgrp->ref.lk);
+
+ procsleep();
+
+ return up->rendval;
+}
+
+void*
+sysrendezvous(void *tag, void *val)
+{
+ void *n;
+
+ starterror();
+ if(waserror()){
+ _syserror();
+ return (void*)~0;
+ }
+ n = _sysrendezvous(tag, val);
+ enderror();
+ return n;
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/sysproc.c b/sys/src/cmd/unix/drawterm/kern/sysproc.c
new file mode 100755
index 000000000..11caeb773
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/sysproc.c
@@ -0,0 +1,32 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+long
+sysexits(ulong *arg)
+{
+ char *status;
+ char *inval = "invalid exit string";
+ char buf[ERRMAX];
+
+ status = (char*)arg[0];
+ if(status){
+ if(waserror())
+ status = inval;
+ else{
+ validaddr((ulong)status, 1, 0);
+ if(vmemchr(status, 0, ERRMAX) == 0){
+ memmove(buf, status, ERRMAX);
+ buf[ERRMAX-1] = 0;
+ status = buf;
+ }
+ }
+ poperror();
+
+ }
+ pexit(status, 1);
+ return 0; /* not reached */
+}
+
diff --git a/sys/src/cmd/unix/drawterm/kern/term.c b/sys/src/cmd/unix/drawterm/kern/term.c
new file mode 100755
index 000000000..f8fa9cbbf
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/term.c
@@ -0,0 +1,205 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include <draw.h>
+#include <memdraw.h>
+#include "screen.h"
+
+#define MINX 8
+#define Backgnd 0xFF /* white */
+
+ Memsubfont *memdefont;
+
+struct{
+ Point pos;
+ int bwid;
+}out;
+
+Lock screenlock;
+
+Memimage *conscol;
+Memimage *back;
+extern Memimage *gscreen;
+
+static Rectangle flushr;
+static Rectangle window;
+static Point curpos;
+static int h;
+static void termscreenputs(char*, int);
+
+
+static void
+screenflush(void)
+{
+ drawflushr(flushr);
+ flushr = Rect(10000, 10000, -10000, -10000);
+}
+
+static void
+addflush(Rectangle r)
+{
+ if(flushr.min.x >= flushr.max.x)
+ flushr = r;
+ else
+ combinerect(&flushr, r);
+}
+
+static void
+screenwin(void)
+{
+ Point p;
+ char *greet;
+ Memimage *grey;
+
+ drawqlock();
+ back = memwhite;
+ conscol = memblack;
+ memfillcolor(gscreen, 0x444488FF);
+
+ h = memdefont->height;
+
+ window.min = addpt(gscreen->r.min, Pt(20,20));
+ window.max.x = window.min.x + Dx(gscreen->r)*3/4-40;
+ window.max.y = window.min.y + Dy(gscreen->r)*3/4-100;
+
+ memimagedraw(gscreen, window, memblack, ZP, memopaque, ZP, S);
+ window = insetrect(window, 4);
+ memimagedraw(gscreen, window, memwhite, ZP, memopaque, ZP, S);
+
+ /* a lot of work to get a grey color */
+ grey = allocmemimage(Rect(0,0,1,1), CMAP8);
+ grey->flags |= Frepl;
+ grey->clipr = gscreen->r;
+ memfillcolor(grey, 0xAAAAAAFF);
+ memimagedraw(gscreen, Rect(window.min.x, window.min.y,
+ window.max.x, window.min.y+h+5+6), grey, ZP, nil, ZP, S);
+ freememimage(grey);
+ window = insetrect(window, 5);
+
+ greet = " Plan 9 Console ";
+ p = addpt(window.min, Pt(10, 0));
+ memimagestring(gscreen, p, conscol, ZP, memdefont, greet);
+ window.min.y += h+6;
+ curpos = window.min;
+ window.max.y = window.min.y+((window.max.y-window.min.y)/h)*h;
+ flushmemscreen(gscreen->r);
+ drawqunlock();
+}
+
+void
+terminit(void)
+{
+ memdefont = getmemdefont();
+ out.pos.x = MINX;
+ out.pos.y = 0;
+ out.bwid = memdefont->info[' '].width;
+ screenwin();
+ screenputs = termscreenputs;
+}
+
+static void
+scroll(void)
+{
+ int o;
+ Point p;
+ Rectangle r;
+
+ o = 8*h;
+ r = Rpt(window.min, Pt(window.max.x, window.max.y-o));
+ p = Pt(window.min.x, window.min.y+o);
+ memimagedraw(gscreen, r, gscreen, p, nil, p, S);
+ r = Rpt(Pt(window.min.x, window.max.y-o), window.max);
+ memimagedraw(gscreen, r, back, ZP, nil, ZP, S);
+ flushmemscreen(gscreen->r);
+ curpos.y -= o;
+}
+
+static void
+screenputc(char *buf)
+{
+ Point p;
+ int w, pos;
+ Rectangle r;
+ static int *xp;
+ static int xbuf[256];
+
+ if(xp < xbuf || xp >= &xbuf[sizeof(xbuf)])
+ xp = xbuf;
+
+ switch(buf[0]) {
+ case '\n':
+ if(curpos.y+h >= window.max.y)
+ scroll();
+ curpos.y += h;
+ screenputc("\r");
+ break;
+ case '\r':
+ xp = xbuf;
+ curpos.x = window.min.x;
+ break;
+ case '\t':
+ p = memsubfontwidth(memdefont, " ");
+ w = p.x;
+ *xp++ = curpos.x;
+ pos = (curpos.x-window.min.x)/w;
+ pos = 8-(pos%8);
+ r = Rect(curpos.x, curpos.y, curpos.x+pos*w, curpos.y + h);
+ memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
+ addflush(r);
+ curpos.x += pos*w;
+ break;
+ case '\b':
+ if(xp <= xbuf)
+ break;
+ xp--;
+ r = Rect(*xp, curpos.y, curpos.x, curpos.y + h);
+ memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
+ addflush(r);
+ curpos.x = *xp;
+ break;
+ default:
+ p = memsubfontwidth(memdefont, buf);
+ w = p.x;
+
+ if(curpos.x >= window.max.x-w)
+ screenputc("\n");
+
+ *xp++ = curpos.x;
+ r = Rect(curpos.x, curpos.y, curpos.x+w, curpos.y + h);
+ memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
+ memimagestring(gscreen, curpos, conscol, ZP, memdefont, buf);
+ addflush(r);
+ curpos.x += w;
+ }
+}
+
+static void
+termscreenputs(char *s, int n)
+{
+ int i, locked;
+ Rune r;
+ char buf[4];
+
+ lock(&screenlock);
+ locked = drawcanqlock();
+ while(n > 0){
+ i = chartorune(&r, s);
+ if(i == 0){
+ s++;
+ --n;
+ continue;
+ }
+ memmove(buf, s, i);
+ buf[i] = 0;
+ n -= i;
+ s += i;
+ screenputc(buf);
+ }
+ if(locked)
+ drawqunlock();
+ screenflush();
+ unlock(&screenlock);
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/uart.c b/sys/src/cmd/unix/drawterm/kern/uart.c
new file mode 100755
index 000000000..da6d93e3d
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/uart.c
@@ -0,0 +1,15 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+extern int panicking;
+void
+uartputs(char *s, int n)
+{
+ if(panicking)
+ write(1, s, n);
+}
+
+
diff --git a/sys/src/cmd/unix/drawterm/kern/unused/syscall.c b/sys/src/cmd/unix/drawterm/kern/unused/syscall.c
new file mode 100755
index 000000000..1ae9a7cb2
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/unused/syscall.c
@@ -0,0 +1,837 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+Chan*
+fdtochan(int fd, int mode, int chkmnt, int iref)
+{
+ Fgrp *f;
+ Chan *c;
+
+ c = 0;
+ f = up->fgrp;
+
+ lock(&f->ref.lk);
+ if(fd<0 || NFD<=fd || (c = f->fd[fd])==0) {
+ unlock(&f->ref.lk);
+ error(Ebadfd);
+ }
+ if(iref)
+ refinc(&c->ref);
+ unlock(&f->ref.lk);
+
+ if(chkmnt && (c->flag&CMSG))
+ goto bad;
+ if(mode<0 || c->mode==ORDWR)
+ return c;
+ if((mode&OTRUNC) && c->mode==OREAD)
+ goto bad;
+ if((mode&~OTRUNC) != c->mode)
+ goto bad;
+ return c;
+bad:
+ if(iref)
+ cclose(c);
+ error(Ebadusefd);
+ return nil; /* shut up compiler */
+}
+
+static void
+fdclose(int fd, int flag)
+{
+ int i;
+ Chan *c;
+ Fgrp *f;
+
+ f = up->fgrp;
+
+ lock(&f->ref.lk);
+ c = f->fd[fd];
+ if(c == 0) {
+ unlock(&f->ref.lk);
+ return;
+ }
+ if(flag) {
+ if(c==0 || !(c->flag&flag)) {
+ unlock(&f->ref.lk);
+ return;
+ }
+ }
+ f->fd[fd] = 0;
+ if(fd == f->maxfd)
+ for(i=fd; --i>=0 && f->fd[i]==0; )
+ f->maxfd = i;
+
+ unlock(&f->ref.lk);
+ cclose(c);
+}
+
+static int
+newfd(Chan *c)
+{
+ int i;
+ Fgrp *f;
+
+ f = up->fgrp;
+ lock(&f->ref.lk);
+ for(i=0; i<NFD; i++)
+ if(f->fd[i] == 0){
+ if(i > f->maxfd)
+ f->maxfd = i;
+ f->fd[i] = c;
+ unlock(&f->ref.lk);
+ return i;
+ }
+ unlock(&f->ref.lk);
+ error("no file descriptors");
+ return 0;
+}
+
+int
+sysclose(int fd)
+{
+ if(waserror())
+ return -1;
+
+ fdtochan(fd, -1, 0, 0);
+ fdclose(fd, 0);
+ poperror();
+ return 0;
+}
+
+int
+syscreate(char *path, int mode, ulong perm)
+{
+ int fd;
+ Chan *c = 0;
+
+ if(waserror()) {
+ cclose(c);
+ return -1;
+ }
+
+ openmode(mode); /* error check only */
+ c = namec(path, Acreate, mode, perm);
+ fd = newfd((Chan*)c);
+ poperror();
+ return fd;
+}
+
+int
+sysdup(int old, int new)
+{
+ Chan *oc;
+ Fgrp *f = up->fgrp;
+ Chan *c = 0;
+
+ if(waserror())
+ return -1;
+
+ c = fdtochan(old, -1, 0, 1);
+ if(new != -1) {
+ if(new < 0 || NFD <= new) {
+ cclose(c);
+ error(Ebadfd);
+ }
+ lock(&f->ref.lk);
+ if(new > f->maxfd)
+ f->maxfd = new;
+ oc = f->fd[new];
+ f->fd[new] = (Chan*)c;
+ unlock(&f->ref.lk);
+ if(oc != 0)
+ cclose(oc);
+ }
+ else {
+ if(waserror()) {
+ cclose(c);
+ nexterror();
+ }
+ new = newfd((Chan*)c);
+ poperror();
+ }
+ poperror();
+ return new;
+}
+
+int
+sysfstat(int fd, char *buf)
+{
+ Chan *c = 0;
+
+ if(waserror()) {
+ cclose(c);
+ return -1;
+ }
+ c = fdtochan(fd, -1, 0, 1);
+ devtab[c->type]->stat((Chan*)c, buf);
+ poperror();
+ cclose(c);
+ return 0;
+}
+
+int
+sysfwstat(int fd, char *buf)
+{
+ Chan *c = 0;
+
+ if(waserror()) {
+ cclose(c);
+ return -1;
+ }
+ nameok(buf);
+ c = fdtochan(fd, -1, 1, 1);
+ devtab[c->type]->wstat((Chan*)c, buf);
+ poperror();
+ cclose(c);
+ return 0;
+}
+
+int
+syschdir(char *dir)
+{
+ return 0;
+}
+
+long
+bindmount(Chan *c0, char *old, int flag, char *spec)
+{
+ int ret;
+ Chan *c1 = 0;
+
+ if(flag>MMASK || (flag&MORDER) == (MBEFORE|MAFTER))
+ error(Ebadarg);
+
+ c1 = namec(old, Amount, 0, 0);
+ if(waserror()){
+ cclose(c1);
+ nexterror();
+ }
+
+ ret = cmount(c0, c1, flag, spec);
+
+ poperror();
+ cclose(c1);
+ return ret;
+}
+
+int
+sysbind(char *new, char *old, int flags)
+{
+ long r;
+ Chan *c0 = 0;
+
+ if(waserror()) {
+ cclose(c0);
+ return -1;
+ }
+ c0 = namec(new, Aaccess, 0, 0);
+ r = bindmount(c0, old, flags, "");
+ poperror();
+ cclose(c0);
+ return 0;
+}
+
+int
+sysmount(int fd, char *old, int flags, char *spec)
+{
+ long r;
+ Chan *c0 = 0, *bc = 0;
+ struct {
+ Chan* chan;
+ char* spec;
+ int flags;
+ } mntparam;
+
+ if(waserror()) {
+ cclose(bc);
+ cclose(c0);
+ return -1;
+ }
+ bc = fdtochan(fd, ORDWR, 0, 1);
+ mntparam.chan = (Chan*)bc;
+ mntparam.spec = spec;
+ mntparam.flags = flags;
+ c0 = (*devtab[devno('M', 0)].attach)(&mntparam);
+ cclose(bc);
+ r = bindmount(c0, old, flags, spec);
+ poperror();
+ cclose(c0);
+
+ return r;
+}
+
+int
+sysunmount(char *old, char *new)
+{
+ Chan *cmount = 0, *cmounted = 0;
+
+ if(waserror()) {
+ cclose(cmount);
+ cclose(cmounted);
+ return -1;
+ }
+
+ cmount = namec(new, Amount, OREAD, 0);
+ if(old != 0)
+ cmounted = namec(old, Aopen, OREAD, 0);
+
+ cunmount(cmount, cmounted);
+ poperror();
+ cclose(cmount);
+ cclose(cmounted);
+ return 0;
+}
+
+int
+sysopen(char *path, int mode)
+{
+ int fd;
+ Chan *c = 0;
+
+ if(waserror()){
+ cclose(c);
+ return -1;
+ }
+ openmode(mode); /* error check only */
+ c = namec(path, Aopen, mode, 0);
+ fd = newfd((Chan*)c);
+ poperror();
+ return fd;
+}
+
+long
+unionread(Chan *c, void *va, long n)
+{
+ long nr;
+ Chan *nc = 0;
+ Pgrp *pg = 0;
+
+ pg = up->pgrp;
+ rlock(&pg->ns);
+
+ for(;;) {
+ if(waserror()) {
+ runlock(&pg->ns);
+ nexterror();
+ }
+ nc = clone(c->mnt->to, 0);
+ poperror();
+
+ if(c->mountid != c->mnt->mountid) {
+ runlock(&pg->ns);
+ cclose(nc);
+ return 0;
+ }
+
+ /* Error causes component of union to be skipped */
+ if(waserror()) {
+ cclose(nc);
+ goto next;
+ }
+
+ nc = (*devtab[nc->type].open)((Chan*)nc, OREAD);
+ nc->offset = c->offset;
+ nr = (*devtab[nc->type].read)((Chan*)nc, va, n, nc->offset);
+ /* devdirread e.g. changes it */
+ c->offset = nc->offset;
+ poperror();
+
+ cclose(nc);
+ if(nr > 0) {
+ runlock(&pg->ns);
+ return nr;
+ }
+ /* Advance to next element */
+ next:
+ c->mnt = c->mnt->next;
+ if(c->mnt == 0)
+ break;
+ c->mountid = c->mnt->mountid;
+ c->offset = 0;
+ }
+ runlock(&pg->ns);
+ return 0;
+}
+
+long
+sysread(int fd, void *va, long n)
+{
+ int dir;
+ Lock *cl;
+ Chan *c = 0;
+
+ if(waserror()) {
+ cclose(c);
+ return -1;
+ }
+ c = fdtochan(fd, OREAD, 1, 1);
+
+ dir = c->qid.path&CHDIR;
+ if(dir) {
+ n -= n%DIRLEN;
+ if(c->offset%DIRLEN || n==0)
+ error(Etoosmall);
+ }
+
+ if(dir && c->mnt)
+ n = unionread((Chan*)c, va, n);
+ else
+ n = (*devtab[c->type].read)((Chan*)c, va, n, c->offset);
+
+ cl = (Lock*)&c->r.l;
+ lock(cl);
+ c->offset += n;
+ unlock(cl);
+
+ poperror();
+ cclose(c);
+
+ return n;
+}
+
+int
+sysremove(char *path)
+{
+ Chan *c = 0;
+
+ if(waserror()) {
+ if(c != 0)
+ c->type = 0; /* see below */
+ cclose(c);
+ return -1;
+ }
+ c = namec(path, Aaccess, 0, 0);
+ (*devtab[c->type].remove)((Chan*)c);
+ /*
+ * Remove clunks the fid, but we need to recover the Chan
+ * so fake it up. rootclose() is known to be a nop.
+ */
+ c->type = 0;
+ poperror();
+ cclose(c);
+ return 0;
+}
+
+long
+sysseek(int fd, long off, int whence)
+{
+ Dir dir;
+ Chan *c;
+ char buf[DIRLEN];
+
+ if(waserror())
+ return -1;
+
+ c = fdtochan(fd, -1, 1, 0);
+ if(c->qid.path & CHDIR)
+ error(Eisdir);
+
+ switch(whence) {
+ case 0:
+ c->offset = off;
+ break;
+
+ case 1:
+ lock(&c->r.l); /* lock for read/write update */
+ c->offset += off;
+ off = c->offset;
+ unlock(&c->r.l);
+ break;
+
+ case 2:
+ (*devtab[c->type].stat)(c, buf);
+ convM2D(buf, &dir);
+ c->offset = dir.length + off;
+ off = c->offset;
+ break;
+ }
+ poperror();
+ return off;
+}
+
+int
+sysstat(char *path, char *buf)
+{
+ Chan *c = 0;
+
+ if(waserror()){
+ cclose(c);
+ return -1;
+ }
+ c = namec(path, Aaccess, 0, 0);
+ (*devtab[c->type].stat)((Chan*)c, buf);
+ poperror();
+ cclose(c);
+ return 0;
+}
+
+long
+syswrite(int fd, void *va, long n)
+{
+ Lock *cl;
+ Chan *c = 0;
+
+ if(waserror()) {
+ cclose(c);
+ return -1;
+ }
+ c = fdtochan(fd, OWRITE, 1, 1);
+ if(c->qid.path & CHDIR)
+ error(Eisdir);
+
+ n = (*devtab[c->type].write)((Chan*)c, va, n, c->offset);
+
+ cl = (Lock*)&c->r.l;
+ lock(cl);
+ c->offset += n;
+ unlock(cl);
+
+ poperror();
+ cclose(c);
+
+ return n;
+}
+
+int
+syswstat(char *path, char *buf)
+{
+ Chan *c = 0;
+
+ if(waserror()) {
+ cclose(c);
+ return -1;
+ }
+
+ nameok(buf);
+ c = namec(path, Aaccess, 0, 0);
+ (*devtab[c->type].wstat)((Chan*)c, buf);
+ poperror();
+ cclose(c);
+ return 0;
+}
+
+int
+sysdirstat(char *name, Dir *dir)
+{
+ char buf[DIRLEN];
+
+ if(sysstat(name, buf) == -1)
+ return -1;
+ convM2D(buf, dir);
+ return 0;
+}
+
+int
+sysdirfstat(int fd, Dir *dir)
+{
+ char buf[DIRLEN];
+
+ if(sysfstat(fd, buf) == -1)
+ return -1;
+
+ convM2D(buf, dir);
+ return 0;
+}
+
+int
+sysdirwstat(char *name, Dir *dir)
+{
+ char buf[DIRLEN];
+
+ convD2M(dir, buf);
+ return syswstat(name, buf);
+}
+
+int
+sysdirfwstat(int fd, Dir *dir)
+{
+ char buf[DIRLEN];
+
+ convD2M(dir, buf);
+ return sysfwstat(fd, buf);
+}
+
+long
+sysdirread(int fd, Dir *dbuf, long count)
+{
+ int c, n, i, r;
+ char buf[DIRLEN*50];
+
+ n = 0;
+ count = (count/sizeof(Dir)) * DIRLEN;
+ while(n < count) {
+ c = count - n;
+ if(c > sizeof(buf))
+ c = sizeof(buf);
+ r = sysread(fd, buf, c);
+ if(r == 0)
+ break;
+ if(r < 0 || r % DIRLEN)
+ return -1;
+ for(i=0; i<r; i+=DIRLEN) {
+ convM2D(buf+i, dbuf);
+ dbuf++;
+ }
+ n += r;
+ if(r != c)
+ break;
+ }
+
+ return (n/DIRLEN) * sizeof(Dir);
+}
+
+static int
+call(char *clone, char *dest, int *cfdp, char *dir, char *local)
+{
+ int fd, cfd, n;
+ char *p, name[3*NAMELEN+5], data[3*NAMELEN+10];
+
+ cfd = sysopen(clone, ORDWR);
+ if(cfd < 0){
+ werrstr("%s: %r", clone);
+ return -1;
+ }
+
+ /* get directory name */
+ n = sysread(cfd, name, sizeof(name)-1);
+ if(n < 0) {
+ sysclose(cfd);
+ return -1;
+ }
+ name[n] = 0;
+ sprint(name, "%d", strtoul(name, 0, 0));
+ p = strrchr(clone, '/');
+ *p = 0;
+ if(dir)
+ snprint(dir, 2*NAMELEN, "%s/%s", clone, name);
+ snprint(data, sizeof(data), "%s/%s/data", clone, name);
+
+ /* connect */
+ /* set local side (port number, for example) if we need to */
+ if(local)
+ snprint(name, sizeof(name), "connect %s %s", dest, local);
+ else
+ snprint(name, sizeof(name), "connect %s", dest);
+ if(syswrite(cfd, name, strlen(name)) < 0){
+ werrstr("%s failed: %r", name);
+ sysclose(cfd);
+ return -1;
+ }
+
+ /* open data connection */
+ fd = sysopen(data, ORDWR);
+ if(fd < 0){
+ werrstr("can't open %s: %r", data);
+ sysclose(cfd);
+ return -1;
+ }
+ if(cfdp)
+ *cfdp = cfd;
+ else
+ sysclose(cfd);
+ return fd;
+}
+
+int
+sysdial(char *dest, char *local, char *dir, int *cfdp)
+{
+ int n, fd, rv;
+ char *p, net[128], clone[NAMELEN+12];
+
+ /* go for a standard form net!... */
+ p = strchr(dest, '!');
+ if(p == 0){
+ snprint(net, sizeof(net), "net!%s", dest);
+ } else {
+ strncpy(net, dest, sizeof(net)-1);
+ net[sizeof(net)-1] = 0;
+ }
+
+ /* call the connection server */
+ fd = sysopen("/net/cs", ORDWR);
+ if(fd < 0){
+ /* no connection server, don't translate */
+ p = strchr(net, '!');
+ *p++ = 0;
+ snprint(clone, sizeof(clone), "/net/%s/clone", net);
+ return call(clone, p, cfdp, dir, local);
+ }
+
+ /*
+ * send dest to connection to translate
+ */
+ if(syswrite(fd, net, strlen(net)) < 0){
+ werrstr("%s: %r", net);
+ sysclose(fd);
+ return -1;
+ }
+
+ /*
+ * loop through each address from the connection server till
+ * we get one that works.
+ */
+ rv = -1;
+ sysseek(fd, 0, 0);
+ while((n = sysread(fd, net, sizeof(net) - 1)) > 0){
+ net[n] = 0;
+ p = strchr(net, ' ');
+ if(p == 0)
+ continue;
+ *p++ = 0;
+ rv = call(net, p, cfdp, dir, local);
+ if(rv >= 0)
+ break;
+ }
+ sysclose(fd);
+ return rv;
+}
+
+static int
+identtrans(char *addr, char *naddr, int na, char *file, int nf)
+{
+ char *p;
+ char reply[4*NAMELEN];
+
+ /* parse the network */
+ strncpy(reply, addr, sizeof(reply));
+ reply[sizeof(reply)-1] = 0;
+ p = strchr(reply, '!');
+ if(p)
+ *p++ = 0;
+
+ sprint(file, "/net/%.*s/clone", na - sizeof("/net//clone"), reply);
+ strncpy(naddr, p, na);
+ naddr[na-1] = 0;
+ return 1;
+}
+
+static int
+nettrans(char *addr, char *naddr, int na, char *file, int nf)
+{
+ long n;
+ int fd;
+ char *cp;
+ char reply[4*NAMELEN];
+
+ /*
+ * ask the connection server
+ */
+ fd = sysopen("/net/cs", ORDWR);
+ if(fd < 0)
+ return identtrans(addr, naddr, na, file, nf);
+ if(syswrite(fd, addr, strlen(addr)) < 0){
+ sysclose(fd);
+ return -1;
+ }
+ sysseek(fd, 0, 0);
+ n = sysread(fd, reply, sizeof(reply)-1);
+ sysclose(fd);
+ if(n <= 0)
+ return -1;
+ reply[n] = '\0';
+
+ /*
+ * parse the reply
+ */
+ cp = strchr(reply, ' ');
+ if(cp == 0)
+ return -1;
+ *cp++ = 0;
+ strncpy(naddr, cp, na);
+ naddr[na-1] = 0;
+ strncpy(file, reply, nf);
+ file[nf-1] = 0;
+ return 0;
+}
+
+int
+sysannounce(char *addr, char *dir)
+{
+ char *cp;
+ int ctl, n, m;
+ char buf[3*NAMELEN];
+ char buf2[3*NAMELEN];
+ char netdir[2*NAMELEN];
+ char naddr[3*NAMELEN];
+
+ /*
+ * translate the address
+ */
+ if(nettrans(addr, naddr, sizeof(naddr), netdir, sizeof(netdir)) < 0){
+ werrstr("can't translate address");
+ return -1;
+ }
+
+ /*
+ * get a control channel
+ */
+ ctl = sysopen(netdir, ORDWR);
+ if(ctl<0){
+ werrstr("can't open control channel");
+ return -1;
+ }
+ cp = strrchr(netdir, '/');
+ *cp = 0;
+
+ /*
+ * find out which line we have
+ */
+ n = sprint(buf, "%.*s/", 2*NAMELEN+1, netdir);
+ m = sysread(ctl, &buf[n], sizeof(buf)-n-1);
+ if(m <= 0) {
+ sysclose(ctl);
+ werrstr("can't read control file");
+ return -1;
+ }
+ buf[n+m] = 0;
+
+ /*
+ * make the call
+ */
+ n = sprint(buf2, "announce %.*s", 2*NAMELEN, naddr);
+ if(syswrite(ctl, buf2, n) != n) {
+ sysclose(ctl);
+ werrstr("announcement fails");
+ return -1;
+ }
+
+ strcpy(dir, buf);
+
+ return ctl;
+}
+
+int
+syslisten(char *dir, char *newdir)
+{
+ char *cp;
+ int ctl, n, m;
+ char buf[3*NAMELEN];
+
+ /*
+ * open listen, wait for a call
+ */
+ sprint(buf, "%.*s/listen", 2*NAMELEN+1, dir);
+ ctl = sysopen(buf, ORDWR);
+ if(ctl < 0)
+ return -1;
+
+ /*
+ * find out which line we have
+ */
+ strcpy(buf, dir);
+ cp = strrchr(buf, '/');
+ *++cp = 0;
+ n = cp-buf;
+ m = sysread(ctl, cp, sizeof(buf) - n - 1);
+ if(n<=0){
+ sysclose(ctl);
+ return -1;
+ }
+ buf[n+m] = 0;
+
+ strcpy(newdir, buf);
+ return ctl;
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/waserror.c b/sys/src/cmd/unix/drawterm/kern/waserror.c
new file mode 100755
index 000000000..3a07bc928
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/waserror.c
@@ -0,0 +1,27 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+Label*
+pwaserror(void)
+{
+ if(up->nerrlab == NERR)
+ panic("error stack overflow");
+ return &up->errlab[up->nerrlab++];
+}
+
+void
+nexterror(void)
+{
+ longjmp(up->errlab[--up->nerrlab].buf, 1);
+}
+
+void
+error(char *e)
+{
+ kstrcpy(up->errstr, e, ERRMAX);
+ setjmp(up->errlab[NERR-1].buf);
+ nexterror();
+}
diff --git a/sys/src/cmd/unix/drawterm/kern/win32.c b/sys/src/cmd/unix/drawterm/kern/win32.c
new file mode 100755
index 000000000..fa79c1d54
--- /dev/null
+++ b/sys/src/cmd/unix/drawterm/kern/win32.c
@@ -0,0 +1,470 @@
+#include <windows.h>
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include <libsec.h>
+
+typedef struct Oproc Oproc;
+struct Oproc {
+ int tid;
+ HANDLE *sema;
+};
+
+static int tlsx = TLS_OUT_OF_INDEXES;
+
+char *argv0;
+
+Proc*
+_getproc(void)
+{
+ if(tlsx == TLS_OUT_OF_INDEXES)
+ return nil;
+ return TlsGetValue(tlsx);
+}
+
+void
+_setproc(Proc *p)
+{
+ if(tlsx == TLS_OUT_OF_INDEXES){
+ tlsx = TlsAlloc();
+ if(tlsx == TLS_OUT_OF_INDEXES)
+ panic("out of indexes");
+ }
+ TlsSetValue(tlsx, p);
+}
+
+void
+oserror(void)
+{
+ oserrstr();
+ nexterror();
+}
+
+void
+osinit(void)
+{
+ Oproc *t;
+ static Proc firstprocCTstore;
+
+ _setproc(&firstprocCTstore);
+ t = (Oproc*)firstprocCTstore.oproc;
+ assert(t != 0);
+
+ t->tid = GetCurrentThreadId();
+ t->sema = CreateSemaphore(0, 0, 1000, 0);
+ if(t->sema == 0) {
+ oserror();
+ panic("could not create semaphore: %r");
+ }
+}
+
+void
+osnewproc(Proc *p)
+{
+ Oproc *op;
+
+ op = (Oproc*)p->oproc;
+ op->sema = CreateSemaphore(0, 0, 1000, 0);
+ if (op->sema == 0) {
+ oserror();
+ panic("could not create semaphore: %r");
+ }
+}
+
+void
+osmsleep(int ms)
+{
+ Sleep((DWORD) ms);
+}
+
+void
+osyield(void)
+{
+ Sleep(0);
+}
+
+static DWORD WINAPI tramp(LPVOID vp);
+
+void
+osproc(Proc *p)
+{
+ DWORD tid;
+
+ if(CreateThread(0, 0, tramp, p, 0, &tid) == 0) {
+ oserror();
+ panic("osproc: %r");
+ }
+
+ Sleep(0);
+}
+
+static DWORD WINAPI
+tramp(LPVOID vp)
+{
+ Proc *p = (Proc *) vp;
+ Oproc *op = (Oproc*) p->oproc;
+
+ _setproc(p);
+ op->tid = GetCurrentThreadId();
+ op->sema = CreateSemaphore(0, 0, 1000, 0);
+ if(op->sema == 0) {
+ oserror();
+ panic("could not create semaphore: %r");
+ }
+
+ (*p->fn)(p->arg);
+ ExitThread(0);
+ return 0;
+}
+
+void
+procsleep(void)
+{
+ Proc *p;
+ Oproc *op;
+
+ p = up;
+ op = (Oproc*)p->oproc;
+ WaitForSingleObject(op->sema, INFINITE);}
+
+void
+procwakeup(Proc *p)
+{
+ Oproc *op;
+
+ op = (Oproc*)p->oproc;
+ ReleaseSemaphore(op->sema, 1, 0);
+}
+
+void
+random20(uchar *p)
+{
+ LARGE_INTEGER ti;
+ int i, j;
+ FILETIME ft;
+ DigestState ds;
+ vlong tsc;
+
+ GetSystemTimeAsFileTime(&ft);
+ memset(&ds, 0, sizeof ds);
+ sha1((uchar*)&ft, sizeof(ft), 0, &ds);
+ for(i=0; i<50; i++) {
+ for(j=0; j<10; j++) {
+ QueryPerformanceCounter(&ti);
+ sha1((uchar*)&ti, sizeof(ti), 0, &ds);
+ tsc = GetTickCount();
+ sha1((uchar*)&tsc, sizeof(tsc), 0, &ds);
+ }
+ Sleep(10);
+ }
+ sha1(0, 0, p, &ds);
+}
+
+void
+randominit(void)
+{
+}
+
+ulong
+randomread(void *v, ulong n)
+{
+ int i;
+ uchar p[20];
+
+ for(i=0; i<n; i+=20){
+ random20(p);
+ if(i+20 <= n)
+ memmove((char*)v+i, p, 20);
+ else
+ memmove((char*)v+i, p, n-i);
+ }
+ return n;
+}
+
+long
+seconds(void)
+{
+ return time(0);
+}
+
+ulong
+ticks(void)
+{
+ return GetTickCount();
+}
+
+#if 0
+uvlong
+fastticks(uvlong *v)
+{
+ uvlong n;
+
+ n = GetTickCount() * 1000 * 1000;
+ if(v)
+ *v = n;
+ return n;
+}
+#endif
+
+extern int main(int, char*[]);
+
+
+int
+wstrutflen(Rune *s)
+{
+ int n;
+
+ for(n=0; *s; n+=runelen(*s),s++)
+ ;
+ return n;
+}
+
+int
+wstrtoutf(char *s, Rune *t, int n)
+{
+ int i;
+ char *s0;
+
+ s0 = s;
+ if(n <= 0)
+ return wstrutflen(t)+1;
+ while(*t) {
+ if(n < UTFmax+1 && n < runelen(*t)+1) {
+ *s = 0;
+ return s-s0+wstrutflen(t)+1;
+ }
+ i = runetochar(s, t);
+ s += i;
+ n -= i;
+ t++;
+ }
+ *s = 0;
+ return s-s0;
+}
+
+int
+win_hasunicode(void)
+{
+ OSVERSIONINFOA osinfo;
+ int r;
+
+ osinfo.dwOSVersionInfoSize = sizeof(osinfo);
+ if(!GetVersionExA(&osinfo))
+ panic("GetVersionEx failed");
+ switch(osinfo.dwPlatformId) {
+ default:
+ panic("unknown PlatformId");
+ case VER_PLATFORM_WIN32s:
+ panic("Win32s not supported");
+ case VER_PLATFORM_WIN32_WINDOWS:
+ r = 0;
+ break;
+ case VER_PLATFORM_WIN32_NT:
+ r = 1;
+ break;
+ }
+
+ return r;
+}
+
+int
+wstrlen(Rune *s)
+{
+ int n;
+
+ for(n=0; *s; s++,n++)
+ ;
+ return n;
+}
+static int args(char *argv[], int n, char *p);
+
+int APIENTRY
+WinMain(HINSTANCE x, HINSTANCE y, LPSTR z, int w)
+{
+ int argc, n;
+ char *arg, *p, **argv;
+ Rune *warg;
+
+ if(0 && win_hasunicode()){
+ warg = GetCommandLineW();
+ n = (wstrlen(warg)+1)*UTFmax;
+ arg = malloc(n);
+ wstrtoutf(arg, warg, n);
+ }else
+ arg = GetCommandLineA();
+
+ /* conservative guess at the number of args */
+ for(argc=4,p=arg; *p; p++)
+ if(*p == ' ' || *p == '\t')
+ argc++;
+ argv = malloc(argc*sizeof(char*));
+ argc = args(argv, argc, arg);
+
+ mymain(argc, argv);
+ ExitThread(0);
+ return 0;
+}
+
+/*
+ * Break the command line into arguments
+ * The rules for this are not documented but appear to be the following
+ * according to the source for the microsoft C library.
+ * Words are seperated by space or tab
+ * Words containing a space or tab can be quoted using "
+ * 2N backslashes + " ==> N backslashes and end quote
+ * 2N+1 backslashes + " ==> N backslashes + literal "
+ * N backslashes not followed by " ==> N backslashes
+ */
+static int
+args(char *argv[], int n, char *p)
+{
+ char *p2;
+ int i, j, quote, nbs;
+
+ for(i=0; *p && i<n-1; i++) {
+ while(*p == ' ' || *p == '\t')
+ p++;
+ quote = 0;
+ argv[i] = p2 = p;
+ for(;*p; p++) {
+ if(!quote && (*p == ' ' || *p == '\t'))
+ break;
+ for(nbs=0; *p == '\\'; p++,nbs++)
+ ;
+ if(*p == '"') {
+ for(j=0; j<(nbs>>1); j++)
+ *p2++ = '\\';
+ if(nbs&1)
+ *p2++ = *p;
+ else
+ quote = !quote;
+ } else {
+ for(j=0; j<nbs; j++)
+ *p2++ = '\\';
+ *p2++ = *p;
+ }
+ }
+ /* move p up one to avoid pointing to null at end of p2 */
+ if(*p)
+ p++;
+ *p2 = 0;
+ }
+ argv[i] = 0;
+
+ return i;
+}
+/*
+ * Windows socket error messages
+ * There must be a way to get these strings out of the library.
+ * This table is derived from the MSDN online help.
+ */
+static struct {
+ int e;
+ char *s;
+} tab[] = {
+ { 10004, "interrupted function call" },
+ { 10013, "permission denied" },
+ { 10014, "bad address" },
+ { 10022, "invalid argument" },
+ { 10024, "too many open files" },
+ { 10035, "resource temporarily unavailable" },
+ { 10036, "operation now in progress" },
+ { 10037, "operation already in progress" },
+ { 10038, "socket operation on nonsocket" },
+ { 10039, "destination address required" },
+ { 10040, "message too long" },
+ { 10041, "protocol wrong type for socket" },
+ { 10042, "bad protocol option" },
+ { 10043, "protocol not supported" },
+ { 10044, "socket type not supported" },
+ { 10045, "operation not supported" },
+ { 10046, "protocol family not supported" },
+ { 10047, "address family not supported by protocol family" },
+ { 10048, "address already in use" },
+ { 10049, "cannot assign requested address" },
+ { 10050, "network is down" },
+ { 10051, "network is unreachable" },
+ { 10052, "network dropped connection on reset" },
+ { 10053, "software caused connection abort" },
+ { 10054, "connection reset by peer" },
+ { 10055, "no buffer space available" },
+ { 10056, "socket is already connected" },
+ { 10057, "socket is not connected" },
+ { 10058, "cannot send after socket shutdown" },
+ { 10060, "connection timed out" },
+ { 10061, "connection refused" },
+ { 10064, "host is down" },
+ { 10065, "no route to host" },
+ { 10067, "too many processes" },
+ { 10091, "network subsystem is unavailable" },
+ { 10092, "winsock.dll version out of range" },
+ { 10093, "wsastartup not called" },
+ { 10101, "graceful shutdown in progress" },
+ { 10109, "class type not found" },
+ { 11001, "host name not found" },
+ { 11002, "host not found (non-authoritative)" },
+ { 11003, "nonrecoverable error" },
+ { 11004, "valid name, but no data record of requested type" },
+};
+
+void
+osrerrstr(char *buf, uint nbuf)
+{
+ char *p, *q;
+ int e, i, r;
+
+ e = GetLastError();
+ r = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM,
+ 0, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ buf, nbuf, 0);
+ if(r == 0){
+ for(i=0; i<nelem(tab); i++)
+ if(tab[i].e == e){
+ strecpy(buf, buf+nbuf, tab[i].s);
+ break;
+ }
+ if(i==nelem(tab))
+ snprint(buf, nbuf, "windows error %d", e);
+ }
+
+ for(p=q=buf; *p; p++) {
+ if(*p == '\r')
+ continue;
+ if(*p == '\n')
+ *q++ = ' ';
+ else
+ *q++ = *p;
+ }
+ *q = '\0';
+}
+
+void
+oserrstr(void)
+{
+ osrerrstr(up->errstr, ERRMAX);
+}
+
+long
+showfilewrite(char *a, int n)
+{
+ Rune *action, *arg, *cmd;
+ static Rune Lopen[] = { 'o', 'p', 'e', 'n', 0 };
+
+ cmd = runesmprint("%.*s", n, a);
+ if(cmd == nil)
+ error("out of memory");
+ if(cmd[runestrlen(cmd)-1] == '\n')
+ cmd[runestrlen(cmd)] = 0;
+ p = runestrchr(cmd, ' ');
+ if(p){
+ action = cmd;
+ *p++ = 0;
+ arg = p;
+ }else{
+ action = Lopen;
+ arg = cmd;
+ }
+ ShellExecute(0, 0, action, arg, 0, SW_SHOWNORMAL);
+ return n;
+}