]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/httpfile.c
httpfile: use webfs, fix 9p flushes
[plan9front.git] / sys / src / cmd / ip / httpfile.c
1 /* contributed by 20h@r-36.net, September 2005 */
2
3 #include <u.h>
4 #include <libc.h>
5 #include <thread.h>
6 #include <fcall.h>
7 #include <9p.h>
8
9 enum
10 {
11         Blocksize = 64*1024,
12         Stacksize = 8192,
13 };
14
15 char *user, *url, *file;
16 char webconn[64];
17 int webctlfd = -1;
18
19 vlong size;
20 int usetls;
21 int debug;
22 int ncache;
23 int mcache;
24
25 void
26 usage(void)
27 {
28         fprint(2, "usage: httpfile [-Dd] [-c count] [-f file] [-m mtpt] [-s srvname] url\n");
29         exits("usage");
30 }
31
32 enum
33 {
34         Qroot,
35         Qfile,
36 };
37
38 #define PATH(type, n)           ((type)|((n)<<8))
39 #define TYPE(path)                      ((int)(path) & 0xFF)
40 #define NUM(path)                       ((uint)(path)>>8)
41
42 Channel *reqchan;
43 Channel *httpchan;
44 Channel *finishchan;
45 ulong time0;
46
47 typedef struct Block Block;
48 struct Block
49 {
50         uchar *p;
51         vlong off;
52         vlong len;
53         Block *link;
54         long lastuse;
55         Req *rq;
56         Req **erq;
57 };
58
59 typedef struct Blocklist Blocklist;
60 struct Blocklist
61 {
62         Block *first;
63         Block **end;
64 };
65
66 Blocklist cache;
67 Blocklist inprogress;
68
69 void
70 queuereq(Block *b, Req *r)
71 {
72         if(b->rq==nil)
73                 b->erq = &b->rq;
74         *b->erq = r;
75         r->aux = nil;
76         b->erq = (Req**)&r->aux;
77 }
78
79 void
80 addblock(Blocklist *l, Block *b)
81 {
82         if(debug)
83                 print("adding: %p %lld\n", b, b->off);
84
85         if(l->first == nil)
86                 l->end = &l->first;
87         b->lastuse = time(0);
88         b->link = nil;
89         *l->end = b;
90         l->end = &b->link;
91 }
92
93 void
94 delreq(Block *b, Req *r)
95 {
96         Req **l;
97
98         for(l = &b->rq; *l; l = (Req**)&(*l)->aux){
99                 if(*l == r){
100                         *l = r->aux;
101                         if(*l == nil)
102                                 b->erq = l;
103                         return;
104                 }
105         }
106 }
107
108 void
109 evictblock(Blocklist *cache)
110 {
111         Block **l, **oldest, *b;
112
113         oldest = nil;
114         for(l=&cache->first; (b=*l) != nil; l=&b->link){
115                 if(b->rq != nil)        /* dont touch block when still requests queued */
116                         continue;
117                 if(b->rq != nil && (oldest == nil || (*oldest)->lastuse > b->lastuse))
118                         oldest = l;
119         }
120
121         if(oldest == nil)
122                 return;
123
124         b = *oldest;
125         if((*oldest = b->link) == nil)
126                 cache->end = oldest;
127
128         free(b->p);
129         free(b);
130         ncache--;
131 }
132
133 Block *
134 findblock(Blocklist *s, vlong off)
135 {
136         Block *b;
137
138         for(b = s->first; b != nil; b = b->link){
139                 if(off >= b->off && off < b->off + b->len){
140                         if(debug)
141                                 print("found: %lld -> %lld\n", off, b->off);
142                         b->lastuse = time(0);
143                         return b;
144                 }
145         }
146         return nil;
147 }
148
149 void
150 readfrom(Req *r, Block *b)
151 {
152         int d, n;
153
154         n = r->ifcall.count;
155         d = r->ifcall.offset - b->off;
156         if(d + n > b->len)
157                 n = b->len - d;
158         if(debug)
159                 print("Reading from: %p %d %d\n", b->p, d, n);
160         memmove(r->ofcall.data, b->p + d, n);
161         r->ofcall.count = n;
162
163         respond(r, nil);
164 }
165
166 void
167 hangupclient(Srv*)
168 {
169         if(debug)
170                 print("Hangup.\n");
171
172         threadexitsall("done");
173 }
174
175 static int
176 readfile(int fd, char *buf, int nbuf)
177 {
178         int r, n;
179
180         for(n = 0; n < nbuf; n += r)
181                 if((r = read(fd, buf + n, nbuf - n)) <= 0)
182                         break;
183         return n;
184 }
185
186 static int
187 readstring(int fd, char *buf, int nbuf)
188 {
189         int n;
190
191         if((n = readfile(fd, buf, nbuf-1)) < 0){
192                 buf[0] = '\0';
193                 return -1;
194         }
195         if(n > 0 && buf[n-1] == '\n')
196                 n--;
197         buf[n] = '\0';
198         return n;
199 }
200
201 uchar*
202 getrange(Block *b)
203 {
204         char buf[128];
205         int fd, cfd;
206         uchar *data;
207
208         b->len = Blocksize;
209         if(b->off + b->len > size)
210                 b->len = size - b->off;
211
212         if(debug)
213                 print("getrange: %lld %lld\n", b->off, b->len);
214
215         if(fprint(webctlfd, "url %s\n", url) < 0)
216                 return nil;
217         if(fprint(webctlfd, "request GET\n") < 0)
218                 return nil;
219         if(fprint(webctlfd, "headers Range: bytes=%lld-%lld\n", b->off, b->off+b->len-1) < 0)
220                 return nil;
221
222         /* start the request */
223         snprint(buf, sizeof(buf), "%s/body", webconn);
224         if((fd = open(buf, OREAD)) < 0)
225                 return nil;
226
227         /* verify content-range response header */ 
228         snprint(buf, sizeof(buf), "%s/contentrange", webconn);
229         if((cfd = open(buf, OREAD)) < 0){
230                 close(fd);
231                 return nil;
232         }
233         if(readstring(cfd, buf, sizeof(buf)) <= 0){
234 Badrange:
235                 close(cfd);
236                 close(fd);
237                 return nil;
238         }
239         if(cistrncmp(buf, "bytes ", 6) != 0)
240                 goto Badrange;
241         if(strtoll(buf + 6, nil, 10) != b->off)
242                 goto Badrange;
243         close(cfd);
244
245         /* read body data */
246         data = emalloc9p(b->len);
247         if(readfile(fd, (char*)data, b->len) != b->len){
248                 close(fd);
249                 free(data);
250                 return nil;
251         }
252         close(fd);
253         b->p = data;
254         return data;
255 }
256
257 void
258 httpfilereadproc(void*)
259 {
260         Block *b;
261
262         threadsetname("httpfilereadproc %s", url);
263
264         for(;;){
265                 b = recvp(httpchan);
266                 if(b == nil)
267                         continue;
268                 if(getrange(b) == nil)
269                         sysfatal("getrange: %r");
270                 sendp(finishchan, b);
271         }
272 }
273
274 typedef struct Tab Tab;
275 struct Tab
276 {
277         char *name;
278         ulong mode;
279 };
280
281 Tab tab[] =
282 {
283         "/",            DMDIR|0555,
284         nil,            0444,
285 };
286
287 static void
288 fillstat(Dir *d, uvlong path)
289 {
290         Tab *t;
291
292         memset(d, 0, sizeof(*d));
293         d->uid = estrdup9p(user);
294         d->gid = estrdup9p(user);
295         d->qid.path = path;
296         d->atime = d->mtime = time0;
297         t = &tab[TYPE(path)];
298         d->name = estrdup9p(t->name);
299         d->length = size;
300         d->qid.type = t->mode>>24;
301         d->mode = t->mode;
302 }
303
304 static void
305 fsattach(Req *r)
306 {
307         if(r->ifcall.aname && r->ifcall.aname[0]){
308                 respond(r, "invalid attach specifier");
309                 return;
310         }
311         r->fid->qid.path = PATH(Qroot, 0);
312         r->fid->qid.type = QTDIR;
313         r->fid->qid.vers = 0;
314         r->ofcall.qid = r->fid->qid;
315         respond(r, nil);
316 }
317
318 static void
319 fsstat(Req *r)
320 {
321         fillstat(&r->d, r->fid->qid.path);
322         respond(r, nil);
323 }
324
325 static int
326 rootgen(int i, Dir *d, void*)
327 {
328         i += Qroot + 1;
329         if(i <= Qfile){
330                 fillstat(d, i);
331                 return 0;
332         }
333         return -1;
334 }
335
336 static char*
337 fswalk1(Fid *fid, char *name, Qid *qid)
338 {
339         int i;
340         ulong path;
341
342         path = fid->qid.path;
343         if(!(fid->qid.type & QTDIR))
344                 return "walk in non-directory";
345
346         if(strcmp(name, "..") == 0){
347                 switch(TYPE(path)){
348                 case Qroot:
349                         return nil;
350                 default:
351                         return "bug in fswalk1";
352                 }
353         }
354
355         i = TYPE(path) + 1;
356         while(i < nelem(tab)){
357                 if(strcmp(name, tab[i].name) == 0){
358                         qid->path = PATH(i, NUM(path));
359                         qid->type = tab[i].mode>>24;
360                         return nil;
361                 }
362                 if(tab[i].mode & DMDIR)
363                         break;
364                 i++;
365         }
366         return "directory entry not found";
367 }
368
369 vlong
370 getfilesize(void)
371 {
372         char buf[128];
373         int fd, cfd;
374
375         if(fprint(webctlfd, "url %s\n", url) < 0)
376                 return -1;
377         if(fprint(webctlfd, "request HEAD\n") < 0)
378                 return -1;
379         snprint(buf, sizeof(buf), "%s/body", webconn);
380         if((fd = open(buf, OREAD)) < 0)
381                 return -1;
382         snprint(buf, sizeof(buf), "%s/contentlength", webconn);
383         cfd = open(buf, OREAD);
384         close(fd);
385         if(cfd < 0)
386                 return -1;
387         if(readstring(cfd, buf, sizeof(buf)) <= 0){
388                 close(cfd);
389                 return -1;
390         }
391         close(cfd);
392         return strtoll(buf, nil, 10);
393 }
394
395 void
396 fileread(Req *r)
397 {
398         Block *b;
399
400         if(r->ifcall.offset >= size){
401                 r->ofcall.count = 0;
402                 respond(r, nil);
403                 return;
404         }
405
406         if((b = findblock(&cache, r->ifcall.offset)) != nil){
407                 readfrom(r, b);
408                 return;
409         }
410         if((b = findblock(&inprogress, r->ifcall.offset)) == nil){
411                 b = emalloc9p(sizeof(Block));
412                 b->off = r->ifcall.offset - (r->ifcall.offset % Blocksize);
413                 addblock(&inprogress, b);
414                 if(inprogress.first == b)
415                         sendp(httpchan, b);
416         }
417         queuereq(b, r);
418 }
419
420 static void
421 fsopen(Req *r)
422 {
423         if(r->ifcall.mode != OREAD){
424                 respond(r, "permission denied");
425                 return;
426         }
427         respond(r, nil);
428 }
429
430 void
431 finishthread(void*)
432 {
433         Block *b;
434         Req *r;
435
436         threadsetname("finishthread");
437
438         for(;;){
439                 b = recvp(finishchan);
440                 assert(b == inprogress.first);
441                 inprogress.first = b->link;
442                 ncache++;
443                 if(ncache >= mcache)
444                         evictblock(&cache);
445                 addblock(&cache, b);
446                 while((r = b->rq) != nil){
447                         b->rq = r->aux;
448                         r->aux = nil;
449                         readfrom(r, b);
450                 }
451                 if(inprogress.first)
452                         sendp(httpchan, inprogress.first);
453         }
454 }
455
456 void
457 fsnetproc(void*)
458 {
459         Req *r, *o;
460         Block *b;
461
462         threadcreate(finishthread, nil, 8192);
463
464         threadsetname("fsnetproc");
465
466         for(;;){
467                 r = recvp(reqchan);
468                 switch(r->ifcall.type){
469                 case Tflush:
470                         o = r->oldreq;
471                         b = findblock(&inprogress, o->ifcall.offset);
472                         if(b != nil)
473                                 delreq(b, o);
474                         respond(o, "interrupted");
475                         respond(r, nil);
476                         break;
477                 case Tread:
478                         fileread(r);
479                         break;
480                 default:
481                         respond(r, "bug in fsthread");
482                         break;
483                 }
484         }
485 }
486
487 static void
488 fsflush(Req *r)
489 {
490         sendp(reqchan, r);
491 }
492
493 static void
494 fsread(Req *r)
495 {
496         char e[ERRMAX];
497         ulong path;
498
499         path = r->fid->qid.path;
500         switch(TYPE(path)){
501         case Qroot:
502                 dirread9p(r, rootgen, nil);
503                 respond(r, nil);
504                 break;
505         case Qfile:
506                 sendp(reqchan, r);
507                 break;
508         default:
509                 snprint(e, sizeof(e), "bug in fsread path=%lux", path);
510                 respond(r, e);
511                 break;
512         }
513 }
514
515 Srv fs = 
516 {
517 .attach=                fsattach,
518 .walk1=         fswalk1,
519 .open=          fsopen,
520 .read=          fsread,
521 .stat=          fsstat,
522 .flush=         fsflush,
523 .end=           hangupclient,
524 };
525
526 void
527 threadmain(int argc, char **argv)
528 {
529         char *mtpt, *srvname, *p;
530
531         mtpt = nil;
532         srvname = nil;
533         ARGBEGIN{
534         case 'D':
535                 chatty9p++;
536                 break;
537         case 'd':
538                 debug++;
539                 break;
540         case 's':
541                 srvname = EARGF(usage());
542                 break;
543         case 'm':
544                 mtpt = EARGF(usage());
545                 break;
546         case 'c':
547                 mcache = atoi(EARGF(usage()));
548                 break;
549         case 'f':
550                 file = EARGF(usage());
551                 break;
552         default:
553                 usage();
554         }ARGEND;
555
556         if(srvname == nil && mtpt == nil)
557                 mtpt = ".";
558
559         if(argc < 1)
560                 usage();
561         if(mcache <= 0)
562                 mcache = 32;
563
564         time0 = time(0);
565         url = estrdup9p(argv[0]);
566         if(file == nil){
567                 file = strrchr(url, '/');
568                 if(file == nil || file[1] == '\0')
569                         file = "index";
570                 else
571                         file++;
572         }
573
574         snprint(webconn, sizeof(webconn), "/mnt/web/clone");
575         if((webctlfd = open(webconn, ORDWR)) < 0)
576                 sysfatal("open: %r");
577         p = strrchr(webconn, '/')+1;
578         if(readstring(webctlfd, p, webconn+sizeof(webconn)-p) <= 0)
579                 sysfatal("read: %r");
580
581         tab[Qfile].name = file;
582         user = getuser();
583         size = getfilesize();
584         if(size < 0)
585                 sysfatal("getfilesize: %r");
586
587         reqchan = chancreate(sizeof(Req*), 0);
588         httpchan = chancreate(sizeof(Block*), 0);
589         finishchan = chancreate(sizeof(Block*), 0);
590
591         procrfork(fsnetproc, nil, Stacksize, RFNAMEG|RFNOTEG);
592         procrfork(httpfilereadproc, nil, Stacksize, RFNAMEG|RFNOTEG);
593
594         threadpostmountsrv(&fs, srvname, mtpt, MBEFORE);
595         threadexits(0);
596 }