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