plan9front/sys/src/cmd/ip/httpfile.c

601 lines
9.4 KiB
C

/* contributed by 20h@r-36.net, September 2005 */
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
enum
{
Blocksize = 64*1024,
Stacksize = 8192,
};
char *user, *url, *file;
char webconn[64];
int webctlfd = -1;
vlong size;
int usetls;
int debug;
int ncache;
int mcache;
void
usage(void)
{
fprint(2, "usage: httpfile [-Dd] [-c count] [-f file] [-m mtpt] [-s srvname] 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 *reqchan;
Channel *httpchan;
Channel *finishchan;
ulong time0;
typedef struct Block Block;
struct Block
{
uchar *p;
vlong off;
vlong len;
Block *link;
long lastuse;
Req *rq;
Req **erq;
};
typedef struct Blocklist Blocklist;
struct Blocklist
{
Block *first;
Block **end;
};
Blocklist cache;
Blocklist inprogress;
void
queuereq(Block *b, Req *r)
{
if(b->rq==nil)
b->erq = &b->rq;
*b->erq = r;
r->aux = nil;
b->erq = (Req**)&r->aux;
}
void
addblock(Blocklist *l, Block *b)
{
if(debug)
print("adding: %p %lld\n", b, b->off);
if(l->first == nil)
l->end = &l->first;
b->lastuse = time(0);
b->link = nil;
*l->end = b;
l->end = &b->link;
}
void
delreq(Block *b, Req *r)
{
Req **l;
for(l = &b->rq; *l; l = (Req**)&(*l)->aux){
if(*l == r){
*l = r->aux;
if(*l == nil)
b->erq = l;
r->aux = nil;
respond(r, "interrupted");
return;
}
}
}
void
evictblock(Blocklist *cache)
{
Block **l, **oldest, *b;
oldest = nil;
for(l=&cache->first; (b=*l) != nil; l=&b->link){
if(b->rq != nil) /* dont touch block when still requests queued */
continue;
if(oldest == nil || (*oldest)->lastuse > b->lastuse)
oldest = l;
}
if(oldest == nil || *oldest == nil || (*oldest)->rq != nil)
return;
b = *oldest;
if((*oldest = b->link) == nil)
cache->end = oldest;
free(b->p);
free(b);
ncache--;
}
Block *
findblock(Blocklist *s, vlong off)
{
Block *b;
for(b = s->first; b != nil; b = b->link){
if(off >= b->off && off < b->off + b->len){
if(debug)
print("found: %lld -> %lld\n", off, b->off);
b->lastuse = time(0);
return b;
}
}
return nil;
}
void
readfrom(Req *r, Block *b)
{
int d, n;
n = r->ifcall.count;
d = r->ifcall.offset - b->off;
if(d + n > b->len)
n = b->len - d;
if(debug)
print("Reading from: %p %d %d\n", b->p, d, n);
memmove(r->ofcall.data, b->p + d, n);
r->ofcall.count = n;
respond(r, nil);
}
void
hangupclient(Srv*)
{
if(debug)
print("Hangup.\n");
threadexitsall("done");
}
static int
readfile(int fd, char *buf, int nbuf)
{
int r, n;
for(n = 0; n < nbuf; n += r)
if((r = read(fd, buf + n, nbuf - n)) <= 0)
break;
return n;
}
static int
readstring(int fd, char *buf, int nbuf)
{
int n;
if((n = readfile(fd, buf, nbuf-1)) < 0){
buf[0] = '\0';
return -1;
}
if(n > 0 && buf[n-1] == '\n')
n--;
buf[n] = '\0';
return n;
}
uchar*
getrange(Block *b)
{
char buf[128];
int fd, cfd;
uchar *data;
if(debug)
print("getrange: %lld %lld\n", b->off, b->len);
if(fprint(webctlfd, "url %s\n", url) < 0)
return nil;
if(fprint(webctlfd, "request GET\n") < 0)
return nil;
if(fprint(webctlfd, "headers Range: bytes=%lld-%lld\n", b->off, b->off+b->len-1) < 0)
return nil;
/* start the request */
snprint(buf, sizeof(buf), "%s/body", webconn);
if((fd = open(buf, OREAD)) < 0)
return nil;
/* verify content-range response header */
snprint(buf, sizeof(buf), "%s/contentrange", webconn);
if((cfd = open(buf, OREAD)) < 0){
close(fd);
return nil;
}
werrstr("bad contentrange header");
if(readstring(cfd, buf, sizeof(buf)) <= 0){
Badrange:
close(cfd);
close(fd);
return nil;
}
if(cistrncmp(buf, "bytes ", 6) != 0)
goto Badrange;
if(strtoll(buf + 6, nil, 10) != b->off)
goto Badrange;
close(cfd);
/* read body data */
data = emalloc9p(b->len);
werrstr("body data truncated");
if(readfile(fd, (char*)data, b->len) != b->len){
close(fd);
free(data);
return nil;
}
close(fd);
b->p = data;
return data;
}
void
httpfilereadproc(void*)
{
Block *b;
threadsetname("httpfilereadproc %s", url);
for(;;){
b = recvp(httpchan);
if(b == nil)
continue;
if(getrange(b) == nil)
sysfatal("getrange: %r");
sendp(finishchan, b);
}
}
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
getfilesize(void)
{
char buf[128];
int fd, cfd;
if(fprint(webctlfd, "url %s\n", url) < 0)
return -1;
if(fprint(webctlfd, "request HEAD\n") < 0)
return -1;
snprint(buf, sizeof(buf), "%s/body", webconn);
if((fd = open(buf, OREAD)) < 0)
return -1;
snprint(buf, sizeof(buf), "%s/contentlength", webconn);
cfd = open(buf, OREAD);
close(fd);
if(cfd < 0)
return -1;
if(readstring(cfd, buf, sizeof(buf)) <= 0){
close(cfd);
return -1;
}
close(cfd);
return strtoll(buf, nil, 10);
}
void
fileread(Req *r)
{
Block *b;
if(r->ifcall.offset >= size){
r->ofcall.count = 0;
respond(r, nil);
return;
}
if((b = findblock(&cache, r->ifcall.offset)) != nil){
readfrom(r, b);
return;
}
if((b = findblock(&inprogress, r->ifcall.offset)) == nil){
b = emalloc9p(sizeof(Block));
b->off = r->ifcall.offset - (r->ifcall.offset % Blocksize);
b->len = Blocksize;
if(b->off + b->len > size)
b->len = size - b->off;
addblock(&inprogress, b);
if(inprogress.first == b)
sendp(httpchan, b);
}
queuereq(b, r);
}
static void
fsopen(Req *r)
{
if(r->ifcall.mode != OREAD){
respond(r, "permission denied");
return;
}
respond(r, nil);
}
void
finishthread(void*)
{
Block *b;
Req *r;
threadsetname("finishthread");
for(;;){
b = recvp(finishchan);
assert(b == inprogress.first);
inprogress.first = b->link;
if(++ncache >= mcache)
evictblock(&cache);
addblock(&cache, b);
while((r = b->rq) != nil){
b->rq = r->aux;
r->aux = nil;
readfrom(r, b);
}
if(inprogress.first != nil)
sendp(httpchan, inprogress.first);
}
}
void
fsnetproc(void*)
{
Req *r, *o;
Block *b;
threadcreate(finishthread, nil, 8192);
threadsetname("fsnetproc");
for(;;){
r = recvp(reqchan);
switch(r->ifcall.type){
case Tflush:
o = r->oldreq;
if(o->ifcall.type == Tread){
b = findblock(&inprogress, o->ifcall.offset);
if(b != nil)
delreq(b, o);
}
respond(r, nil);
break;
case Tread:
fileread(r);
break;
default:
respond(r, "bug in fsthread");
break;
}
}
}
static void
fsflush(Req *r)
{
sendp(reqchan, r);
}
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:
sendp(reqchan, r);
break;
default:
snprint(e, sizeof(e), "bug in fsread path=%lux", path);
respond(r, e);
break;
}
}
Srv fs =
{
.attach= fsattach,
.walk1= fswalk1,
.open= fsopen,
.read= fsread,
.stat= fsstat,
.flush= fsflush,
.end= hangupclient,
};
void
threadmain(int argc, char **argv)
{
char *mtpt, *srvname, *p;
mtpt = nil;
srvname = nil;
ARGBEGIN{
case 'D':
chatty9p++;
break;
case 'd':
debug++;
break;
case 's':
srvname = EARGF(usage());
break;
case 'm':
mtpt = EARGF(usage());
break;
case 'c':
mcache = atoi(EARGF(usage()));
break;
case 'f':
file = EARGF(usage());
break;
default:
usage();
}ARGEND;
if(srvname == nil && mtpt == nil)
mtpt = ".";
if(argc < 1)
usage();
if(mcache <= 0)
mcache = 32;
time0 = time(0);
url = estrdup9p(argv[0]);
if(file == nil){
file = strrchr(url, '/');
if(file == nil || file[1] == '\0')
file = "index";
else
file++;
}
snprint(webconn, sizeof(webconn), "/mnt/web/clone");
if((webctlfd = open(webconn, ORDWR)) < 0)
sysfatal("open: %r");
p = strrchr(webconn, '/')+1;
if(readstring(webctlfd, p, webconn+sizeof(webconn)-p) <= 0)
sysfatal("read: %r");
tab[Qfile].name = file;
user = getuser();
size = getfilesize();
if(size < 0)
sysfatal("getfilesize: %r");
reqchan = chancreate(sizeof(Req*), 0);
httpchan = chancreate(sizeof(Block*), 0);
finishchan = chancreate(sizeof(Block*), 0);
procrfork(fsnetproc, nil, Stacksize, RFNAMEG|RFNOTEG);
procrfork(httpfilereadproc, nil, Stacksize, RFNAMEG|RFNOTEG);
threadpostmountsrv(&fs, srvname, mtpt, MBEFORE);
threadexits(0);
}