/* * 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 #include #include #include #include #include #include #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(; ipath = 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, };