Subversion Repositories planix.SVN

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Web file system.  Conventionally mounted at /mnt/web
 *
 *      ctl                             send control messages (might go away)
 *      cookies                 list of cookies, editable
 *      clone                   open and read to obtain new connection
 *      n                               connection directory
 *              ctl                             control messages (like get url)
 *              body                            retrieved data
 *              content-type            mime content-type of body
 *              postbody                        data to be posted
 *              parsed                  parsed version of url
 *                      url                             entire url
 *                      scheme                  http, ftp, etc.
 *                      host                            hostname
 *                      path                            path on host
 *                      query                   query after path
 *                      fragment                        #foo anchor reference
 *                      user                            user name (ftp)
 *                      password                        password (ftp)
 *                      ftptype                 transfer mode (ftp)
 */

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ip.h>
#include <plumb.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
#include "dat.h"
#include "fns.h"

int fsdebug;

enum
{
        Qroot,
        Qrootctl,
        Qclone,
        Qcookies,
        Qclient,
        Qctl,
        Qbody,
        Qbodyext,
        Qcontenttype,
        Qpostbody,
        Qparsed,
        Qurl,
        Qscheme,
        Qschemedata,
        Quser,
        Qpasswd,
        Qhost,
        Qport,
        Qpath,
        Qquery,
        Qfragment,
        Qftptype,
        Qend,
};

#define PATH(type, n)   ((type)|((n)<<8))
#define TYPE(path)              ((int)(path) & 0xFF)
#define NUM(path)               ((uint)(path)>>8)

Channel *creq;
Channel *creqwait;
Channel *cclunk;
Channel *cclunkwait;

typedef struct Tab Tab;
struct Tab
{
        char *name;
        ulong mode;
        int offset;
};

Tab tab[] =
{
        "/",                    DMDIR|0555,             0,
        "ctl",                  0666,                   0,
        "clone",                0666,                   0,
        "cookies",              0666,                   0,
        "XXX",          DMDIR|0555,             0,
        "ctl",                  0666,                   0,
        "body",         0444,                   0,
        "XXX",          0444,                   0,
        "contenttype",  0444,                   0,
        "postbody",     0666,                   0,
        "parsed",               DMDIR|0555,             0,
        "url",                  0444,                   offsetof(Url, url),
        "scheme",               0444,                   offsetof(Url, scheme),
        "schemedata",   0444,                   offsetof(Url, schemedata),
        "user",         0444,                   offsetof(Url, user),
        "passwd",               0444,                   offsetof(Url, passwd),
        "host",         0444,                   offsetof(Url, host),
        "port",         0444,                   offsetof(Url, port),
        "path",         0444,                   offsetof(Url, path),
        "query",                0444,                   offsetof(Url, query),
        "fragment",     0444,                   offsetof(Url, fragment),
        "ftptype",              0444,                   offsetof(Url, ftp.type),
};

ulong time0;

static void
fillstat(Dir *d, uvlong path, ulong length, char *ext)
{
        Tab *t;
        int type;
        char buf[32];

        memset(d, 0, sizeof(*d));
        d->uid = estrdup("web");
        d->gid = estrdup("web");
        d->qid.path = path;
        d->atime = d->mtime = time0;
        d->length = length;
        type = TYPE(path);
        t = &tab[type];
        if(type == Qbodyext) {
                snprint(buf, sizeof buf, "body.%s", ext == nil ? "xxx" : ext);
                d->name = estrdup(buf);
        }
        else if(t->name)
                d->name = estrdup(t->name);
        else{   /* client directory */
                snprint(buf, sizeof buf, "%ud", NUM(path));
                d->name = estrdup(buf);
        }
        d->qid.type = t->mode>>24;
        d->mode = t->mode;
}

static void
fsstat(Req *r)
{
        fillstat(&r->d, r->fid->qid.path, 0, nil);
        respond(r, nil);
}

static int
rootgen(int i, Dir *d, void*)
{
        char buf[32];

        i += Qroot+1;
        if(i < Qclient){
                fillstat(d, i, 0, nil);
                return 0;
        }
        i -= Qclient;
        if(i < nclient){
                fillstat(d, PATH(Qclient, i), 0, nil);
                snprint(buf, sizeof buf, "%d", i);
                free(d->name);
                d->name = estrdup(buf);
                return 0;
        }
        return -1;
}

static int
clientgen(int i, Dir *d, void *aux)
{
        Client *c;

        c = aux;
        i += Qclient+1;
        if(i <= Qparsed){
                fillstat(d, PATH(i, c->num), 0, c->ext);
                return 0;
        }
        return -1;
}

static int
parsedgen(int i, Dir *d, void *aux)
{
        Client *c;

        c = aux;
        i += Qparsed+1;
        if(i < Qend){
                fillstat(d, PATH(i, c->num), 0, nil);
                return 0;
        }
        return -1;
}

