ref: 11025d6f4a2c3c7e75010f85c2d3108e602270b7
dir: /sys/src/cmd/nusb/ptp/ptp.c/
#include <u.h> #include <libc.h> #include <thread.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, GetPartialObject = 0x101B, Maxio = 0x2000, }; typedef struct Ptprpc Ptprpc; typedef struct Node Node; struct Ptprpc { uchar length[4]; uchar type[2]; uchar code[2]; uchar transid[4]; uchar d[500]; }; struct Node { Dir d; Node *parent; Node *next; Node *child; int store; int handle; int format; void *data; int ndata; }; enum { In, Out, Int, Setup, }; static Dev *usbep[Setup+1]; static int debug = 0; static ulong time0; static int maxpacket = 64; static int sessionId = 1; static int transId = 1; 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.d + i*4, 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){ n = e - p; if(n > Maxio) n = Maxio; if((n = iowrite(io, usbep[Out]->dfd, p, n)) < 0){ 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; while((n = ioread(io, usbep[In]->dfd, &rpc, usbep[In]->maxpkt)) <= 0){ if(n < 0){ wasinterrupt(); return -1; } } if((l = ptpcheckerr(&rpc, 2, t, n)) < 0) return -1; if(l && GET2(rpc.type) == 3) goto Resp1; l = GET4(rpc.length); l -= (4+2+2+4); n -= (4+2+2+4); b = emalloc9p(l); p = b; e = b+l; if(n <= l){ if(debug) hexdump("data<", rpc.d, n); memmove(p, rpc.d, n); p += n; while(p < e){ n = e-p; if(n > Maxio) n = Maxio; while((n = ioread(io, usbep[In]->dfd, p, n)) <= 0){ if(n < 0){ wasinterrupt(); free(b); return -1; } } if(debug) hexdump("data<", p, n); p += n; } *prdata = b; *prdatalen = e-b; } else { if(debug) hexdump("data<", rpc.d, l); memmove(p, rpc.d, l); *prdata = b; *prdatalen = e-b; n -= l; memmove(&rpc, rpc.d+l, n); goto Resp1; } } while((n = ioread(io, usbep[In]->dfd, &rpc, usbep[In]->maxpkt)) <= 0){ if(n < 0){ wasinterrupt(); return -1; } } Resp1: if(debug) hexdump("resp<", (uchar*)&rpc, n); if(ptpcheckerr(&rpc, 3, t, n)) return -1; if(flags & OutParam){ int *pp; for(i=0; i<5; i++){ if((pp = va_arg(a, int*)) == nil) break; *pp = GET4(rpc.d + i*4); } } return 0; } static int ptprpc(Req *r, int code, int flags, ...) { va_list va; Channel *c; Ioproc *io; Alt a[3]; char *m; int i; i = -1; c = nil; io = nil; m = Einterrupt; a[0].op = CHANRCV; a[0].c = iochan; a[0].v = &io; if(r){ c = chancreate(sizeof(char*), 0); a[1].op = CHANRCV; a[1].c = c; a[1].v = &m; a[2].op = CHANEND; r->aux = c; srvrelease(r->srv); } else a[1].op = CHANEND; if(alt(a) == 0){ va_start(va, flags); i = vptprpc(io, code, flags, va); va_end(va); if(i < 0 && debug) fprint(2, "rpc: %r\n"); } else werrstr("%s", m); if(r){ srvacquire(r->srv); r->aux = nil; } if(io) sendp(iochan, io); if(c) chanfree(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; if(ptprpc(r, GetStorageInfo, 1|DataRecv, NUM(path), &p, &np) < 0) break; if(debug) hexdump("storageinfo", p, np); if(np < 26){ werrstr("bad storageinfo"); break; } if((x->d.name = ptpstring2(p+26, p+np)) == nil){ werrstr("bad storageinfo"); break; } free(p); 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){ 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; } if(nod->data == nil){ int offset, count, pos; offset = r->ifcall.offset; if(offset >= nod->d.length){ r->ofcall.count = 0; respond(r, nil); return; } /* are these people stupid? */ pos = (offset == 0) ? 1 : 2; count = r->ifcall.count; if((count + offset) > nod->d.length){ count = nod->d.length - offset; pos = 3; } if(!ptprpc(r, GetPartialObject, 4|DataRecv, nod->handle, offset, count, pos, &p, &np)){ if(np <= count){ memmove(r->ofcall.data, p, np); r->ofcall.count = np; respond(r, nil); free(p); return; } } free(p); } /* no break */ case Qthumb: if(nod->data == nil){ np = 0; p = nil; if(ptprpc(r, TYPE(path)==Qthumb ? GetThumb : GetObject, 1|DataRecv, nod->handle, &p, &np) < 0){ free(p); break; } nod->data = p; nod->ndata = np; } readbuf(r, nod->data, nod->ndata); respond(r, nil); return; } } 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) nbsendp(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 *epint) { int i; Ep *ep; Usbdev *ud; ud = d->usb; *epin = *epout = *epint = -1; for(i=0; i<nelem(ud->ep); i++){ if((ep = ud->ep[i]) == nil) continue; if(ep->type == Eintr && *epint == -1) *epint = ep->id; 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, epint; Dev *d; ARGBEGIN { case 'd': debug++; break; case 'D': chatty9p++; break; default: usage(); } ARGEND; if(argc == 0) usage(); if((d = getdev(*argv)) == nil) sysfatal("opendev: %r"); if(findendpoints(d, &epin, &epout, &epint) < 0) sysfatal("findendpoints: %r"); usbep[In] = openep(usbep[Setup] = 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"); if(usbep[In]->maxpkt < 12 || usbep[In]->maxpkt > sizeof(Ptprpc)) sysfatal("bad packet size: %d\n", usbep[In]->maxpkt); 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%s", d->hname); snprint(desc, sizeof desc, "%d.ptp", d->id); threadpostsharesrv(&fs, nil, name, desc); threadexits(0); }