Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * 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 <u.h>
#include <libc.h>
#include <thread.h>
#include <fcall.h>
#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]);
}