static void
fsread(Req *r)
{
        char *s;
        char e[ERRMAX];
        Client *c;
        ulong path;

        path = r->fid->qid.path;
        switch(TYPE(path)){
        default:
                snprint(e, sizeof e, "bug in webfs path=%lux\n", path);
                respond(r, e);
                break;

        case Qroot:
                dirread9p(r, rootgen, nil);
                respond(r, nil);
                break;

        case Qrootctl:
                globalctlread(r);
                break;

        case Qcookies:
                cookieread(r);
                break;

        case Qclient:
                dirread9p(r, clientgen, client[NUM(path)]);
                respond(r, nil);
                break;

        case Qctl:
                ctlread(r, client[NUM(path)]);
                break;

        case Qcontenttype:
                c = client[NUM(path)];
                if(c->contenttype == nil)
                        r->ofcall.count = 0;
                else
                        readstr(r, c->contenttype);
                respond(r, nil);
                break;

        case Qpostbody:
                c = client[NUM(path)];
                readbuf(r, c->postbody, c->npostbody);
                respond(r, nil);
                break;
                
        case Qbody:
        case Qbodyext:
                c = client[NUM(path)];
                if(c->iobusy){
                        respond(r, "already have i/o pending");
                        break;
                }
                c->iobusy = 1;
                sendp(c->creq, r);
                break;

        case Qparsed:
                dirread9p(r, parsedgen, client[NUM(path)]);
                respond(r, nil);
                break;

        case Qurl:
        case Qscheme:
        case Qschemedata:
        case Quser:
        case Qpasswd:
        case Qhost:
        case Qport:
        case Qpath:
        case Qquery:
        case Qfragment:
        case Qftptype:
                c = client[NUM(path)];
                r->ofcall.count = 0;
                if(c->url != nil
                && (s = *(char**)((uintptr)c->url+tab[TYPE(path)].offset)) != nil)
                        readstr(r, s);
                respond(r, nil);
                break;
        }
}

static void
fswrite(Req *r)
{
        int m;
        ulong path;
        char e[ERRMAX], *buf, *cmd, *arg;
        Client *c;

        path = r->fid->qid.path;
        switch(TYPE(path)){
        default:
                snprint(e, sizeof e, "bug in webfs path=%lux\n", path);
                respond(r, e);
                break;

        case Qcookies:
                cookiewrite(r);
                break;

        case Qrootctl:
        case Qctl:
                if(r->ifcall.count >= 1024){
                        respond(r, "ctl message too long");
                        return;
                }
                buf = estredup(r->ifcall.data, (char*)r->ifcall.data+r->ifcall.count);
                cmd = buf;
                arg = strpbrk(cmd, "\t ");
                if(arg){
                        *arg++ = '\0';
                        arg += strspn(arg, "\t ");
                }else
                        arg = "";
                r->ofcall.count = r->ifcall.count;
                if(TYPE(path)==Qrootctl){
                        if(!ctlwrite(r, &globalctl, cmd, arg)
                        && !globalctlwrite(r, cmd, arg))
                                respond(r, "unknown control command");
                }else{
                        c = client[NUM(path)];
                        if(!ctlwrite(r, &c->ctl, cmd, arg)
                        && !clientctlwrite(r, c, cmd, arg))
                                respond(r, "unknown control command");
                }
                free(buf);
                break;

        case Qpostbody:
                c = client[NUM(path)];
                if(c->bodyopened){
                        respond(r, "cannot write postbody after opening body");
                        break;
                }
                if(r->ifcall.offset >= 128*1024*1024){  /* >128MB is probably a mistake */
                        respond(r, "offset too large");
                        break;
                }
                m = r->ifcall.offset + r->ifcall.count;
                if(c->npostbody < m){
                        c->postbody = erealloc(c->postbody, m);
                        memset(c->postbody+c->npostbody, 0, m-c->npostbody);
                        c->npostbody = m;
                }
                memmove(c->postbody+r->ifcall.offset, r->ifcall.data, r->ifcall.count);
                r->ofcall.count = r->ifcall.count;
                respond(r, nil);
                break;
        }
}

static void
fsopen(Req *r)
{
        static int need[4] = { 4, 2, 6, 1 };
        ulong path;
        int n;
        Client *c;
        Tab *t;

        /*
         * lib9p already handles the blatantly obvious.
         * we just have to enforce the permissions we have set.
         */
        path = r->fid->qid.path;
        t = &tab[TYPE(path)];
        n = need[r->ifcall.mode&3];
        if((n&t->mode) != n){
                respond(r, "permission denied");
                return;
        }

        switch(TYPE(path)){
        case Qcookies:
                cookieopen(r);
                break;

        case Qpostbody:
                c = client[NUM(path)];
                c->havepostbody++;
                c->ref++;
                respond(r, nil);
                break;

        case Qbody:
        case Qbodyext:
                c = client[NUM(path)];
                if(c->url == nil){
                        respond(r, "url is not yet set");
                        break;
                }
                c->bodyopened = 1;
                c->ref++;
                sendp(c->creq, r);
                break;

        case Qclone:
                n = newclient(0);
                path = PATH(Qctl, n);
                r->fid->qid.path = path;
                r->ofcall.qid.path = path;
                if(fsdebug)
                        fprint(2, "open clone => path=%lux\n", path);
                t = &tab[Qctl];
                /* fall through */
        default:
                if(t-tab >= Qclient)
                        client[NUM(path)]->ref++;
                respond(r, nil);
                break;
        }
}

