#include #include #include #include #include #include #include <9p.h> #include #include char *mtpt; char *host; char *file; char *port; char *url; char *get; char *user; char *netstk; vlong size; int usetls; int blocks = 32; int debug; void usage(void) { fprint(2, "usage: htfilefs [-9d] [-b count] [-x netstk] [-m mntpt] [-s serv] URL\n"); exits("usage"); } enum { Qroot, Qfile, }; #define PATH(type, n) ((type)|((n)<<8)) #define TYPE(path) ((int)(path) & 0xFF) #define NUM(path) ((uint)(path)>>8) Channel *fsreqchan; Channel *fsreqwaitchan; Channel *fsblockchan; Channel *fsblockwaitchan; ulong time0; typedef struct Block Block; struct Block { char *d; vlong off; vlong len; Block *link; }; typedef struct Client Client; struct Client { int ref; int nb; Block *brq; Block **ebrq; Block *bq; Block **ebq; Req *rq; Req **erq; }; Client client; void queuereq(Client *c, Req *r) { if(c->rq==nil) c->erq = &c->rq; *c->erq = r; r->aux = nil; c->erq = (Req**)&r->aux; } void queueblock(Client *c, Block *b) { if(c->brq == nil) c->ebrq = &c->brq; *c->ebrq = b; b->link = nil; c->ebrq = (Block**)&b->link; } void demiseblock(Client *c) { Block *b; if(debug) print("Demising block.\n"); b = c->bq->link; free(c->bq); c->bq = b; c->nb--; return; } void addblock(Client *c, Block *b) { if(debug) print("adding: %d %d %lld %lld\n", c->nb, (int)b, b->len, b->off); if(c->nb == blocks) demiseblock(c); if(c->bq == nil) c->ebq = &c->bq; *c->ebq = b; b->link = nil; c->ebq = (Block**)&b->link; c->nb++; } Block* findblock(Client *c, vlong off) { Block *l; l = c->bq; while(l) { if(off >= l->off && off < l->off + l->len) { if(debug) print("found: %lld -> %d %lld %lld\n", off, (int)l, l->off, l->len); return l; } l = l->link; } return nil; } Req* findreq(Client *c, Req *r) { Req **l; l = &c->rq; while(*l) { if(*l == r) { *l = r->aux; if(*l == nil) c->erq = l; return r; } l = (Req **)&(*l)->aux; } return nil; } void matchblocks(Client *c) { Req *r; Block *b, *br; vlong n, m; if(c->rq == nil) return; br = nil; r = c->rq; b = findblock(c, r->ifcall.offset); if(b != nil) { c->rq = r->aux; m = r->ifcall.offset - b->off; n = r->ifcall.count; if(n >= (b->len - m)) { n = b->len - m; if(b->off + b->len < size) { br = emalloc9p(sizeof(Block)); br->d = nil; br->off = b->off + b->len; if(br->off + b->len < size) { br->len = b->len; } else { br->len = size - br->off; } } } if(debug) print("Giving back: %lld %lld -> %lld %lld\n", b->off, b->len, b->off + m, n); memmove(r->ofcall.data, b->d + m, n); r->ofcall.count = n; respond(r, nil); } else { if(r->ifcall.offset >= size) { c->rq = r->aux; respond(r, nil); } br = emalloc9p(sizeof(Block)); br->d = nil; br->off = r->ifcall.offset - (r->ifcall.offset % 65536); if(br->off < size - 65536) { br->len = 65536; } else { br->len = size - br->off; } } if(br != nil) { sendp(fsblockchan, br); recvp(fsblockwaitchan); } } void hangupclient(Client *c) { Req *r, *next; Block *b, *bnext; if(--c->ref) return; b = c->brq; while(b) { bnext = b->link; if(b->d != nil) free(b->d); free(b); b = bnext; } c->brq = nil; b = c->bq; while(b) { bnext = b->link; if(b->d != nil) free(b->d); free(b); b = bnext; } c->bq = nil; r = c->rq; while(r) { next = r->aux; respond(r, "We are out of here."); r = next; } c->rq = nil; return; } char * read_line(int s) { char *ret; int l; ret = nil; l = 0; while((ret = realloc(ret, ++l)) != nil && read(s, &ret[l - 1], 1) > 0 && l < 1024) { if(l > 1) { if(ret[l - 1] == '\n') { ret[l - 1] = '\0'; if(ret[l - 2] == '\r') ret[l - 2] = '\0'; return ret; } } } if(ret != nil) free(ret); return nil; } int do_tls(int fd) { TLSconn conn; int ret; ret = tlsClient(fd, &conn); if(ret < 0) sysfatal("Unable to do TLS"); if(conn.cert != nil) free(conn.cert); return ret; } char * read_httphdr(int s, vlong *size) { char *ret, *stat; stat = nil; while((ret = read_line(s)) != nil && ret[0] != '\0') { if(stat == nil) stat = estrdup9p(ret); if(!strncmp(ret, "Content-Length: ", 16) && size != nil) *size = atoll(ret + 16); free(ret); } return stat; } char * get_range(vlong min, vlong len) { char *ret, *b; int fd, userange; userange = 1; if(len < 65536 && min == 0) { userange = 0; } else { min -= 65536 - len; } fd = dial(netmkaddr(host, netstk, port), 0, 0, 0); if(fd < 0) return nil; if(usetls) fd = do_tls(fd); if(userange) { ret = smprint("Range: bytes=%lld-%lld\r\n", min, min + 65536); } else { ret = estrdup9p(""); } fprint(fd, "GET /%s HTTP/1.1\r\nHost: %s\r\nAccept-Encoding:\r\n%s\r\n", get, host, ret); free(ret); ret = read_httphdr(fd, nil); if(!strstr(ret, "206 Partial Content") && userange) { free(ret); close(fd); return nil; } if(!strstr(ret, "200 OK") && !userange) { free(ret); close(fd); return nil; } free(ret); ret = emalloc9p(len); if(readn(fd, ret, len) != len) { free(ret); close(fd); return nil; } close(fd); if(len < 65536 && userange) { b = emalloc9p(len); memmove(b, ret + (65536 - len), len); free(ret); ret = b; } return ret; } void htfilereadproc(void *a) { Block *p; Client *c; p = nil; c = a; while(1) { nothingtodo: matchblocks(c); sendp(fsblockchan, p); p = recvp(fsblockwaitchan); if(p == nil) { sleep(1000); goto nothingtodo; } p->d = get_range(p->off, p->len); if(p->d == nil) sysfatal("We got a nil pointer from get_range."); } } typedef struct Tab Tab; struct Tab { char *name; ulong mode; }; Tab tab[] = { "/", DMDIR|0555, nil, 0444, }; static void fillstat(Dir *d, uvlong path) { Tab *t; memset(d, 0, sizeof(*d)); d->uid = estrdup9p(user); d->gid = estrdup9p(user); d->qid.path = path; d->atime = d->mtime = time0; t = &tab[TYPE(path)]; d->name = estrdup9p(t->name); d->length = size; d->qid.type = t->mode>>24; d->mode = t->mode; } 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) { fillstat(&r->d, r->fid->qid.path); respond(r, nil); } static int rootgen(int i, Dir *d, void*) { i += Qroot + 1; if(i <= Qfile) { fillstat(d, i); return 0; } return -1; } static char* fswalk1(Fid *fid, char *name, Qid *qid) { int i; ulong path; path = fid->qid.path; if(!(fid->qid.type & QTDIR)) return "walk in non-directory"; if(strcmp(name, "..") == 0) { switch(TYPE(path)) { case Qroot: return nil; default: return "bug in fswalk1"; } } i = TYPE(path) + 1; while(i < nelem(tab)) { 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; i++; } return "directory entry not found"; } vlong get_filesize(void) { vlong ret; int fd; char *b; fd = dial(netmkaddr(host, netstk, port), 0, 0, 0); if(fd < 0) return -1; if(usetls) fd = do_tls(fd); fprint(fd, "HEAD /%s HTTP/1.1\r\nHost: %s\r\nAccept-Encoding:\r\n\r\n", get, host); b = read_httphdr(fd, &ret); if(!strstr(b, "200 OK")) ret = -1; free(b); close(fd); return ret; } static void fileread(Req *r, Client *c) { queuereq(c, r); matchblocks(c); } static void fsread(Req *r) { char e[ERRMAX]; ulong path; path = r->fid->qid.path; switch(TYPE(path)) { case Qroot: dirread9p(r, rootgen, nil); respond(r, nil); break; case Qfile: fileread(r, &client); break; default: snprint(e, sizeof e, "bug in fsread path=%lux", path); respond(r, e); break; } } static void fsopen(Req *r) { static int need[4] = { 4, 2, 6, 1 }; ulong path; int n; Tab *t; 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; } respond(r, nil); } static void fsflush(Req *r) { if(findreq(&client, r->oldreq)) respond(r->oldreq, "interrupted"); respond(r, nil); } void fsnetproc(void *) { Alt a[3]; Req *r; Block *b; threadsetname("fsthread"); a[0].op = CHANRCV; a[0].c = fsblockchan; a[0].v = &b; a[1].op = CHANRCV; a[1].c = fsreqchan; a[1].v = &r; a[2].op = CHANEND; while(1) { switch(alt(a)) { case 0: if(b != nil && b->d == nil) { queueblock(&client, b); sendp(fsblockwaitchan, 0); } else { if(b != nil) { if(b != client.brq) sysfatal("Did not ask the first queue entry?"); addblock(&client, b); client.brq = client.brq->link; } sendp(fsblockwaitchan, client.brq); } break; case 1: switch(r->ifcall.type) { case Tattach: fsattach(r); break; case Topen: fsopen(r); break; case Tstat: fsstat(r); break; case Tflush: fsflush(r); break; default: respond(r, "bug in fsthread"); break; } sendp(fsreqwaitchan, 0); break; } } } static void fssend(Req *r) { sendp(fsreqchan, r); recvp(fsreqwaitchan); } static void fsdestroyfid(Fid *) { return; } void takedown(Srv *) { hangupclient(&client); threadexitsall("done"); } Srv fs = { .attach= fssend, .destroyfid= fsdestroyfid, .walk1= fswalk1, .open= fssend, .read= fsread, .stat= fssend, .flush= fssend, .end= takedown, }; void threadmain(int argc, char **argv) { char *srvname, *mntpt; srvname = "htfile"; mntpt = nil; ARGBEGIN { case 'D': chatty9p++; break; case 'd': debug++; break; case 's': srvname = EARGF(usage()); break; case 'm': mntpt = EARGF(usage()); break; case 'b': blocks = atoi(EARGF(usage())); break; case 'x': netstk = EARGF(usage()); break; default: usage(); } ARGEND; if(argc < 1) usage(); if(blocks <= 0) blocks = 32; time0 = time(0); host = url = estrdup9p(argv[0]); if(!strncmp(url, "https://", 8)) { host += 8; usetls = 1; } else if(!strncmp(url, "http://", 7)) { host += 7; } else { free(url); sysfatal("Only http and https are supported or bad syntax."); } port = strchr(host, ':'); get = strchr(host, '/'); if(get != nil) *get++ = '\0'; if(port != nil && get != nil) { if(port < get) { *port++ = '\0'; port = estrdup9p(port); } else { port = estrdup9p(usetls ? "443" : "80"); } } if(port == nil && get != nil) port = estrdup9p(usetls ? "443" : "80"); if(port != nil && get == nil) { *port++ = '\0'; port = estrdup9p(port); get = estrdup9p("/"); } if(port == nil && get == nil) { port = estrdup9p(usetls ? "443" : "80"); get = estrdup9p("/"); } file = strrchr(get, '/'); if(file != nil) { file++; } else { if(strcmp(get, "/")) { file = get; } else { file = "index"; } } tab[Qfile].name = file; user = getuser(); size = get_filesize(); if(size < 0) sysfatal("Could not get the filesize: %r"); fsreqchan = chancreate(sizeof(Req *), 0); fsreqwaitchan = chancreate(sizeof(void *), 0); fsblockchan = chancreate(sizeof(Block *), 0); fsblockwaitchan = chancreate(sizeof(Block *), 0); procrfork(fsnetproc, nil, 8192, RFNAMEG | RFNOTEG); procrfork(htfilereadproc, &client, 8192, RFNAMEG | RFNOTEG); threadpostmountsrv(&fs, srvname, mntpt, MREPL); exits(0); }