1 /*
2 * Web file system. Conventionally mounted at /mnt/web
3 *
4 * ctl send control messages (might go away)
5 * cookies list of cookies, editable
6 * clone open and read to obtain new connection
7 * n connection directory
8 * ctl control messages (like get url)
9 * body retrieved data
10 * content-type mime content-type of body
11 * postbody data to be posted
12 * parsed parsed version of url
13 * url entire url
14 * scheme http, ftp, etc.
15 * host hostname
16 * path path on host
17 * query query after path
18 * fragment #foo anchor reference
19 * user user name (ftp)
20 * password password (ftp)
21 * ftptype transfer mode (ftp)
22 */
23
24 #include <u.h>
25 #include <libc.h>
26 #include <bio.h>
27 #include <ip.h>
28 #include <plumb.h>
29 #include <thread.h>
30 #include <fcall.h>
31 #include <9p.h>
32 #include "dat.h"
33 #include "fns.h"
34
35 int fsdebug;
36
37 enum
38 {
39 Qroot,
40 Qrootctl,
41 Qclone,
42 Qcookies,
43 Qclient,
44 Qctl,
45 Qbody,
46 Qbodyext,
47 Qcontenttype,
48 Qpostbody,
49 Qparsed,
50 Qurl,
51 Qscheme,
52 Qschemedata,
53 Quser,
54 Qpasswd,
55 Qhost,
56 Qport,
57 Qpath,
58 Qquery,
59 Qfragment,
60 Qftptype,
61 Qend,
62 };
63
64 #define PATH(type, n) ((type)|((n)<<8))
65 #define TYPE(path) ((int)(path) & 0xFF)
66 #define NUM(path) ((uint)(path)>>8)
67
68 Channel *creq;
69 Channel *creqwait;
70 Channel *cclunk;
71 Channel *cclunkwait;
72
73 typedef struct Tab Tab;
74 struct Tab
75 {
76 char *name;
77 ulong mode;
78 int offset;
79 };
80
81 Tab tab[] =
82 {
83 "/", DMDIR|0555, 0,
84 "ctl", 0666, 0,
85 "clone", 0666, 0,
86 "cookies", 0666, 0,
87 "XXX", DMDIR|0555, 0,
88 "ctl", 0666, 0,
89 "body", 0444, 0,
90 "XXX", 0444, 0,
91 "contenttype", 0444, 0,
92 "postbody", 0666, 0,
93 "parsed", DMDIR|0555, 0,
94 "url", 0444, offsetof(Url, url),
95 "scheme", 0444, offsetof(Url, scheme),
96 "schemedata", 0444, offsetof(Url, schemedata),
97 "user", 0444, offsetof(Url, user),
98 "passwd", 0444, offsetof(Url, passwd),
99 "host", 0444, offsetof(Url, host),
100 "port", 0444, offsetof(Url, port),
101 "path", 0444, offsetof(Url, path),
102 "query", 0444, offsetof(Url, query),
103 "fragment", 0444, offsetof(Url, fragment),
104 "ftptype", 0444, offsetof(Url, ftp.type),
105 };
106
107 ulong time0;
108
109 static void
fillstat(Dir * d,uvlong path,ulong length,char * ext)110 fillstat(Dir *d, uvlong path, ulong length, char *ext)
111 {
112 Tab *t;
113 int type;
114 char buf[32];
115
116 memset(d, 0, sizeof(*d));
117 d->uid = estrdup("web");
118 d->gid = estrdup("web");
119 d->qid.path = path;
120 d->atime = d->mtime = time0;
121 d->length = length;
122 type = TYPE(path);
123 t = &tab[type];
124 if(type == Qbodyext) {
125 snprint(buf, sizeof buf, "body.%s", ext == nil ? "xxx" : ext);
126 d->name = estrdup(buf);
127 }
128 else if(t->name)
129 d->name = estrdup(t->name);
130 else{ /* client directory */
131 snprint(buf, sizeof buf, "%ud", NUM(path));
132 d->name = estrdup(buf);
133 }
134 d->qid.type = t->mode>>24;
135 d->mode = t->mode;
136 }
137
138 static void
fsstat(Req * r)139 fsstat(Req *r)
140 {
141 fillstat(&r->d, r->fid->qid.path, 0, nil);
142 respond(r, nil);
143 }
144
145 static int
rootgen(int i,Dir * d,void *)146 rootgen(int i, Dir *d, void*)
147 {
148 char buf[32];
149
150 i += Qroot+1;
151 if(i < Qclient){
152 fillstat(d, i, 0, nil);
153 return 0;
154 }
155 i -= Qclient;
156 if(i < nclient){
157 fillstat(d, PATH(Qclient, i), 0, nil);
158 snprint(buf, sizeof buf, "%d", i);
159 free(d->name);
160 d->name = estrdup(buf);
161 return 0;
162 }
163 return -1;
164 }
165
166 static int
clientgen(int i,Dir * d,void * aux)167 clientgen(int i, Dir *d, void *aux)
168 {
169 Client *c;
170
171 c = aux;
172 i += Qclient+1;
173 if(i <= Qparsed){
174 fillstat(d, PATH(i, c->num), 0, c->ext);
175 return 0;
176 }
177 return -1;
178 }
179
180 static int
parsedgen(int i,Dir * d,void * aux)181 parsedgen(int i, Dir *d, void *aux)
182 {
183 Client *c;
184
185 c = aux;
186 i += Qparsed+1;
187 if(i < Qend){
188 fillstat(d, PATH(i, c->num), 0, nil);
189 return 0;
190 }
191 return -1;
192 }
193
194 static void
fsread(Req * r)195 fsread(Req *r)
196 {
197 char *s;
198 char e[ERRMAX];
199 Client *c;
200 ulong path;
201
202 path = r->fid->qid.path;
203 switch(TYPE(path)){
204 default:
205 snprint(e, sizeof e, "bug in webfs path=%lux\n", path);
206 respond(r, e);
207 break;
208
209 case Qroot:
210 dirread9p(r, rootgen, nil);
211 respond(r, nil);
212 break;
213
214 case Qrootctl:
215 globalctlread(r);
216 break;
217
218 case Qcookies:
219 cookieread(r);
220 break;
221
222 case Qclient:
223 dirread9p(r, clientgen, client[NUM(path)]);
224 respond(r, nil);
225 break;
226
227 case Qctl:
228 ctlread(r, client[NUM(path)]);
229 break;
230
231 case Qcontenttype:
232 c = client[NUM(path)];
233 if(c->contenttype == nil)
234 r->ofcall.count = 0;
235 else
236 readstr(r, c->contenttype);
237 respond(r, nil);
238 break;
239
240 case Qpostbody:
241 c = client[NUM(path)];
242 readbuf(r, c->postbody, c->npostbody);
243 respond(r, nil);
244 break;
245
246 case Qbody:
247 case Qbodyext:
248 c = client[NUM(path)];
249 if(c->iobusy){
250 respond(r, "already have i/o pending");
251 break;
252 }
253 c->iobusy = 1;
254 sendp(c->creq, r);
255 break;
256
257 case Qparsed:
258 dirread9p(r, parsedgen, client[NUM(path)]);
259 respond(r, nil);
260 break;
261
262 case Qurl:
263 case Qscheme:
264 case Qschemedata:
265 case Quser:
266 case Qpasswd:
267 case Qhost:
268 case Qport:
269 case Qpath:
270 case Qquery:
271 case Qfragment:
272 case Qftptype:
273 c = client[NUM(path)];
274 r->ofcall.count = 0;
275 if(c->url != nil
276 && (s = *(char**)((uintptr)c->url+tab[TYPE(path)].offset)) != nil)
277 readstr(r, s);
278 respond(r, nil);
279 break;
280 }
281 }
282
283 static void
fswrite(Req * r)284 fswrite(Req *r)
285 {
286 int m;
287 ulong path;
288 char e[ERRMAX], *buf, *cmd, *arg;
289 Client *c;
290
291 path = r->fid->qid.path;
292 switch(TYPE(path)){
293 default:
294 snprint(e, sizeof e, "bug in webfs path=%lux\n", path);
295 respond(r, e);
296 break;
297
298 case Qcookies:
299 cookiewrite(r);
300 break;
301
302 case Qrootctl:
303 case Qctl:
304 if(r->ifcall.count >= 1024){
305 respond(r, "ctl message too long");
306 return;
307 }
308 buf = estredup(r->ifcall.data, (char*)r->ifcall.data+r->ifcall.count);
309 cmd = buf;
310 arg = strpbrk(cmd, "\t ");
311 if(arg){
312 *arg++ = '\0';
313 arg += strspn(arg, "\t ");
314 }else
315 arg = "";
316 r->ofcall.count = r->ifcall.count;
317 if(TYPE(path)==Qrootctl){
318 if(!ctlwrite(r, &globalctl, cmd, arg)
319 && !globalctlwrite(r, cmd, arg))
320 respond(r, "unknown control command");
321 }else{
322 c = client[NUM(path)];
323 if(!ctlwrite(r, &c->ctl, cmd, arg)
324 && !clientctlwrite(r, c, cmd, arg))
325 respond(r, "unknown control command");
326 }
327 free(buf);
328 break;
329
330 case Qpostbody:
331 c = client[NUM(path)];
332 if(c->bodyopened){
333 respond(r, "cannot write postbody after opening body");
334 break;
335 }
336 if(r->ifcall.offset >= 128*1024*1024){ /* >128MB is probably a mistake */
337 respond(r, "offset too large");
338 break;
339 }
340 m = r->ifcall.offset + r->ifcall.count;
341 if(c->npostbody < m){
342 c->postbody = erealloc(c->postbody, m);
343 memset(c->postbody+c->npostbody, 0, m-c->npostbody);
344 c->npostbody = m;
345 }
346 memmove(c->postbody+r->ifcall.offset, r->ifcall.data, r->ifcall.count);
347 r->ofcall.count = r->ifcall.count;
348 respond(r, nil);
349 break;
350 }
351 }
352
353 static void
fsopen(Req * r)354 fsopen(Req *r)
355 {
356 static int need[4] = { 4, 2, 6, 1 };
357 ulong path;
358 int n;
359 Client *c;
360 Tab *t;
361
362 /*
363 * lib9p already handles the blatantly obvious.
364 * we just have to enforce the permissions we have set.
365 */
366 path = r->fid->qid.path;
367 t = &tab[TYPE(path)];
368 n = need[r->ifcall.mode&3];
369 if((n&t->mode) != n){
370 respond(r, "permission denied");
371 return;
372 }
373
374 switch(TYPE(path)){
375 case Qcookies:
376 cookieopen(r);
377 break;
378
379 case Qpostbody:
380 c = client[NUM(path)];
381 c->havepostbody++;
382 c->ref++;
383 respond(r, nil);
384 break;
385
386 case Qbody:
387 case Qbodyext:
388 c = client[NUM(path)];
389 if(c->url == nil){
390 respond(r, "url is not yet set");
391 break;
392 }
393 c->bodyopened = 1;
394 c->ref++;
395 sendp(c->creq, r);
396 break;
397
398 case Qclone:
399 n = newclient(0);
400 path = PATH(Qctl, n);
401 r->fid->qid.path = path;
402 r->ofcall.qid.path = path;
403 if(fsdebug)
404 fprint(2, "open clone => path=%lux\n", path);
405 t = &tab[Qctl];
406 /* fall through */
407 default:
408 if(t-tab >= Qclient)
409 client[NUM(path)]->ref++;
410 respond(r, nil);
411 break;
412 }
413 }
414
415 static void
fsdestroyfid(Fid * fid)416 fsdestroyfid(Fid *fid)
417 {
418 sendp(cclunk, fid);
419 recvp(cclunkwait);
420 }
421
422 static void
fsattach(Req * r)423 fsattach(Req *r)
424 {
425 if(r->ifcall.aname && r->ifcall.aname[0]){
426 respond(r, "invalid attach specifier");
427 return;
428 }
429 r->fid->qid.path = PATH(Qroot, 0);
430 r->fid->qid.type = QTDIR;
431 r->fid->qid.vers = 0;
432 r->ofcall.qid = r->fid->qid;
433 respond(r, nil);
434 }
435
436 static char*
fswalk1(Fid * fid,char * name,Qid * qid)437 fswalk1(Fid *fid, char *name, Qid *qid)
438 {
439 int i, n;
440 ulong path;
441 char buf[32], *ext;
442
443 path = fid->qid.path;
444 if(!(fid->qid.type&QTDIR))
445 return "walk in non-directory";
446
447 if(strcmp(name, "..") == 0){
448 switch(TYPE(path)){
449 case Qparsed:
450 qid->path = PATH(Qclient, NUM(path));
451 qid->type = tab[Qclient].mode>>24;
452 return nil;
453 case Qclient:
454 case Qroot:
455 qid->path = PATH(Qroot, 0);
456 qid->type = tab[Qroot].mode>>24;
457 return nil;
458 default:
459 return "bug in fswalk1";
460 }
461 }
462
463 i = TYPE(path)+1;
464 for(; i<nelem(tab); i++){
465 if(i==Qclient){
466 n = atoi(name);
467 snprint(buf, sizeof buf, "%d", n);
468 if(n < nclient && strcmp(buf, name) == 0){
469 qid->path = PATH(i, n);
470 qid->type = tab[i].mode>>24;
471 return nil;
472 }
473 break;
474 }
475 if(i==Qbodyext){
476 ext = client[NUM(path)]->ext;
477 snprint(buf, sizeof buf, "body.%s", ext == nil ? "xxx" : ext);
478 if(strcmp(buf, name) == 0){
479 qid->path = PATH(i, NUM(path));
480 qid->type = tab[i].mode>>24;
481 return nil;
482 }
483 }
484 else if(strcmp(name, tab[i].name) == 0){
485 qid->path = PATH(i, NUM(path));
486 qid->type = tab[i].mode>>24;
487 return nil;
488 }
489 if(tab[i].mode&DMDIR)
490 break;
491 }
492 return "directory entry not found";
493 }
494
495 static void
fsflush(Req * r)496 fsflush(Req *r)
497 {
498 Req *or;
499 int t;
500 Client *c;
501 ulong path;
502
503 or=r;
504 while(or->ifcall.type==Tflush)
505 or = or->oldreq;
506
507 if(or->ifcall.type != Tread && or->ifcall.type != Topen)
508 abort();
509
510 path = or->fid->qid.path;
511 t = TYPE(path);
512 if(t != Qbody && t != Qbodyext)
513 abort();
514
515 c = client[NUM(path)];
516 sendp(c->creq, r);
517 iointerrupt(c->io);
518 }
519
520 static void
fsthread(void *)521 fsthread(void*)
522 {
523 ulong path;
524 Alt a[3];
525 Fid *fid;
526 Req *r;
527
528 threadsetname("fsthread");
529 plumbstart();
530
531 a[0].op = CHANRCV;
532 a[0].c = cclunk;
533 a[0].v = &fid;
534 a[1].op = CHANRCV;
535 a[1].c = creq;
536 a[1].v = &r;
537 a[2].op = CHANEND;
538
539 for(;;){
540 switch(alt(a)){
541 case 0:
542 path = fid->qid.path;
543 if(TYPE(path)==Qcookies)
544 cookieclunk(fid);
545 if(fid->omode != -1 && TYPE(path) >= Qclient)
546 closeclient(client[NUM(path)]);
547 sendp(cclunkwait, nil);
548 break;
549 case 1:
550 switch(r->ifcall.type){
551 case Tattach:
552 fsattach(r);
553 break;
554 case Topen:
555 fsopen(r);
556 break;
557 case Tread:
558 fsread(r);
559 break;
560 case Twrite:
561 fswrite(r);
562 break;
563 case Tstat:
564 fsstat(r);
565 break;
566 case Tflush:
567 fsflush(r);
568 break;
569 default:
570 respond(r, "bug in fsthread");
571 break;
572 }
573 sendp(creqwait, 0);
574 break;
575 }
576 }
577 }
578
579 static void
fssend(Req * r)580 fssend(Req *r)
581 {
582 sendp(creq, r);
583 recvp(creqwait); /* avoids need to deal with spurious flushes */
584 }
585
586 void
initfs(void)587 initfs(void)
588 {
589 time0 = time(0);
590 creq = chancreate(sizeof(void*), 0);
591 creqwait = chancreate(sizeof(void*), 0);
592 cclunk = chancreate(sizeof(void*), 0);
593 cclunkwait = chancreate(sizeof(void*), 0);
594 procrfork(fsthread, nil, STACK, RFNAMEG);
595 }
596
597 void
takedown(Srv *)598 takedown(Srv*)
599 {
600 closecookies();
601 threadexitsall("done");
602 }
603
604 Srv fs =
605 {
606 .attach= fssend,
607 .destroyfid= fsdestroyfid,
608 .walk1= fswalk1,
609 .open= fssend,
610 .read= fssend,
611 .write= fssend,
612 .stat= fssend,
613 .flush= fssend,
614 .end= takedown,
615 };
616
617