git: 9front

Download patch

ref: 6f4f7004681fb3156bdccfe9619423541801e5d1
parent: f65f8427a0544a7f1d04d4a4d33cecc118eb47a5
author: cinap_lenrek <cinap_lenrek@localhost>
date: Sun Aug 21 23:05:01 EDT 2011

nusb/ptp: fix memory leaks, use libthread/ioprocs, cleanup

--- a/sys/src/cmd/nusb/ptp/mkfile
+++ b/sys/src/cmd/nusb/ptp/mkfile
@@ -5,7 +5,7 @@
 
 TARG=ptp
 HFILES=
-OFILES=usbptp.$O
+OFILES=ptp.$O
 
 </sys/src/cmd/mkone
 
--- /dev/null
+++ b/sys/src/cmd/nusb/ptp/ptp.c
@@ -1,0 +1,1014 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <bio.h>
+#include <auth.h>
+#include <fcall.h>
+#include <9p.h>
+
+#include "usb.h"
+
+enum
+{
+	Qroot,
+	Qstore,
+	Qobj,
+	Qthumb,
+};
+
+enum {
+	/* flags */
+	DataSend			=	0x00010000,
+	DataRecv			=	0x00020000,
+	OutParam			=	0x00040000,
+
+	/* rpc codes */
+	OpenSession		=	0x1002,
+	CloseSession		=	0x1003,
+	GetStorageIds		=	0x1004,
+	GetStorageInfo		=	0x1005,
+	GetObjectHandles	=	0x1007,
+	GetObjectInfo		=	0x1008,
+	GetObject			=	0x1009,
+	GetThumb		=	0x100A,
+	DeleteObject		=	0x100B,
+};
+
+typedef struct Ptprpc Ptprpc;
+typedef struct Node Node;
+typedef struct Ioflush Ioflush;
+
+struct Ptprpc
+{
+	uchar	length[4];
+	uchar	type[2];
+	uchar	code[2];
+	uchar	transid[4];
+	union {
+		uchar	p[5][4];
+		uchar	d[500];
+	};
+};
+
+struct Node
+{
+	Dir		d;
+
+	Node	*parent;
+	Node	*next;
+	Node	*child;
+
+	int		store;
+	int		handle;
+	int		format;
+
+	void		*data;
+	int		ndata;
+};
+
+struct Ioflush
+{
+	Channel	*c;
+	Ioproc	*io;
+};
+
+
+enum {
+	In,
+	Out,
+};
+
+static Dev *usbep[2];
+
+static int debug;
+static ulong time0;
+static int maxpacket = 64;
+static int sessionId = 0;
+static int transId = 0;
+
+static Node **nodes;
+static int nnodes;
+
+static Channel *iochan;
+
+char Eperm[] = "permission denied";
+char Einterrupt[] = "interrupted";
+
+#define PATH(type, n)		((uvlong)(type)|((uvlong)(n)<<4))
+#define TYPE(path)			((int)((path)&0xF))
+#define NUM(path)			((int)((path)>>4))
+
+static void
+hexdump(char *prefix, uchar *p, int n)
+{
+	char *s;
+	int i;
+	int m;
+
+	m = 12;
+	s = emalloc9p(1+((n+1)*((m*6)+7)));
+	s[0] = '\0';
+	for(i=0; i<n; i++){
+		int printable;
+		char x[8];
+		if((i % m)==0){
+			sprint(x, "\n%.4x: ", i);
+			strcat(s, x);
+		}
+		printable = (p[i] >= 32 && p[i]<=127);
+		sprint(x, "%.2x %c  ", (int)p[i],  printable ? p[i] : '.');
+		strcat(s, x);
+	}
+	fprint(2, "%20-s: %6d bytes %s\n", prefix, n, s);
+	free(s);
+}
+
+static int
+wasinterrupt(void)
+{
+	char err[ERRMAX];
+
+	rerrstr(err, sizeof(err));
+	if(strstr(err, Einterrupt) || strstr(err, "timed out")){
+		werrstr(Einterrupt);
+		return 1;
+	}
+	return 0;
+}
+
+static char *
+ptperrstr(int code)
+{
+	static char *a[] = {
+		"undefined",
+		nil ,
+		"general error" ,
+		"session not open" ,
+		 "invalid transaction id" ,
+		 "operation not supported" ,
+		 "parameter not supported" ,
+		 "incomplete transfer" ,
+		 "invalid storage id" ,
+		 "invalid object handle" ,
+		 "device prop not supported" ,
+		 "invalid object format code" ,
+		 "storage full" ,
+		 "object write protected" ,
+		 "store read only" ,
+		 "access denied" ,
+		 "no thumbnail present" ,
+		 "self test failed" ,
+		 "partial deletion" ,
+		 "store not available" ,
+		 "specification by format unsupported" ,
+		 "no valid object info" ,
+		 "invalid code format" ,
+		 "unknown vendor code",
+		"capture already terminated",
+		"device busy",
+		"invalid parent object",
+		"invalid device prop format",
+		"invalid device prop value",
+		"invalid parameter",
+		"session already opend",
+		"transaction canceld",
+		"specification of destination unsupported"
+	};
+
+	code -= 0x2000;
+	if(code < 0)
+		return nil;
+	if(code >= nelem(a))
+		return "invalid error number";
+	return a[code];
+}
+
+static int
+ptpcheckerr(Ptprpc *rpc, int type, int transid, int length)
+{
+	char *s;
+
+	if(length < 4+2+2+4){
+		werrstr("short response: %d < %d", length, 4+2+2+4);
+		return 1;
+	}
+	if(GET4(rpc->length) < length){
+		werrstr("unexpected response length 0x%x < 0x%x", GET4(rpc->length), length);
+		return 1;
+	}
+	if(GET4(rpc->transid) != transid){
+		werrstr("unexpected transaction id 0x%x != 0x%x", GET4(rpc->transid), transid);
+		return 1;
+	}
+	if(GET2(rpc->type) != type){
+		werrstr("unexpected response type 0x%x != 0x%x", GET2(rpc->type), type);
+		return 1;
+	}
+	if(s = ptperrstr(GET2(rpc->code))){
+		werrstr("%s", s);
+		return -GET2(rpc->code);
+	}
+	return 0;
+}
+
+static int
+vptprpc(Ioproc *io, int code, int flags, va_list a)
+{
+	Ptprpc rpc;
+	int np, n, t, i, l;
+	uchar *b, *p, *e;
+
+	np = flags & 0xF;
+	n = 4+2+2+4+4*np;
+	t = transId++;
+
+	PUT4(rpc.length, n);
+	PUT2(rpc.type, 1);
+	PUT2(rpc.code, code);
+	PUT4(rpc.transid, t);
+
+	for(i=0; i<np; i++){
+		int x = va_arg(a, int);
+		PUT4(rpc.p[i], x);
+	}
+	if(debug)
+		hexdump("req>", (uchar*)&rpc, n);
+
+	werrstr("");
+	if(iowrite(io, usbep[Out]->dfd, &rpc, n) != n){
+		wasinterrupt();
+		return -1;
+	}
+
+	if(flags & DataSend){
+		void *sdata;
+		int sdatalen;
+
+		sdata = va_arg(a, void*);
+		sdatalen = va_arg(a, int);
+
+		b = (uchar*)sdata;
+		p = b;
+		e = b + sdatalen;
+
+		l = 4+2+2+4+sdatalen;
+		PUT4(rpc.length, l);
+		PUT2(rpc.type, 2);
+
+		if((n = sdatalen) > sizeof(rpc.d))
+			n = sizeof(rpc.d);
+		memmove(rpc.d, p, n);
+		p += n;
+		n += (4+2+2+4);
+
+		if(debug)
+			hexdump("data>", (uchar*)&rpc, n);
+		if(iowrite(io, usbep[Out]->dfd, &rpc, n) != n){
+			wasinterrupt();
+			return -1;
+		}
+		while(p < e){	
+			if((n = iowrite(io, usbep[Out]->dfd, p, e-p)) < 0){
+				if(!wasinterrupt())
+					return -1;
+			}
+			p += n;
+		}
+	}
+
+	if(flags & DataRecv){
+		void **prdata;
+		int *prdatalen;
+
+		prdata = va_arg(a, void**);
+		prdatalen = va_arg(a, int*);
+
+		*prdata = nil;
+		*prdatalen = 0;
+
+		do{
+			if((n = ioread(io, usbep[In]->dfd, &rpc, sizeof(rpc))) < 0){
+				wasinterrupt();
+				return -1;
+			}
+			if(debug)
+				hexdump("data<", (uchar*)&rpc, n);
+			if((l = ptpcheckerr(&rpc, 2, t, n)) < 0)
+				return -1;
+		} while(l);
+
+		l = GET4(rpc.length);
+		if((l < 4+2+2+4) || (l < n)){
+			werrstr("invalid recvdata length");
+			return -1;
+		}
+
+		l -= (4+2+2+4);
+		n -= (4+2+2+4);
+
+		b = emalloc9p(l);
+		p = b;
+		e = b+l;
+		memmove(p, rpc.d, n);
+		p += n;
+
+		while(p < e){
+			if((n = ioread(io, usbep[In]->dfd, p, e-p)) < 0){
+				wasinterrupt();
+				free(b);
+				return -1;
+			}
+			p += n;
+		}
+		*prdata = b;
+		*prdatalen =  e-b;
+	}
+
+	do {
+		if((n = ioread(io, usbep[In]->dfd, &rpc, sizeof(rpc))) < 0){
+			wasinterrupt();
+			return -1;
+		}
+		if(debug)
+			hexdump("resp<", (uchar*)&rpc, n);
+		if((l = ptpcheckerr(&rpc, 3, t, n)) < 0)
+			return -1;
+	} while(l);
+
+	if(flags & OutParam){
+		int *pp;
+
+		for(i=0; i<nelem(rpc.p); i++){
+			if((pp = va_arg(a, int*)) == nil)
+				break;
+			*pp = GET4(rpc.p[i]);
+		}
+	}
+	return 0;
+}
+
+static void
+ioflusher(void *aux)
+{
+	Ioflush f = *((Ioflush*)aux);
+	while(recvp(f.c))
+		iointerrupt(f.io);
+	chanfree(f.c);
+}
+
+static int
+ptprpc(Req *r, int code, int flags, ...)
+{
+	va_list va;
+	Ioflush f;
+	Alt a[3];
+	char *m;
+	int i;
+
+	i = -1;
+	f.c = nil;
+	f.io = nil;
+	m = Einterrupt;
+	a[0].op = CHANRCV;
+	a[0].c = iochan;
+	a[0].v = &f.io;
+	if(r){
+		f.c = chancreate(sizeof(char*), 0);
+		a[1].op = CHANRCV;
+		a[1].c = f.c;
+		a[1].v = &m;
+		a[2].op = CHANEND;
+		r->aux = f.c;
+		srvrelease(r->srv);
+	} else
+		a[1].op = CHANEND;
+	if(alt(a) == 0){
+		ioflush(f.io);
+		if(f.c)
+			threadcreate(ioflusher, &f, 4*1024);
+		va_start(va, flags);
+		i = vptprpc(f.io, code, flags, va);
+		va_end(va);
+	} else
+		werrstr("%s", m);
+	if(r){
+		srvacquire(r->srv);
+		r->aux = nil;
+	}
+	if(f.io){
+		if(f.c)
+			sendp(f.c, nil);
+		sendp(iochan, f.io);
+	} else if(f.c)
+		chanfree(f.c);
+	return i;
+}
+
+static int*
+ptparray4(uchar *d, uchar *e)
+{
+	int *a, i, n;
+
+	if(d + 4 > e)
+		return nil;
+	n = GET4(d);
+	d += 4;
+	if(d + n*4 > e)
+		return nil;
+	a = emalloc9p((1+n) * sizeof(int));
+	a[0] = n;
+	for(i=0; i<n; i++){
+		a[i+1] = GET4(d);
+		d += 4;
+	}
+	return a;
+}
+
+static char*
+ptpstring2(uchar *d, uchar *e)
+{
+	int n, i;
+	char *s, *p;
+
+	if(d+1 > e)
+		return nil;
+	n = *d;
+	d++;
+	if(d + n*2 > e)
+		return nil;
+	p = s = emalloc9p((n+1)*UTFmax);
+	for(i=0; i<n; i++){
+		Rune r;
+
+		r = GET2(d);
+		d += 2;
+		if(r == 0)
+			break;
+		p += runetochar(p, &r);
+	}
+	*p = 0;
+	return s;
+}
+
+static void
+cleardir(Dir *d)
+{
+	free(d->name);
+	free(d->uid);
+	free(d->gid);
+	free(d->muid);
+	memset(d, 0, sizeof(*d));
+}
+
+static void
+copydir(Dir *d, Dir *s)
+{
+	memmove(d, s, sizeof(*d));
+	if(d->name)
+		d->name = estrdup9p(d->name);
+	if(d->uid)
+		d->uid = estrdup9p(d->uid);
+	if(d->gid)
+		d->gid = estrdup9p(d->gid);
+	if(d->muid)
+		d->muid = estrdup9p(d->muid);
+}
+
+static Node*
+cachednode(uvlong path, Node ***pf)
+{
+	Node *x;
+	int i;
+
+	if(pf)
+		*pf = nil;
+	for(i=0; i<nnodes; i++){
+		if((x = nodes[i]) == nil){
+			if(pf)
+				*pf = &nodes[i];
+			continue;
+		}
+		if(x->d.qid.path == path)
+			return x;
+	}
+	return nil;
+}
+
+static Node*
+getnode(Req *r, uvlong path)
+{
+	Node *x, *y, **f;
+	uchar *p;
+	int np;
+	char *s;
+
+	if(x = cachednode(path, &f))
+		return x;
+
+	y = nil;
+	x = emalloc9p(sizeof(*x));
+	memset(x, 0, sizeof(*x));
+	x->d.qid.path = path;
+	x->d.uid = estrdup9p("ptp");
+	x->d.gid = estrdup9p("usb");
+	x->d.atime = x->d.mtime = time0;
+
+	p = nil;
+	np = 0;
+	switch(TYPE(path)){
+	case Qroot:
+		x->d.qid.type = QTDIR;
+		x->d.mode = DMDIR|0777;
+		x->d.name = estrdup9p("/");
+		goto Addnode;
+
+	case Qstore:
+		x->store = NUM(path);
+		x->handle = 0xffffffff;
+		x->d.qid.type = QTDIR;
+		x->d.mode = DMDIR|0777;
+		x->d.name = emalloc9p(10);
+		sprint(x->d.name, "%x", x->store);
+		goto Addnode;
+
+	case Qobj:
+	case Qthumb:
+		if(ptprpc(r, GetObjectInfo, 1|DataRecv, NUM(path), &p, &np) < 0)
+			break;
+		if(debug)
+			hexdump("objectinfo", p, np);
+		if(np < 52){
+			werrstr("bad objectinfo");
+			break;
+		}
+
+		/*
+		 * another proc migh'v come in and done it for us,
+		 * so check the cache again.
+		 */
+		if(y = cachednode(path, &f))
+			break;
+
+		if((x->d.name = ptpstring2(p+52, p+np)) == nil){
+			werrstr("bad objectinfo");
+			break;
+		}
+		x->handle = NUM(path);
+		x->store = GET4(p);
+		x->format = GET2(p+4);
+		if(x->format == 0x3001 && GET2(p+42) == 1){
+			x->d.qid.type = QTDIR;
+			x->d.mode = DMDIR|0777;
+		} else {
+			x->d.mode = 0666;
+			if(TYPE(path) == Qthumb){
+				char *t;
+
+				t = emalloc9p(8 + strlen(x->d.name));
+				sprint(t, "thumb_%s", x->d.name);
+				free(x->d.name);
+				x->d.name = t;
+
+				x->d.length = GET4(p+14);
+			} else {
+				x->d.length = GET4(p+8);
+			}
+		}
+		if(s = ptpstring2(p+(53+p[52]*2), p+np)){
+			if(strlen(s) >= 15){
+				Tm t;
+
+				// 0123 45 67 8 9A BC DF
+				// 2008 12 26 T 00 21 18
+				memset(&t, 0, sizeof(t));
+
+				s[0x10] = 0;
+				t.sec = atoi(s+0xD);
+				s[0xD] = 0;
+				t.min = atoi(s+0xB);
+				s[0xB] = 0;
+				t.hour = atoi(s+0x9);
+				s[0x8] = 0;
+				t.mday = atoi(s+0x6);
+				s[0x6] = 0;
+				t.mon = atoi(s+0x4) - 1;
+				s[0x4] = 0;
+				t.year = atoi(s) - 1900;
+
+				x->d.atime = x->d.mtime = tm2sec(&t);
+			}
+			free(s);
+		}
+		free(p);
+	Addnode:
+		if(f == nil){
+			if(nnodes % 64 == 0)
+				nodes = erealloc9p(nodes, sizeof(nodes[0]) * (nnodes + 64));
+			f = &nodes[nnodes++];
+		}
+		return *f = x;
+	}
+
+	cleardir(&x->d);
+	free(x);
+	free(p);
+	return y;
+}
+
+static void
+freenode(Node *nod)
+{
+	int i;
+
+	/* remove the node from the tree */
+	for(i=0; i<nnodes; i++){
+		if(nod == nodes[i]){
+			nodes[i] = nil;
+			break;
+		}
+	}
+	cleardir(&nod->d);
+	free(nod->data);
+	free(nod);
+}
+
+static int
+readchilds(Req *r, Node *nod)
+{
+	int e, i;
+	int *a;
+	uchar *p;
+	int np;
+	Node *x, **xx;
+
+	e = 0;
+	switch(TYPE(nod->d.qid.path)){
+	case Qroot:
+		if(ptprpc(r, GetStorageIds, 0|DataRecv, &p, &np) < 0)
+			return -1;
+		a = ptparray4(p, p+np);
+		free(p);
+		xx = &nod->child;
+		*xx = nil;
+		for(i=0; a && i<a[0]; i++){
+			if((x = getnode(r, PATH(Qstore, a[i+1]))) == nil){
+				e = -1;
+				break;
+			}
+			x->parent = nod;
+			*xx = x;
+			xx = &x->next;
+			*xx = nil;
+		}		
+		free(a);
+		break;
+
+	case Qstore:
+	case Qobj:
+		if(ptprpc(r, GetObjectHandles, 3|DataRecv, nod->store, 0, nod->handle, &p, &np) < 0)
+			return -1;
+		a = ptparray4(p, p+np);
+		free(p);
+		xx = &nod->child;
+		*xx = nil;
+		for(i=0; a && i<a[0]; i++){
+			if((x = getnode(r, PATH(Qobj, a[i+1]))) == nil){
+				e = -1;
+				break;
+			}
+			x->parent = nod;
+			*xx = x;
+			xx = &x->next;
+			*xx = nil;
+
+			/* skip thumb when not image format */
+			if((x->format & 0xFF00) != 0x3800)
+				continue;
+			if((x = getnode(r, PATH(Qthumb, a[i+1]))) == nil){
+				e = -1;
+				break;
+			}
+			x->parent = nod;
+			*xx = x;
+			xx = &x->next;
+			*xx = nil;
+		}
+		free(a);
+		break;
+	}
+
+	return e;
+}
+
+static void
+fsattach(Req *r)
+{
+	if(r->ifcall.aname && r->ifcall.aname[0]){
+		respond(r, "invalid attach specifier");
+		return;
+	}
+	r->fid->qid.path = PATH(Qroot, 0);
+	r->fid->qid.type = QTDIR;
+	r->fid->qid.vers = 0;
+	r->ofcall.qid = r->fid->qid;
+	respond(r, nil);
+}
+
+static void
+fsstat(Req *r)
+{
+	Node *nod;
+
+	if((nod = getnode(r, r->fid->qid.path)) == nil){
+		responderror(r);
+		return;
+	}
+	copydir(&r->d, &nod->d);
+	respond(r, nil);
+}
+
+static int
+nodegen(int i, Dir *d, void *aux)
+{
+	Node *nod = aux;
+
+	for(nod=nod->child; nod && i; nod=nod->next, i--)
+		;
+	if(i==0 && nod){
+		copydir(d, &nod->d);
+		return 0;
+	}
+	return -1;
+}
+
+static char*
+fswalk1(Req *r, char *name, Qid *qid)
+{
+	static char buf[ERRMAX];
+	uvlong path;
+	Node *nod;
+	Fid *fid;
+
+	fid = r->newfid;
+	path = fid->qid.path;
+	if(!(fid->qid.type&QTDIR))
+		return "walk in non-directory";
+	if(nod = getnode(r, path)){
+		if(strcmp(name, "..") == 0){
+			if(nod = nod->parent)
+				*qid = nod->d.qid;
+			return nil;
+		}
+		if(readchilds(r, nod) == 0){
+			for(nod=nod->child; nod; nod=nod->next){
+				if(strcmp(nod->d.name, name) == 0){
+					*qid = nod->d.qid;
+					return nil;
+				}
+			}
+			return "directory entry not found";
+		}
+	}
+	rerrstr(buf, sizeof(buf));
+	return buf;
+}
+
+static char*
+oldwalk1(Fid *fid, char *name, void *arg)
+{
+	Qid qid;
+	char *e;
+	Req *r;
+
+	r = arg;
+	assert(fid == r->newfid);
+	if(e = fswalk1(r, name, &qid))
+		return e;
+	fid->qid = qid;
+	return nil;
+}
+
+static char*
+oldclone(Fid *, Fid *, void *)
+{
+	return nil;
+}
+
+static void
+fswalk(Req *r)
+{
+	walkandclone(r, oldwalk1, oldclone, r);
+}
+
+static void
+fsread(Req *r)
+{
+	uvlong path;
+	Node *nod;
+	uchar *p;
+	int np;
+
+	np = 0;
+	p = nil;
+	path = r->fid->qid.path;
+	if(nod = getnode(r, path)){
+		switch(TYPE(path)){
+		case Qroot:
+		case Qstore:
+		case Qobj:
+			if(nod->d.qid.type & QTDIR){
+				if(readchilds(r, nod) < 0)
+					break;
+				dirread9p(r, nodegen, nod);
+				respond(r, nil);
+				return;
+			}
+			/* no break */
+		case Qthumb:
+			if(nod->data == nil){
+				if(ptprpc(r, TYPE(path)==Qthumb ? GetThumb : GetObject,
+					1|DataRecv, nod->handle, &p, &np) < 0)
+					break;
+				nod->data = p;
+				nod->ndata = np;
+			}
+			readbuf(r, nod->data, nod->ndata);
+			respond(r, nil);
+			return;
+		}
+	}
+	free(p);
+	responderror(r);
+}
+
+static void
+fsremove(Req *r)
+{
+	Node *nod;
+	uvlong path;
+
+	path = r->fid->qid.path;
+	if(nod = getnode(r, path)){
+		switch(TYPE(path)){
+		default:
+			werrstr(Eperm);
+			break;
+		case Qobj:
+			if(ptprpc(r, DeleteObject, 2, nod->handle, 0) < 0)
+				break;
+			/* no break */
+		case Qthumb:
+			if(nod = cachednode(path, nil))
+				freenode(nod);
+			respond(r, nil);
+			return;
+		}
+	}
+	responderror(r);
+}
+
+static void
+fsopen(Req *r)
+{
+	if(r->ifcall.mode != OREAD){
+		respond(r, Eperm);
+		return;
+	}
+	respond(r, nil);
+}
+
+static void
+fsflush(Req *r)
+{
+	Channel *c;
+
+	if(c = r->oldreq->aux)
+		sendp(c, Einterrupt);
+	respond(r, nil);
+}
+
+static void
+fsdestroyfid(Fid *fid)
+{
+	Node *nod;
+	uvlong path;
+
+	path = fid->qid.path;
+	switch(TYPE(path)){
+	case Qobj:
+	case Qthumb:
+		if(nod = cachednode(path, nil)){
+			free(nod->data);
+			nod->data = nil;
+			nod->ndata = 0;
+		}
+		break;
+	}
+}
+
+static void
+fsend(Srv *)
+{
+	ptprpc(nil, CloseSession, 0);
+	closeioproc(recvp(iochan));
+}
+
+static int
+findendpoints(Dev *d, int *epin, int *epout)
+{
+	int i;
+	Ep *ep;
+	Usbdev *ud;
+
+	ud = d->usb;
+	*epin = *epout = -1;
+	for(i=0; i<nelem(ud->ep); i++){
+		if((ep = ud->ep[i]) == nil)
+			continue;
+		if(ep->type != Ebulk)
+			continue;
+		if(ep->dir == Eboth || ep->dir == Ein)
+			if(*epin == -1)
+				*epin =  ep->id;
+		if(ep->dir == Eboth || ep->dir == Eout)
+			if(*epout == -1)
+				*epout = ep->id;
+		if(*epin >= 0 && *epout >= 0)
+			return 0;
+	}
+	return -1;
+}
+
+Srv fs = 
+{
+	.attach = fsattach,
+	.destroyfid = fsdestroyfid,
+	.walk = fswalk,
+	.open = fsopen,
+	.read = fsread,
+	.remove = fsremove,
+	.stat = fsstat,
+	.flush = fsflush,
+	.end = fsend,
+};
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-dD] devid\n", argv0);
+	threadexits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	char name[64], desc[64];
+	int epin, epout;
+	Dev *d;
+
+	ARGBEGIN {
+	case 'd':
+		debug++;
+		break;
+	case 'D':
+		chatty9p++;
+		break;
+	default:
+		usage();
+	} ARGEND;
+
+	if(argc == 0)
+		usage();
+	if((d = getdev(atoi(*argv))) == nil)
+		sysfatal("opendev: %r");
+	if(findendpoints(d, &epin, &epout)  < 0)
+		sysfatal("findendpoints: %r");
+
+	usbep[In] = openep(d, epin);
+	if(epin == epout){
+		incref(usbep[In]);
+		usbep[Out] = usbep[In];
+		opendevdata(usbep[In], ORDWR);
+	} else {
+		usbep[Out] = openep(d, epout);
+		opendevdata(usbep[In], OREAD);
+		opendevdata(usbep[Out], OWRITE);
+	}
+	if(usbep[In]->dfd < 0 || usbep[Out]->dfd < 0)
+		sysfatal("open endpoints: %r");
+
+	iochan = chancreate(sizeof(Ioproc*), 1);
+	sendp(iochan, ioproc());
+
+	sessionId = getpid();
+	if(ptprpc(nil, OpenSession, 1, sessionId) < 0)
+		sysfatal("open session: %r");
+
+	time0 = time(0);
+
+	snprint(name, sizeof name, "sdU%d.0", d->id);
+	snprint(desc, sizeof desc, "%d.ptp", d->id);
+	threadpostsharesrv(&fs, nil, name, desc);
+
+	threadexits(0);
+}
--- a/sys/src/cmd/nusb/ptp/usbptp.c
+++ /dev/null
@@ -1,987 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <thread.h>
-#include <bio.h>
-#include <auth.h>
-#include <fcall.h>
-#include <9p.h>
-
-#include "usb.h"
-
-enum
-{
-	Qroot,
-	Qstore,
-	Qobj,
-	Qthumb,
-};
-
-enum {
-	/* flags */
-	DataSend			=	0x00010000,
-	DataRecv			=	0x00020000,
-	OutParam			=	0x00040000,
-
-	/* rpc codes */
-	OpenSession		=	0x1002,
-	CloseSession		=	0x1003,
-	GetStorageIds		=	0x1004,
-	GetStorageInfo		=	0x1005,
-	GetObjectHandles	=	0x1007,
-	GetObjectInfo		=	0x1008,
-	GetObject			=	0x1009,
-	GetThumb		=	0x100A,
-	DeleteObject		=	0x100B,
-};
-
-typedef struct Ptprpc Ptprpc;
-struct Ptprpc
-{
-	uchar	length[4];
-	uchar	type[2];
-	uchar	code[2];
-	uchar	transid[4];
-	union {
-		uchar	p[5][4];
-		uchar	d[500];
-	};
-};
-
-typedef struct Node Node;
-struct Node
-{
-	Dir		d;
-
-	Node	*parent;
-	Node	*next;
-	Node	*child;
-
-	int		store;
-	int		handle;
-	int		format;
-
-	void		*data;
-	int		ndata;
-};
-
-int debug;
-
-enum {
-	In,
-	Out,
-};
-static Dev *usbep[2];
-
-static ulong time0;
-static int maxpacket = 64;
-static int sessionId = 0;
-static int transId = 0;
-
-static Node **nodes;
-static int nnodes;
-
-static char *uname;
-
-#define PATH(type, n)		((uvlong)(type)|((uvlong)(n)<<4))
-#define TYPE(path)			((int)((path)&0xF))
-#define NUM(path)			((int)((path)>>4))
-
-static void
-hexdump(char *prefix, uchar *p, int n)
-{
-	char *s;
-	int i;
-	int m;
-
-	m = 12;
-	s = emalloc9p(1+((n+1)*((m*6)+7)));
-	s[0] = '\0';
-	for(i=0; i<n; i++){
-		int printable;
-		char x[8];
-		if((i % m)==0){
-			sprint(x, "\n%.4x: ", i);
-			strcat(s, x);
-		}
-		printable = (p[i] >= 32 && p[i]<=127);
-		sprint(x, "%.2x %c  ", (int)p[i],  printable ? p[i] : '.');
-		strcat(s, x);
-	}
-	fprint(2, "%20-s: %6d bytes %s\n", prefix, n, s);
-	free(s);
-}
-
-static int
-wasinterrupt(void)
-{
-	char err[ERRMAX];
-
-	rerrstr(err, sizeof(err));
-	if(strstr(err, "interrupted") || strstr(err, "request timed out")){
-		werrstr("interrupted");
-		return 1;
-	}
-	return 0;
-}
-
-static char *
-ptperrstr(int code)
-{
-	static char *a[] = {
-		"undefined",
-		nil ,
-		"general error" ,
-		"session not open" ,
-		 "invalid transaction id" ,
-		 "operation not supported" ,
-		 "parameter not supported" ,
-		 "incomplete transfer" ,
-		 "invalid storage id" ,
-		 "invalid object handle" ,
-		 "device prop not supported" ,
-		 "invalid object format code" ,
-		 "storage full" ,
-		 "object write protected" ,
-		 "store read only" ,
-		 "access denied" ,
-		 "no thumbnail present" ,
-		 "self test failed" ,
-		 "partial deletion" ,
-		 "store not available" ,
-		 "specification by format unsupported" ,
-		 "no valid object info" ,
-		 "invalid code format" ,
-		 "unknown vendor code",
-		"capture already terminated",
-		"device busy",
-		"invalid parent object",
-		"invalid device prop format",
-		"invalid device prop value",
-		"invalid parameter",
-		"session already opend",
-		"transaction canceld",
-		"specification of destination unsupported"
-	};
-
-	code -= 0x2000;
-	if(code < 0)
-		return nil;
-	if(code >= nelem(a))
-		return "invalid error number";
-	return a[code];
-}
-
-static int
-ptpcheckerr(Ptprpc *rpc, int type, int transid, int length)
-{
-	char *s;
-
-	if(length < 4+2+2+4){
-		werrstr("short response: %d < %d", length, 4+2+2+4);
-		return 1;
-	}
-	if(GET4(rpc->length) < length){
-		werrstr("unexpected response length 0x%x < 0x%x", GET4(rpc->length), length);
-		return 1;
-	}
-	if(GET4(rpc->transid) != transid){
-		werrstr("unexpected transaction id 0x%x != 0x%x", GET4(rpc->transid), transid);
-		return 1;
-	}
-	if(GET2(rpc->type) != type){
-		werrstr("unexpected response type 0x%x != 0x%x", GET2(rpc->type), type);
-		return 1;
-	}
-	if(s = ptperrstr(GET2(rpc->code))){
-		werrstr("%s", s);
-		return -GET2(rpc->code);
-	}
-	return 0;
-}
-
-static int
-vptprpc(int code, int flags, va_list a)
-{
-	Ptprpc rpc;
-	int np, n, t, i, l;
-	uchar *b, *p, *e;
-
-	np = flags & 0xF;
-	n = 4+2+2+4+4*np;
-	t = transId++;
-
-	PUT4(rpc.length, n);
-	PUT2(rpc.type, 1);
-	PUT2(rpc.code, code);
-	PUT4(rpc.transid, t);
-
-	for(i=0; i<np; i++){
-		int x = va_arg(a, int);
-		PUT4(rpc.p[i], x);
-	}
-	if(debug)
-		hexdump("req>", (uchar*)&rpc, n);
-
-	werrstr("");
-	if(write(usbep[Out]->dfd, &rpc, n) != n){
-		wasinterrupt();
-		return -1;
-	}
-
-	if(flags & DataSend){
-		void *sdata;
-		int sdatalen;
-
-		sdata = va_arg(a, void*);
-		sdatalen = va_arg(a, int);
-
-		b = (uchar*)sdata;
-		p = b;
-		e = b + sdatalen;
-
-		l = 4+2+2+4+sdatalen;
-		PUT4(rpc.length, l);
-		PUT2(rpc.type, 2);
-
-		if((n = sdatalen) > sizeof(rpc.d))
-			n = sizeof(rpc.d);
-		memmove(rpc.d, p, n);
-		p += n;
-		n += (4+2+2+4);
-
-		if(debug)
-			hexdump("data>", (uchar*)&rpc, n);
-		if(write(usbep[Out]->dfd, &rpc, n) != n){
-			wasinterrupt();
-			return -1;
-		}
-		while(p < e){	
-			if((n = write(usbep[Out]->dfd, p, e-p)) < 0){
-				wasinterrupt();
-				break;
-			}
-			p += n;
-		}
-	}
-
-	if(flags & DataRecv){
-		void **prdata;
-		int *prdatalen;
-
-		prdata = va_arg(a, void**);
-		prdatalen = va_arg(a, int*);
-
-		*prdata = nil;
-		*prdatalen = 0;
-
-		do{
-			if((n = read(usbep[In]->dfd, &rpc, sizeof(rpc))) < 0){
-				wasinterrupt();
-				return -1;
-			}
-			if(debug > 1)
-				hexdump("data<", (uchar*)&rpc, n);
-			if((l = ptpcheckerr(&rpc, 2, t, n)) < 0)
-				return -1;
-		} while(l);
-
-		l = GET4(rpc.length);
-		if((l < 4+2+2+4) || (l < n)){
-			werrstr("invalid recvdata length");
-			return -1;
-		}
-
-		l -= (4+2+2+4);
-		n -= (4+2+2+4);
-
-		b = emalloc9p(l);
-		p = b;
-		e = b+l;
-		memmove(p, rpc.d, n);
-		p += n;
-
-		while(p < e){
-			if((n = read(usbep[In]->dfd, p, e-p)) < 0){
-				wasinterrupt();
-				free(b);
-				return -1;
-			}
-			p += n;
-		}
-		*prdata = b;
-		*prdatalen =  e-b;
-	}
-
-	do {
-		if((n = read(usbep[In]->dfd, &rpc, sizeof(rpc))) < 0){
-			wasinterrupt();
-			return -1;
-		}
-		if(debug > 1)
-			hexdump("resp<", (uchar*)&rpc, n);
-		if((l = ptpcheckerr(&rpc, 3, t, n)) < 0)
-			return -1;
-	} while(l);
-
-	if(flags & OutParam){
-		int *pp;
-
-		for(i=0; i<nelem(rpc.p); i++){
-			if((pp = va_arg(a, int*)) == nil)
-				break;
-			*pp = GET4(rpc.p[i]);
-		}
-	}
-	return 0;
-}
-
-static int
-ptprpc(Req *r, int code, int flags, ...)
-{
-	static long ptpsem = 1;
-	va_list a;
-	int i;
-
-	if(r != nil){
-		r->aux = (void*)getpid();
-		srvrelease(r->srv);
-	}
-	i = semacquire(&ptpsem, 1);
-	if(i == 1){
-		va_start(a, flags);
-		i = vptprpc(code, flags, a);
-		va_end(a);
-		semrelease(&ptpsem, 1);
-	}
-	if(i < 0 && debug)
-		fprint(2, "ptprpc: req=%p %r\n", r);
-	if(r != nil){
-		srvacquire(r->srv);
-		r->aux = (void*)-1;
-	}
-	return i;
-}
-
-static int*
-ptparray4(uchar *d, uchar *e)
-{
-	int *a, i, n;
-
-	if(d + 4 > e)
-		return nil;
-	n = GET4(d);
-	d += 4;
-	if(d + n*4 > e)
-		return nil;
-	a = emalloc9p((1+n) * sizeof(int));
-	a[0] = n;
-	for(i=0; i<n; i++){
-		a[i+1] = GET4(d);
-		d += 4;
-	}
-	return a;
-}
-
-static char*
-ptpstring2(uchar *d, uchar *e)
-{
-	int n, i;
-	char *s, *p;
-
-	if(d+1 > e)
-		return nil;
-	n = *d;
-	d++;
-	if(d + n*2 > e)
-		return nil;
-	p = s = emalloc9p((n+1)*UTFmax);
-	for(i=0; i<n; i++){
-		Rune r;
-
-		r = GET2(d);
-		d += 2;
-		if(r == 0)
-			break;
-		p += runetochar(p, &r);
-	}
-	*p = 0;
-	return s;
-}
-
-static void
-cleardir(Dir *d)
-{
-	free(d->name);
-	free(d->uid);
-	free(d->gid);
-	free(d->muid);
-	memset(d, 0, sizeof(*d));
-}
-
-static void
-copydir(Dir *d, Dir *s)
-{
-	memmove(d, s, sizeof(*d));
-	if(d->name)
-		d->name = estrdup9p(d->name);
-	if(d->uid)
-		d->uid = estrdup9p(d->uid);
-	if(d->gid)
-		d->gid = estrdup9p(d->gid);
-	if(d->muid)
-		d->muid = estrdup9p(d->muid);
-}
-
-static Node*
-getnode(uvlong path, Req *r)
-{
-	int i, j;
-	Node *x;
-	uchar *p;
-	int np;
-	char *s;
-
-	j = -1;
-	for(i=0; i<nnodes; i++){
-		if((x = nodes[i]) == nil){
-			j = i;
-			continue;
-		}
-		if(x->d.qid.path == path)
-			return x;
-	}
-
-	x = emalloc9p(sizeof(*x));
-
-	memset(x, 0, sizeof(*x));
-
-	x->d.qid.path = path;
-	x->d.uid = estrdup9p(uname);
-	x->d.gid = estrdup9p(uname);
-	x->d.atime = x->d.mtime = time0;
-
-	p = nil;
-	np = 0;
-	switch(TYPE(path)){
-	case Qroot:
-		x->d.qid.type = QTDIR;
-		x->d.mode = DMDIR|0777;
-		x->d.name = estrdup9p("/");
-		break;
-
-	case Qstore:
-		x->store = NUM(path);
-		x->handle = 0xffffffff;
-		x->d.qid.type = QTDIR;
-		x->d.mode = DMDIR|0777;
-		x->d.name = emalloc9p(10);
-		sprint(x->d.name, "%x", x->store);
-		break;
-
-	case Qobj:
-	case Qthumb:
-		if(ptprpc(r, GetObjectInfo, 1|DataRecv, NUM(path), &p, &np) < 0)
-			goto err;
-		if(debug > 1)
-			hexdump("objectinfo", p, np);
-		if(np < 52){
-			werrstr("bad objectinfo");
-			goto err;
-		}
-
-		j = -1;
-		for(i=0; i<nnodes; i++){
-			if(nodes[i] == nil){
-				j = i;
-				continue;
-			}
-			if(nodes[i]->d.qid.path == path){
-				/* never mind */
-				cleardir(&x->d);
-				free(x);
-				return nodes[i];
-			}
-		}
-
-		if((x->d.name = ptpstring2(p+52, p+np)) == nil){
-			werrstr("bad objectinfo");
-			goto err;
-		}
-		x->handle = NUM(path);
-		x->store = GET4(p);
-		x->format = GET2(p+4);
-		if(x->format == 0x3001 && GET2(p+42) == 1){
-			x->d.qid.type = QTDIR;
-			x->d.mode = DMDIR|0777;
-		} else {
-			x->d.mode = 0666;
-			if(TYPE(path) == Qthumb){
-				char *t;
-
-				t = emalloc9p(8 + strlen(x->d.name));
-				sprint(t, "thumb_%s", x->d.name);
-				free(x->d.name);
-				x->d.name = t;
-
-				x->d.length = GET4(p+14);
-			} else {
-				x->d.length = GET4(p+8);
-			}
-		}
-		if(s = ptpstring2(p+(53+p[52]*2), p+np)){
-			if(strlen(s) >= 15){
-				Tm t;
-
-				// 0123 45 67 8 9A BC DF
-				// 2008 12 26 T 00 21 18
-				memset(&t, 0, sizeof(t));
-
-				s[0x10] = 0;
-				t.sec = atoi(s+0xD);
-				s[0xD] = 0;
-				t.min = atoi(s+0xB);
-				s[0xB] = 0;
-				t.hour = atoi(s+0x9);
-				s[0x8] = 0;
-				t.mday = atoi(s+0x6);
-				s[0x6] = 0;
-				t.mon = atoi(s+0x4) - 1;
-				s[0x4] = 0;
-				t.year = atoi(s) - 1900;
-
-				x->d.atime = x->d.mtime = tm2sec(&t);
-			}
-			free(s);
-		}
-		free(p);
-		break;
-	}
-
-	if(j < 0){
-		if(nnodes % 64 == 0)
-			nodes = erealloc9p(nodes, sizeof(nodes[0]) * (nnodes + 64));
-		j = nnodes++;
-	}
-	return nodes[j] = x;
-
-err:
-	cleardir(&x->d);
-	free(x);
-	free(p);
-
-	return nil;
-}
-
-static void
-freenode(Node *nod)
-{
-	int i;
-
-	/* remove the node from the tree */
-	for(i=0; i<nnodes; i++){
-		if(nod == nodes[i]){
-			nodes[i] = nil;
-			break;
-		}
-	}
-	cleardir(&nod->d);
-	free(nod->data);
-	free(nod);
-}
-
-static int
-readchilds(Node *nod, Req *r)
-{
-	int i;
-	int *a;
-	uchar *p;
-	int np;
-	Node *x, **xx;
-
-	switch(TYPE(nod->d.qid.path)){
-	case Qroot:
-		if(ptprpc(r, GetStorageIds, 0|DataRecv, &p, &np) < 0)
-			return -1;
-		a = ptparray4(p, p+np);
-		free(p);
-		xx = &nod->child;
-		for(i=0; a && i<a[0]; i++){
-			if(x = getnode(PATH(Qstore, a[i+1]), r)){
-				x->parent = nod;
-				*xx = x;
-				xx = &x->next;
-			}
-		}		
-		*xx = nil;
-		free(a);
-		break;
-
-	case Qstore:
-	case Qobj:
-		if(ptprpc(r, GetObjectHandles, 3|DataRecv, nod->store, 0, nod->handle, &p, &np) < 0)
-			return -1;
-		a = ptparray4(p, p+np);
-		free(p);
-		xx = &nod->child;
-		for(i=0; a && i<a[0]; i++){
-			if(x = getnode(PATH(Qobj, a[i+1]), r)){
-				x->parent = nod;
-				*xx = x;
-				xx = &x->next;
-
-				/* skip thumb when not image format */
-				if((x->format & 0xFF00) != 0x3800)
-					continue;
-			}
-			if(x = getnode(PATH(Qthumb, a[i+1]), r)){
-				x->parent = nod;
-				*xx = x;
-				xx = &x->next;
-			}
-		}
-		*xx = nil;
-		free(a);
-		break;
-	}
-
-	return 0;
-}
-
-static void
-fsattach(Req *r)
-{
-	if(r->ifcall.aname && r->ifcall.aname[0]){
-		respond(r, "invalid attach specifier");
-		return;
-	}
-	if(uname == nil)
-		uname = estrdup9p(r->ifcall.uname);
-	r->fid->qid.path = PATH(Qroot, 0);
-	r->fid->qid.type = QTDIR;
-	r->fid->qid.vers = 0;
-	r->ofcall.qid = r->fid->qid;
-	respond(r, nil);
-}
-
-static void
-fsstat(Req *r)
-{
-	Node *nod;
-
-	if((nod = getnode(r->fid->qid.path, r)) == nil){
-		responderror(r);
-		return;
-	}
-	copydir(&r->d, &nod->d);
-	respond(r, nil);
-}
-
-static int
-nodegen(int i, Dir *d, void *aux)
-{
-	Node *nod = aux;
-
-	for(nod=nod->child; nod && i; nod=nod->next, i--)
-		;
-	if(i==0 && nod){
-		copydir(d, &nod->d);
-		return 0;
-	}
-	return -1;
-}
-
-static char*
-fswalk1(Req *r, char *name, Qid *qid)
-{
-	static char buf[ERRMAX];
-	uvlong path;
-	Node *nod;
-	Fid *fid;
-
-	fid = r->newfid;
-	path = fid->qid.path;
-	if(!(fid->qid.type&QTDIR))
-		return "walk in non-directory";
-
-	if((nod = getnode(path, r)) == nil)
-		goto err;
-
-	if(strcmp(name, "..") == 0){
-		if(nod = nod->parent)
-			*qid = nod->d.qid;
-		return nil;
-	}
-	if(readchilds(nod, r) < 0)
-		goto err;
-	for(nod=nod->child; nod; nod=nod->next){
-		if(strcmp(nod->d.name, name) == 0){
-			*qid = nod->d.qid;
-			return nil;
-		}
-	}
-	return "directory entry not found";
-
-err:
-	rerrstr(buf, sizeof(buf));
-	return buf;
-}
-
-static char*
-oldwalk1(Fid *fid, char *name, void *arg)
-{
-	Qid qid;
-	char *e;
-	Req *r;
-
-	r = arg;
-	assert(fid == r->newfid);
-	if(e = fswalk1(r, name, &qid))
-		return e;
-	fid->qid = qid;
-	return nil;
-}
-
-static char*
-oldclone(Fid *, Fid *, void *)
-{
-	return nil;
-}
-
-static void
-fswalk(Req *r)
-{
-	walkandclone(r, oldwalk1, oldclone, r);
-}
-
-static void
-fsread(Req *r)
-{
-	uvlong path;
-	Node *nod;
-	uchar *p;
-	int np;
-
-	np = 0;
-	p = nil;
-	path = r->fid->qid.path;
-	if((nod = getnode(path, r)) == nil)
-		goto err;
-
-	if(nod->d.qid.type & QTDIR){
-		if(readchilds(nod, r) < 0)
-			goto err;
-		dirread9p(r, nodegen, nod);
-		respond(r, nil);
-		return;
-	}
-
-	switch(TYPE(path)){
-	default:
-		werrstr("bug in fsread path=%llux", path);
-		break;
-
-	case Qobj:
-	case Qthumb:
-		if(nod->data == nil){
-			if(TYPE(path)==Qthumb){
-				if(ptprpc(r, GetThumb, 1|DataRecv, nod->handle, &p, &np) < 0)
-					goto err;
-			} else {
-				if(ptprpc(r, GetObject, 1|DataRecv, nod->handle, &p, &np) < 0)
-					goto err;
-			}
-			nod->data = p;
-			nod->ndata = np;
-		}
-		readbuf(r, nod->data, nod->ndata);
-		respond(r, nil);
-		return;
-	}
-err:
-	free(p);
-	responderror(r);
-}
-
-static void
-fsremove(Req *r)
-{
-	Node *nod;
-	uvlong path;
-
-	path = r->fid->qid.path;
-	if((nod = getnode(path, r)) == nil)
-		goto err;
-
-	switch(TYPE(path)){
-	default:
-		werrstr("bug in fsremove path=%llux", path);
-		break;
-	case Qobj:
-		if(ptprpc(r, DeleteObject, 2, nod->handle, 0) < 0)
-			goto err;
-	case Qthumb:
-		freenode(nod);
-		respond(r, nil);
-		return;
-	}
-err:
-	responderror(r);
-}
-
-static void
-fsopen(Req *r)
-{
-	if(r->ifcall.mode != OREAD){
-		respond(r, "permission denied");
-		return;
-	}
-	respond(r, nil);
-}
-
-static void
-fsflush(Req *r)
-{
-	Req *o;
-
-	if(o = r->oldreq){
-		if(debug)
-			fprint(2, "fsflush: req=%p\n", o);
-		int pid = (int)o->aux;
-		if(pid != -1 && pid != 0){
-			o->aux = (void*)-1;
-			postnote(PNPROC, pid, "interrupt");
-		}
-	}
-	respond(r, nil);
-}
-
-static void
-fsdestroyfid(Fid *fid)
-{
-	Node *nod;
-	uvlong path;
-
-	path = fid->qid.path;
-	switch(TYPE(path)){
-	case Qobj:
-	case Qthumb:
-		if(nod = getnode(path, nil)){
-			free(nod->data);
-			nod->data = nil;
-			nod->ndata = 0;
-		}
-		break;
-	}
-}
-
-static void
-fsend(Srv *)
-{
-	ptprpc(nil, CloseSession, 0);
-}
-
-static int
-findendpoints(Dev *d, int *epin, int *epout)
-{
-	int i;
-	Ep *ep;
-	Usbdev *ud;
-
-	ud = d->usb;
-	*epin = *epout = -1;
-	for(i=0; i<nelem(ud->ep); i++){
-		if((ep = ud->ep[i]) == nil)
-			continue;
-		if(ep->type != Ebulk)
-			continue;
-		if(ep->dir == Eboth || ep->dir == Ein)
-			if(*epin == -1)
-				*epin =  ep->id;
-		if(ep->dir == Eboth || ep->dir == Eout)
-			if(*epout == -1)
-				*epout = ep->id;
-		if(*epin >= 0 && *epout >= 0)
-			return 0;
-	}
-	return -1;
-}
-
-static int
-inote(void *, char *msg)
-{
-	if(strstr(msg, "interrupt"))
-		return 1;
-	return 0;
-}
-
-Srv fs = 
-{
-.attach=		fsattach,
-.destroyfid=	fsdestroyfid,
-.walk=		fswalk,
-.open=		fsopen,
-.read=		fsread,
-.remove=		fsremove,
-.stat=		fsstat,
-.flush=		fsflush,
-.end=		fsend,
-};
-
-static void
-usage(void)
-{
-	fprint(2, "usage: %s [-dD] devid\n", argv0);
-	exits("usage");
-}
-
-void
-main(int argc, char **argv)
-{
-	int epin, epout;
-	char name[64], desc[64];
-	Dev *d;
-
-	ARGBEGIN {
-	case 'd':
-		debug++;
-		break;
-	case 'D':
-		chatty9p++;
-		break;
-	default:
-		usage();
-	} ARGEND;
-
-	if(argc == 0)
-		usage();
-	if((d = getdev(atoi(*argv))) == nil)
-		sysfatal("opendev: %r");
-	if(findendpoints(d, &epin, &epout)  < 0)
-		sysfatal("findendpoints: %r");
-
-	usbep[In] = openep(d, epin);
-	if(epin == epout){
-		incref(usbep[In]);
-		usbep[Out] = usbep[In];
-		opendevdata(usbep[In], ORDWR);
-	} else {
-		usbep[Out] = openep(d, epout);
-		opendevdata(usbep[In], OREAD);
-		opendevdata(usbep[Out], OWRITE);
-	}
-	if(usbep[In]->dfd < 0 || usbep[Out]->dfd < 0)
-		sysfatal("open endpoints: %r");
-
-	sessionId = getpid();
-	if(ptprpc(nil, OpenSession, 1, sessionId) < 0)
-		return;
-
-	atnotify(inote, 1);
-	time0 = time(0);
-
-	snprint(name, sizeof name, "sdU%d.0", d->id);
-	snprint(desc, sizeof desc, "%d.ptp", d->id);
-	postsharesrv(&fs, nil, name, desc);
-
-	exits(0);
-}
--