static void
fsdestroyfid(Fid *fid)
{
        sendp(cclunk, fid);
        recvp(cclunkwait);
}

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 char*
fswalk1(Fid *fid, char *name, Qid *qid)
{
        int i, n;
        ulong path;
        char buf[32], *ext;

        path = fid->qid.path;
        if(!(fid->qid.type&QTDIR))
                return "walk in non-directory";

        if(strcmp(name, "..") == 0){
                switch(TYPE(path)){
                case Qparsed:
                        qid->path = PATH(Qclient, NUM(path));
                        qid->type = tab[Qclient].mode>>24;
                        return nil;
                case Qclient:
                case Qroot:
                        qid->path = PATH(Qroot, 0);
                        qid->type = tab[Qroot].mode>>24;
                        return nil;
                default:
                        return "bug in fswalk1";
                }
        }

        i = TYPE(path)+1;
        for(; i<nelem(tab); i++){
                if(i==Qclient){
                        n = atoi(name);
                        snprint(buf, sizeof buf, "%d", n);
                        if(n < nclient && strcmp(buf, name) == 0){
                                qid->path = PATH(i, n);
                                qid->type = tab[i].mode>>24;
                                return nil;
                        }
                        break;
                }
                if(i==Qbodyext){
                        ext = client[NUM(path)]->ext;
                        snprint(buf, sizeof buf, "body.%s", ext == nil ? "xxx" : ext);
                        if(strcmp(buf, name) == 0){
                                qid->path = PATH(i, NUM(path));
                                qid->type = tab[i].mode>>24;
                                return nil;
                        }
                }
                else if(strcmp(name, tab[i].name) == 0){
                        qid->path = PATH(i, NUM(path));
                        qid->type = tab[i].mode>>24;
                        return nil;
                }
                if(tab[i].mode&DMDIR)
                        break;
        }
        return "directory entry not found";
}

static void
fsflush(Req *r)
{
        Req *or;
        int t;
        Client *c;
        ulong path;

        or=r;
        while(or->ifcall.type==Tflush)
                or = or->oldreq;

        if(or->ifcall.type != Tread && or->ifcall.type != Topen)
                abort();

        path = or->fid->qid.path;
        t = TYPE(path);
        if(t != Qbody && t != Qbodyext)
                abort();

        c = client[NUM(path)];
        sendp(c->creq, r);
        iointerrupt(c->io);
}

static void
fsthread(void*)
{
        ulong path;
        Alt a[3];
        Fid *fid;
        Req *r;

        threadsetname("fsthread");
        plumbstart();

        a[0].op = CHANRCV;
        a[0].c = cclunk;
        a[0].v = &fid;
        a[1].op = CHANRCV;
        a[1].c = creq;
        a[1].v = &r;
        a[2].op = CHANEND;

        for(;;){
                switch(alt(a)){
                case 0:
                        path = fid->qid.path;
                        if(TYPE(path)==Qcookies)
                                cookieclunk(fid);
                        if(fid->omode != -1 && TYPE(path) >= Qclient)
                                closeclient(client[NUM(path)]);
                        sendp(cclunkwait, nil);
                        break;
                case 1:
                        switch(r->ifcall.type){
                        case Tattach:
                                fsattach(r);
                                break;
                        case Topen:
                                fsopen(r);
                                break;
                        case Tread:
                                fsread(r);
                                break;
                        case Twrite:
                                fswrite(r);
                                break;
                        case Tstat:
                                fsstat(r);
                                break;
                        case Tflush:
                                fsflush(r);
                                break;
                        default:
                                respond(r, "bug in fsthread");
                                break;
                        }
                        sendp(creqwait, 0);
                        break;
                }
        }
}

static void
fssend(Req *r)
{
        sendp(creq, r);
        recvp(creqwait);        /* avoids need to deal with spurious flushes */
}

void
initfs(void)
{
        time0 = time(0);
        creq = chancreate(sizeof(void*), 0);
        creqwait = chancreate(sizeof(void*), 0);
        cclunk = chancreate(sizeof(void*), 0);
        cclunkwait = chancreate(sizeof(void*), 0);
        procrfork(fsthread, nil, STACK, RFNAMEG);
}

void
takedown(Srv*)
{
        closecookies();
        threadexitsall("done");
}

Srv fs = 
{
.attach=                fssend,
.destroyfid=    fsdestroyfid,
.walk1=         fswalk1,
.open=          fssend,
.read=          fssend,
.write=         fssend,
.stat=          fssend,
.flush=         fssend,
.end=           takedown,
};