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 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 139 fsstat(Req *r) 140 { 141 fillstat(&r->d, r->fid->qid.path, 0, nil); 142 respond(r, nil); 143 } 144 145 static int 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 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 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 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**)((ulong)c->url+tab[TYPE(path)].offset)) != nil) 277 readstr(r, s); 278 respond(r, nil); 279 break; 280 } 281 } 282 283 static void 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 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 416 fsdestroyfid(Fid *fid) 417 { 418 sendp(cclunk, fid); 419 recvp(cclunkwait); 420 } 421 422 static void 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* 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 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 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 580 fssend(Req *r) 581 { 582 sendp(creq, r); 583 recvp(creqwait); /* avoids need to deal with spurious flushes */ 584 } 585 586 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 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