/* * Framework for USB devices that provide a file tree. * The main process (usbd or the driver's main proc) * calls fsinit() to start FS operation. * * One or more drivers call fsstart/fsend to register * or unregister their operations for their subtrees. * * root dir has qids with 0 in high 32 bits. * for other files we keep the device id in there. * The low 32 bits for directories at / must be 0. */ #include #include #include #include #include "usb.h" #include "usbfs.h" #undef dprint #define dprint if(usbfsdebug)fprint typedef struct Rpc Rpc; enum { Nproc = 3, /* max nb. of cached FS procs */ Nofid = ~0, /* null value for fid number */ Notag = ~0, /* null value for tags */ Dietag = 0xdead, /* fake tag to ask outproc to die */ Stack = 16 * 1024, /* Fsproc requests */ Run = 0, /* call f(r) */ Exit, /* terminate */ }; struct Rpc { Fcall t; Fcall r; Fid* fid; int flushed; Rpc* next; char data[Bufsize]; }; int usbfsdebug; char Enotfound[] = "file not found"; char Etoosmall[] = "parameter too small"; char Eio[] = "i/o error"; char Eperm[] = "permission denied"; char Ebadcall[] = "unknown fs call"; char Ebadfid[] = "fid not found"; char Einuse[] = "fid already in use"; char Eisopen[] = "it is already open"; char Ebadctl[] = "unknown control request"; static char *user; static ulong epoch; static ulong msgsize = Msgsize; static int fsfd = -1; static Channel *outc; /* of Rpc* */ static QLock rpclck; /* protect vars in this block */ static Fid *freefids; static Fid *fids; static Rpc *freerpcs; static Rpc *rpcs; static Channel*procc; static Channel*endc; static Usbfs* fsops; static void fsioproc(void*); static void schedproc(void*) { Channel *proc[Nproc]; int nproc; Channel *p; Alt a[] = { {procc, &proc[0], CHANSND}, {endc, &p, CHANRCV}, {nil, nil, CHANEND} }; memset(proc, 0, sizeof(proc)); nproc = 0; for(;;){ if(nproc == 0){ proc[0] = chancreate(sizeof(Rpc*), 0); proccreate(fsioproc, proc[0], Stack); nproc++; } switch(alt(a)){ case 0: proc[0] = nil; if(nproc > 1){ proc[0] = proc[nproc-1]; proc[nproc-1] = nil; } nproc--; break; case 1: if(nproc < nelem(proc)) proc[nproc++] = p; else sendp(p, nil); break; default: sysfatal("alt"); } } } static void dump(void) { Rpc *rpc; Fid *fid; qlock(&rpclck); fprint(2, "dump:\n"); for(rpc = rpcs; rpc != nil; rpc = rpc->next) fprint(2, "rpc %#p %F next %#p\n", rpc, &rpc->t, rpc->next); for(fid = fids; fid != nil; fid = fid->next) fprint(2, "fid %d qid %#llux omode %d aux %#p\n", fid->fid, fid->qid.path, fid->omode, fid->aux); fprint(2, "\n"); qunlock(&rpclck); } static Rpc* newrpc(void) { Rpc *r; qlock(&rpclck); r = freerpcs; if(r != nil) freerpcs = r->next; else r = emallocz(sizeof(Rpc), 0); r->next = rpcs; rpcs = r; r->t.tag = r->r.tag = Notag; r->t.fid = r->r.fid = Nofid; r->t.type = r->r.type = 0; r->flushed = 0; r->fid = nil; r->r.data = (char*)r->data; qunlock(&rpclck); return r; } static void freerpc(Rpc *r) { Rpc **l; if(r == nil) return; qlock(&rpclck); for(l = &rpcs; *l != nil && *l != r; l = &(*l)->next) ; assert(*l == r); *l = r->next; r->next = freerpcs; freerpcs = r; r->t.type = 0; r->t.tag = 0x77777777; qunlock(&rpclck); } static void flushrpc(int tag) { Rpc *r; qlock(&rpclck); for(r = rpcs; r != nil; r = r->next) if(r->t.tag == tag){ r->flushed = 1; break; } qunlock(&rpclck); } static Fid* getfid(int fid, int alloc) { Fid *f; qlock(&rpclck); for(f = fids; f != nil && f->fid != fid; f = f->next) ; if(f != nil && alloc != 0){ /* fid in use */ qunlock(&rpclck); return nil; } if(f == nil && alloc != 0){ if(freefids != nil){ f = freefids; freefids = freefids->next; }else f = emallocz(sizeof(Fid), 1); f->fid = fid; f->aux = nil; f->omode = ONONE; f->next = fids; fids = f; } qunlock(&rpclck); return f; } static void freefid(Fid *f) { Fid **l; if(f == nil) return; if(fsops->clunk != nil) fsops->clunk(fsops, f); qlock(&rpclck); for(l = &fids; *l != nil && *l != f; l = &(*l)->next) ; assert(*l == f); *l = f->next; f->next = freefids; freefids = f; qunlock(&rpclck); } static Rpc* fserror(Rpc *rpc, char* fmt, ...) { va_list arg; char *c; va_start(arg, fmt); c = (char*)rpc->data; vseprint(c, c+sizeof(rpc->data), fmt, arg); va_end(arg); rpc->r.type = Rerror; rpc->r.ename = (char*)rpc->data; return rpc; } static Rpc* fsversion(Rpc *r) { if(r->t.msize < 256) return fserror(r, Etoosmall); if(strncmp(r->t.version, "9P2000", 6) != 0) return fserror(r, "wrong version"); if(r->t.msize < msgsize) msgsize = r->t.msize; r->r.msize = msgsize; r->r.version = "9P2000"; return r; } static Rpc* fsattach(Rpc *r) { static int already; /* Reload user because at boot it could be still none */ user=getuser(); if(already++ > 0 && strcmp(r->t.uname, user) != 0) return fserror(r, Eperm); if(r->fid == nil) return fserror(r, Einuse); r->r.qid.type = QTDIR; r->r.qid.path = fsops->qid; r->r.qid.vers = 0; r->fid->qid = r->r.qid; return r; } static Rpc* fswalk(Rpc *r) { int i; Fid *nfid, *ofid; if(r->fid->omode != ONONE) return fserror(r, Eisopen); nfid = nil; ofid = r->fid; if(r->t.newfid != r->t.fid){ nfid = getfid(r->t.newfid, 1); if(nfid == nil) return fserror(r, Einuse); nfid->qid = r->fid->qid; if(fsops->clone != nil) fsops->clone(fsops, ofid, nfid); else nfid->aux = r->fid->aux; r->fid = nfid; } r->r.nwqid = 0; for(i = 0; i < r->t.nwname; i++) if(fsops->walk(fsops, r->fid, r->t.wname[i]) < 0) break; else r->r.wqid[i] = r->fid->qid; r->r.nwqid = i; if(i != r->t.nwname && r->t.nwname > 0){ if(nfid != nil) freefid(nfid); r->fid = ofid; } if(i == 0 && r->t.nwname > 0) return fserror(r, "%r"); return r; } static void fsioproc(void* a) { long rc; Channel *p = a; Rpc *rpc; Fcall *t, *r; Fid *fid; threadsetname("fsioproc"); dprint(2, "%s: fsioproc pid %d\n", argv0, getpid()); while((rpc = recvp(p)) != nil){ t = &rpc->t; r = &rpc->r; fid = rpc->fid; rc = -1; dprint(2, "%s: fsioproc pid %d: req %d\n", argv0, getpid(), t->type); switch(t->type){ case Topen: rc = fsops->open(fsops, fid, t->mode); if(rc >= 0){ r->iounit = 0; r->qid = fid->qid; fid->omode = t->mode & 3; } break; case Tread: rc = fsops->read(fsops, fid, r->data, t->count, t->offset); if(rc >= 0){ if(rc > t->count) print("%s: bug: read %ld bytes > %ud wanted\n", argv0, rc, t->count); r->count = rc; } /* * TODO: if we encounter a long run of continuous read * errors, we should do something more drastic so that * our caller doesn't just spin its wheels forever. */ break; case Twrite: rc = fsops->write(fsops, fid, t->data, t->count, t->offset); r->count = rc; break; default: sysfatal("fsioproc: bad type"); } if(rc < 0) sendp(outc, fserror(rpc, "%r")); else sendp(outc, rpc); sendp(endc, p); } chanfree(p); dprint(2, "%s: fsioproc %d exiting\n", argv0, getpid()); threadexits(nil); } static Rpc* fsopen(Rpc *r) { Channel *p; if(r->fid->omode != ONONE) return fserror(r, Eisopen); if((r->t.mode & 3) != OREAD && (r->fid->qid.type & QTDIR) != 0) return fserror(r, Eperm); p = recvp(procc); sendp(p, r); return nil; } int usbdirread(Usbfs*f, Qid q, char *data, long cnt, vlong off, Dirgen gen, void *arg) { int i, n, nd; char name[Namesz]; Dir d; memset(&d, 0, sizeof(d)); d.name = name; d.uid = d.gid = d.muid = user; d.atime = time(nil); d.mtime = epoch; d.length = 0; for(i = n = 0; gen(f, q, i, &d, arg) >= 0; i++){ if(usbfsdebug > 1) fprint(2, "%s: dir %d q %#llux: %D\n", argv0, i, q.path, &d); nd = convD2M(&d, (uchar*)data+n, cnt-n); if(nd <= BIT16SZ) break; if(off > 0) off -= nd; else n += nd; d.name = name; d.uid = d.gid = d.muid = user; d.atime = time(nil); d.mtime = epoch; d.length = 0; } return n; } long usbreadbuf(void *data, long count, vlong offset, void *buf, long n) { if(offset >= n) return 0; if(offset + count > n) count = n - offset; memmove(data, (char*)buf + offset, count); return count; } static Rpc* fsread(Rpc *r) { Channel *p; if(r->fid->omode != OREAD && r->fid->omode != ORDWR) return fserror(r, Eperm); p = recvp(procc); sendp(p, r); return nil; } static Rpc* fswrite(Rpc *r) { Channel *p; if(r->fid->omode != OWRITE && r->fid->omode != ORDWR) return fserror(r, Eperm); p = recvp(procc); sendp(p, r); return nil; } static Rpc* fsclunk(Rpc *r) { freefid(r->fid); return r; } static Rpc* fsno(Rpc *r) { return fserror(r, Eperm); } static Rpc* fsstat(Rpc *r) { Dir d; char name[Namesz]; memset(&d, 0, sizeof(d)); d.name = name; d.uid = d.gid = d.muid = user; d.atime = time(nil); d.mtime = epoch; d.length = 0; if(fsops->stat(fsops, r->fid->qid, &d) < 0) return fserror(r, "%r"); r->r.stat = (uchar*)r->data; r->r.nstat = convD2M(&d, (uchar*)r->data, msgsize); return r; } static Rpc* fsflush(Rpc *r) { /* * Flag it as flushed and respond. * Either outproc will reply to the flushed request * before responding to flush, or it will never reply to it. * Note that we do NOT abort the ongoing I/O. * That might leave the affected endpoints in a failed * state. Instead, we pretend the request is aborted. * * Only open, read, and write are processed * by auxiliary processes and other requests wil never be * flushed in practice. */ flushrpc(r->t.oldtag); return r; } Rpc* (*fscalls[])(Rpc*) = { [Tversion] fsversion, [Tauth] fsno, [Tattach] fsattach, [Twalk] fswalk, [Topen] fsopen, [Tcreate] fsno, [Tread] fsread, [Twrite] fswrite, [Tclunk] fsclunk, [Tremove] fsno, [Tstat] fsstat, [Twstat] fsno, [Tflush] fsflush, }; static void outproc(void*) { static uchar buf[Bufsize]; Rpc *rpc; int nw; static int once = 0; if(once++ != 0) sysfatal("more than one outproc"); threadsetname("outproc"); for(;;){ do rpc = recvp(outc); while(rpc == nil); /* a delayed reply */ if(rpc->t.tag == Dietag) break; if(rpc->flushed){ dprint(2, "outproc: tag %d flushed\n", rpc->t.tag); freerpc(rpc); continue; } dprint(2, "-> %F\n", &rpc->r); nw = convS2M(&rpc->r, buf, sizeof(buf)); if(nw == sizeof(buf)) fprint(2, "%s: outproc: buffer is too small\n", argv0); if(nw <= BIT16SZ) fprint(2, "%s: conS2M failed\n", argv0); else if(write(fsfd, buf, nw) != nw){ fprint(2, "%s: outproc: write: %r", argv0); /* continue and let the reader abort us */ } if(usbfsdebug > 1) dump(); freerpc(rpc); } dprint(2, "%s: outproc: exiting\n", argv0); } static void usbfs(void*) { Rpc *rpc; int nr; static int once = 0; if(once++ != 0) sysfatal("more than one usbfs proc"); threadsetname("usbfs"); outc = chancreate(sizeof(Rpc*), 1); procc = chancreate(sizeof(Channel*), 0); endc = chancreate(sizeof(Channel*), 0); if(outc == nil || procc == nil || endc == nil) sysfatal("chancreate: %r"); threadcreate(schedproc, nil, Stack); proccreate(outproc, nil, Stack); for(;;){ rpc = newrpc(); do{ nr = read9pmsg(fsfd, rpc->data, sizeof(rpc->data)); }while(nr == 0); if(nr < 0){ dprint(2, "%s: usbfs: read: '%r'", argv0); if(fsops->end != nil) fsops->end(fsops); else closedev(fsops->dev); rpc->t.tag = Dietag; sendp(outc, rpc); break; } if(convM2S((uchar*)rpc->data, nr, &rpc->t) <=0){ dprint(2, "%s: convM2S failed\n", argv0); freerpc(rpc); continue; } dprint(2, "<- %F\n", &rpc->t); rpc->r.tag = rpc->t.tag; rpc->r.type = rpc->t.type + 1; rpc->r.fid = rpc->t.fid; if(fscalls[rpc->t.type] == nil){ sendp(outc, fserror(rpc, Ebadcall)); continue; } if(rpc->t.fid != Nofid){ if(rpc->t.type == Tattach) rpc->fid = getfid(rpc->t.fid, 1); else rpc->fid = getfid(rpc->t.fid, 0); if(rpc->fid == nil){ sendp(outc, fserror(rpc, Ebadfid)); continue; } } sendp(outc, fscalls[rpc->t.type](rpc)); } dprint(2, "%s: ubfs: eof: exiting\n", argv0); } void usbfsinit(char* srv, char *mnt, Usbfs *f, int flag) { int fd[2]; int sfd; int afd; char sfile[40]; fsops = f; if(pipe(fd) < 0) sysfatal("pipe: %r"); user = getuser(); epoch = time(nil); fmtinstall('D', dirfmt); fmtinstall('M', dirmodefmt); fmtinstall('F', fcallfmt); fsfd = fd[1]; procrfork(usbfs, nil, Stack, RFNAMEG); /* no RFFDG */ if(srv != nil){ snprint(sfile, sizeof(sfile), "#s/%s", srv); remove(sfile); sfd = create(sfile, OWRITE, 0660); if(sfd < 0) sysfatal("post: %r"); snprint(sfile, sizeof(sfile), "%d", fd[0]); if(write(sfd, sfile, strlen(sfile)) != strlen(sfile)) sysfatal("post: %r"); close(sfd); } if(mnt != nil){ sfd = dup(fd[0], -1); /* debug */ afd = fauth(sfd, ""); if(afd >= 0) sysfatal("authentication required??"); if(mount(sfd, -1, mnt, flag, "") < 0) sysfatal("mount: %r"); } close(fd[0]); }