1 #include <u.h> 2 #include <libc.h> 3 #include <bio.h> 4 #include <String.h> 5 #include <thread.h> 6 #include "wiki.h" 7 8 #include <auth.h> 9 #include <fcall.h> 10 #include <9p.h> 11 12 enum { 13 Qindexhtml, 14 Qindextxt, 15 Qraw, 16 Qhistoryhtml, 17 Qhistorytxt, 18 Qdiffhtml, 19 Qedithtml, 20 Qwerrorhtml, 21 Qwerrortxt, 22 Qhttplogin, 23 Nfile, 24 }; 25 26 static char *filelist[] = { 27 "index.html", 28 "index.txt", 29 "current", 30 "history.html", 31 "history.txt", 32 "diff.html", 33 "edit.html", 34 "werror.html", 35 "werror.txt", 36 ".httplogin", 37 }; 38 39 static int needhist[Nfile] = { 40 [Qhistoryhtml] 1, 41 [Qhistorytxt] 1, 42 [Qdiffhtml] 1, 43 }; 44 45 /* 46 * The qids are <8-bit type><16-bit page number><16-bit page version><8-bit file index>. 47 */ 48 enum { /* <8-bit type> */ 49 Droot = 1, 50 D1st, 51 D2nd, 52 Fnew, 53 Fmap, 54 F1st, 55 F2nd, 56 }; 57 58 uvlong 59 mkqid(int type, int num, int vers, int file) 60 { 61 return ((uvlong)type<<40) | ((uvlong)num<<24) | (vers<<8) | file; 62 } 63 64 int 65 qidtype(uvlong path) 66 { 67 return (path>>40)&0xFF; 68 } 69 70 int 71 qidnum(uvlong path) 72 { 73 return (path>>24)&0xFFFF; 74 } 75 76 int 77 qidvers(uvlong path) 78 { 79 return (path>>8)&0xFFFF; 80 } 81 82 int 83 qidfile(uvlong path) 84 { 85 return path&0xFF; 86 } 87 88 typedef struct Aux Aux; 89 struct Aux { 90 String *name; 91 Whist *w; 92 int n; 93 ulong t; 94 String *s; 95 Map *map; 96 }; 97 98 static void 99 fsattach(Req *r) 100 { 101 Aux *a; 102 103 if(r->ifcall.aname && r->ifcall.aname[0]){ 104 respond(r, "invalid attach specifier"); 105 return; 106 } 107 108 a = emalloc(sizeof(Aux)); 109 r->fid->aux = a; 110 a->name = s_copy(r->ifcall.uname); 111 112 r->ofcall.qid = (Qid){mkqid(Droot, 0, 0, 0), 0, QTDIR}; 113 r->fid->qid = r->ofcall.qid; 114 respond(r, nil); 115 } 116 117 static String * 118 httplogin(void) 119 { 120 String *s=s_new(); 121 Biobuf *b; 122 123 if((b = wBopen(".httplogin", OREAD)) == nil) 124 goto Return; 125 126 while(s_read(b, s, Bsize) > 0) 127 ; 128 Bterm(b); 129 130 Return: 131 return s; 132 } 133 134 static char* 135 fswalk1(Fid *fid, char *name, Qid *qid) 136 { 137 char *q; 138 int i, isdotdot, n, t; 139 uvlong path; 140 Aux *a; 141 Whist *wh; 142 String *s; 143 144 isdotdot = strcmp(name, "..")==0; 145 n = strtoul(name, &q, 10); 146 path = fid->qid.path; 147 a = fid->aux; 148 149 switch(qidtype(path)){ 150 case 0: 151 return "wikifs: bad path in server (bug)"; 152 153 case Droot: 154 if(isdotdot){ 155 *qid = fid->qid; 156 return nil; 157 } 158 if(strcmp(name, "new")==0){ 159 *qid = (Qid){mkqid(Fnew, 0, 0, 0), 0, 0}; 160 return nil; 161 } 162 if(strcmp(name, "map")==0){ 163 *qid = (Qid){mkqid(Fmap, 0, 0, 0), 0, 0}; 164 return nil; 165 } 166 if((*q!='\0' || (wh=getcurrent(n))==nil) 167 && (wh=getcurrentbyname(name))==nil) 168 return "file does not exist"; 169 *qid = (Qid){mkqid(D1st, wh->n, 0, 0), wh->doc->time, QTDIR}; 170 a->w = wh; 171 return nil; 172 173 case D1st: 174 if(isdotdot){ 175 *qid = (Qid){mkqid(Droot, 0, 0, 0), 0, QTDIR}; 176 return nil; 177 } 178 179 /* handle history directories */ 180 if(*q == '\0'){ 181 if((wh = gethistory(qidnum(path))) == nil) 182 return "file does not exist"; 183 for(i=0; i<wh->ndoc; i++) 184 if(wh->doc[i].time == n) 185 break; 186 if(i==wh->ndoc){ 187 closewhist(wh); 188 return "file does not exist"; 189 } 190 closewhist(a->w); 191 a->w = wh; 192 a->n = i; 193 *qid = (Qid){mkqid(D2nd, qidnum(path), i, 0), wh->doc[i].time, QTDIR}; 194 return nil; 195 } 196 197 /* handle files other than index */ 198 for(i=0; i<nelem(filelist); i++){ 199 if(strcmp(name, filelist[i])==0){ 200 if(needhist[i]){ 201 if((wh = gethistory(qidnum(path))) == nil) 202 return "file does not exist"; 203 closewhist(a->w); 204 a->w = wh; 205 } 206 *qid = (Qid){mkqid(F1st, qidnum(path), 0, i), a->w->doc->time, 0}; 207 goto Gotfile; 208 } 209 } 210 return "file does not exist"; 211 212 case D2nd: 213 if(isdotdot){ 214 /* 215 * Can't use a->w[a->ndoc-1] because that 216 * might be a failed write rather than the real one. 217 */ 218 *qid = (Qid){mkqid(D1st, qidnum(path), 0, 0), 0, QTDIR}; 219 if((wh = getcurrent(qidnum(path))) == nil) 220 return "file does not exist"; 221 closewhist(a->w); 222 a->w = wh; 223 a->n = 0; 224 return nil; 225 } 226 for(i=0; i<=Qraw; i++){ 227 if(strcmp(name, filelist[i])==0){ 228 *qid = (Qid){mkqid(F2nd, qidnum(path), qidvers(path), i), a->w->doc->time, 0}; 229 goto Gotfile; 230 } 231 } 232 return "file does not exist"; 233 234 default: 235 return "bad programming"; 236 } 237 /* not reached */ 238 239 Gotfile: 240 t = qidtype(qid->path); 241 switch(qidfile(qid->path)){ 242 case Qindexhtml: 243 s = tohtml(a->w, a->w->doc+a->n, 244 t==F1st? Tpage : Toldpage); 245 break; 246 case Qindextxt: 247 s = totext(a->w, a->w->doc+a->n, 248 t==F1st? Tpage : Toldpage); 249 break; 250 case Qraw: 251 s = s_copy(a->w->title); 252 s = s_append(s, "\n"); 253 s = doctext(s, &a->w->doc[a->n]); 254 break; 255 case Qhistoryhtml: 256 s = tohtml(a->w, a->w->doc+a->n, Thistory); 257 break; 258 case Qhistorytxt: 259 s = totext(a->w, a->w->doc+a->n, Thistory); 260 break; 261 case Qdiffhtml: 262 s = tohtml(a->w, a->w->doc+a->n, Tdiff); 263 break; 264 case Qedithtml: 265 s = tohtml(a->w, a->w->doc+a->n, Tedit); 266 break; 267 case Qwerrorhtml: 268 s = tohtml(a->w, a->w->doc+a->n, Twerror); 269 break; 270 case Qwerrortxt: 271 s = totext(a->w, a->w->doc+a->n, Twerror); 272 break; 273 case Qhttplogin: 274 s = httplogin(); 275 break; 276 default: 277 return "internal error"; 278 } 279 a->s = s; 280 return nil; 281 } 282 283 static void 284 fsopen(Req *r) 285 { 286 int t; 287 uvlong path; 288 Aux *a; 289 Fid *fid; 290 Whist *wh; 291 292 fid = r->fid; 293 path = fid->qid.path; 294 t = qidtype(fid->qid.path); 295 if((r->ifcall.mode != OREAD && t != Fnew && t != Fmap) 296 || (r->ifcall.mode&ORCLOSE)){ 297 respond(r, "permission denied"); 298 return; 299 } 300 301 a = fid->aux; 302 switch(t){ 303 case Droot: 304 currentmap(0); 305 rlock(&maplock); 306 a->map = map; 307 incref(map); 308 runlock(&maplock); 309 respond(r, nil); 310 break; 311 312 case D1st: 313 if((wh = gethistory(qidnum(path))) == nil){ 314 respond(r, "file does not exist"); 315 return; 316 } 317 closewhist(a->w); 318 a->w = wh; 319 a->n = a->w->ndoc-1; 320 r->ofcall.qid.vers = wh->doc[a->n].time; 321 r->fid->qid = r->ofcall.qid; 322 respond(r, nil); 323 break; 324 325 case D2nd: 326 respond(r, nil); 327 break; 328 329 case Fnew: 330 a->s = s_copy(""); 331 respond(r, nil); 332 break; 333 334 case Fmap: 335 case F1st: 336 case F2nd: 337 respond(r, nil); 338 break; 339 340 default: 341 respond(r, "programmer error"); 342 break; 343 } 344 } 345 346 static char* 347 fsclone(Fid *old, Fid *new) 348 { 349 Aux *a; 350 351 a = emalloc(sizeof(*a)); 352 *a = *(Aux*)old->aux; 353 if(a->s) 354 s_incref(a->s); 355 if(a->w) 356 incref(a->w); 357 if(a->map) 358 incref(a->map); 359 if(a->name) 360 s_incref(a->name); 361 new->aux = a; 362 new->qid = old->qid; 363 364 return nil; 365 } 366 367 static void 368 fsdestroyfid(Fid *fid) 369 { 370 Aux *a; 371 372 a = fid->aux; 373 if(a==nil) 374 return; 375 376 if(a->name) 377 s_free(a->name); 378 if(a->map) 379 closemap(a->map); 380 if(a->s) 381 s_free(a->s); 382 if(a->w) 383 closewhist(a->w); 384 free(a); 385 fid->aux = nil; 386 } 387 388 static void 389 fillstat(Dir *d, uvlong path, ulong tm, ulong length) 390 { 391 char tmp[32], *p; 392 int type; 393 394 memset(d, 0, sizeof(Dir)); 395 d->uid = estrdup9p("wiki"); 396 d->gid = estrdup9p("wiki"); 397 398 switch(qidtype(path)){ 399 case Droot: 400 case D1st: 401 case D2nd: 402 type = QTDIR; 403 break; 404 default: 405 type = 0; 406 break; 407 } 408 d->qid = (Qid){path, tm, type}; 409 410 d->atime = d->mtime = tm; 411 d->length = length; 412 if(qidfile(path) == Qedithtml) 413 d->atime = d->mtime = time(0); 414 415 switch(qidtype(path)){ 416 case Droot: 417 d->name = estrdup("/"); 418 d->mode = DMDIR|0555; 419 break; 420 421 case D1st: 422 d->name = numtoname(qidnum(path)); 423 if(d->name == nil) 424 d->name = estrdup("<dead>"); 425 for(p=d->name; *p; p++) 426 if(*p==' ') 427 *p = '_'; 428 d->mode = DMDIR|0555; 429 break; 430 431 case D2nd: 432 snprint(tmp, sizeof tmp, "%lud", tm); 433 d->name = estrdup(tmp); 434 d->mode = DMDIR|0555; 435 break; 436 437 case Fmap: 438 d->name = estrdup("map"); 439 d->mode = 0666; 440 break; 441 442 case Fnew: 443 d->name = estrdup("new"); 444 d->mode = 0666; 445 break; 446 447 case F1st: 448 d->name = estrdup(filelist[qidfile(path)]); 449 d->mode = 0444; 450 break; 451 452 case F2nd: 453 d->name = estrdup(filelist[qidfile(path)]); 454 d->mode = 0444; 455 break; 456 457 default: 458 print("bad qid path 0x%.8llux\n", path); 459 break; 460 } 461 } 462 463 static void 464 fsstat(Req *r) 465 { 466 Aux *a; 467 Fid *fid; 468 ulong t; 469 470 t = 0; 471 fid = r->fid; 472 if((a = fid->aux) && a->w) 473 t = a->w->doc[a->n].time; 474 475 fillstat(&r->d, fid->qid.path, t, a->s ? s_len(a->s) : 0); 476 respond(r, nil); 477 } 478 479 typedef struct Bogus Bogus; 480 struct Bogus { 481 uvlong path; 482 Aux *a; 483 }; 484 485 static int 486 rootgen(int i, Dir *d, void *aux) 487 { 488 Aux *a; 489 Bogus *b; 490 491 b = aux; 492 a = b->a; 493 switch(i){ 494 case 0: /* new */ 495 fillstat(d, mkqid(Fnew, 0, 0, 0), a->map->t, 0); 496 return 0; 497 case 1: /* map */ 498 fillstat(d, mkqid(Fmap, 0, 0, 0), a->map->t, 0); 499 return 0; 500 default: /* first-level directory */ 501 i -= 2; 502 if(i >= a->map->nel) 503 return -1; 504 fillstat(d, mkqid(D1st, a->map->el[i].n, 0, 0), a->map->t, 0); 505 return 0; 506 } 507 } 508 509 static int 510 firstgen(int i, Dir *d, void *aux) 511 { 512 ulong t; 513 Bogus *b; 514 int num; 515 Aux *a; 516 517 b = aux; 518 num = qidnum(b->path); 519 a = b->a; 520 t = a->w->doc[a->n].time; 521 522 if(i < Nfile){ /* file in first-level directory */ 523 fillstat(d, mkqid(F1st, num, 0, i), t, 0); 524 return 0; 525 } 526 i -= Nfile; 527 528 if(i < a->w->ndoc){ /* second-level (history) directory */ 529 fillstat(d, mkqid(D2nd, num, i, 0), a->w->doc[i].time, 0); 530 return 0; 531 } 532 //i -= a->w->ndoc; 533 534 return -1; 535 } 536 537 static int 538 secondgen(int i, Dir *d, void *aux) 539 { 540 Bogus *b; 541 uvlong path; 542 Aux *a; 543 544 b = aux; 545 path = b->path; 546 a = b->a; 547 548 if(i <= Qraw){ /* index.html, index.txt, raw */ 549 fillstat(d, mkqid(F2nd, qidnum(path), qidvers(path), i), a->w->doc[a->n].time, 0); 550 return 0; 551 } 552 //i -= Qraw; 553 554 return -1; 555 } 556 557 static void 558 fsread(Req *r) 559 { 560 char *t, *s; 561 uvlong path; 562 Aux *a; 563 Bogus b; 564 565 a = r->fid->aux; 566 path = r->fid->qid.path; 567 b.a = a; 568 b.path = path; 569 switch(qidtype(path)){ 570 default: 571 respond(r, "cannot happen (bad qid)"); 572 return; 573 574 case Droot: 575 if(a == nil || a->map == nil){ 576 respond(r, "cannot happen (no map)"); 577 return; 578 } 579 dirread9p(r, rootgen, &b); 580 respond(r, nil); 581 return; 582 583 case D1st: 584 if(a == nil || a->w == nil){ 585 respond(r, "cannot happen (no wh)"); 586 return; 587 } 588 dirread9p(r, firstgen, &b); 589 respond(r, nil); 590 return; 591 592 case D2nd: 593 dirread9p(r, secondgen, &b); 594 respond(r, nil); 595 return; 596 597 case Fnew: 598 if(a->s){ 599 respond(r, "protocol botch"); 600 return; 601 } 602 /* fall through */ 603 case Fmap: 604 t = numtoname(a->n); 605 if(t == nil){ 606 respond(r, "unknown name"); 607 return; 608 } 609 for(s=t; *s; s++) 610 if(*s == ' ') 611 *s = '_'; 612 readstr(r, t); 613 free(t); 614 respond(r, nil); 615 return; 616 617 case F1st: 618 case F2nd: 619 if(a == nil || a->s == nil){ 620 respond(r, "cannot happen (no s)"); 621 return; 622 } 623 readbuf(r, s_to_c(a->s), s_len(a->s)); 624 respond(r, nil); 625 return; 626 } 627 } 628 629 typedef struct Sread Sread; 630 struct Sread { 631 char *rp; 632 }; 633 634 static char* 635 Srdline(void *v, int c) 636 { 637 char *p, *rv; 638 Sread *s; 639 640 s = v; 641 if(s->rp == nil) 642 rv = nil; 643 else if(p = strchr(s->rp, c)){ 644 *p = '\0'; 645 rv = s->rp; 646 s->rp = p+1; 647 }else{ 648 rv = s->rp; 649 s->rp = nil; 650 } 651 return rv; 652 } 653 654 static void 655 responderrstr(Req *r) 656 { 657 char buf[ERRMAX]; 658 659 rerrstr(buf, sizeof buf); 660 if(buf[0] == '\0') 661 strcpy(buf, "unknown error"); 662 respond(r, buf); 663 } 664 665 static void 666 fswrite(Req *r) 667 { 668 char *author, *comment, *net, *err, *p, *title, tmp[40]; 669 int rv, n; 670 ulong t; 671 Aux *a; 672 Fid *fid; 673 Sread s; 674 String *stmp; 675 Whist *w; 676 677 fid = r->fid; 678 a = fid->aux; 679 switch(qidtype(fid->qid.path)){ 680 case Fmap: 681 stmp = s_nappend(s_reset(nil), r->ifcall.data, r->ifcall.count); 682 a->n = nametonum(s_to_c(stmp)); 683 s_free(stmp); 684 if(a->n < 0) 685 respond(r, "name not found"); 686 else 687 respond(r, nil); 688 return; 689 case Fnew: 690 break; 691 default: 692 respond(r, "cannot happen"); 693 return; 694 } 695 696 if(a->s == nil){ 697 respond(r, "protocol botch"); 698 return; 699 } 700 if(r->ifcall.count==0){ /* do final processing */ 701 s.rp = s_to_c(a->s); 702 w = nil; 703 err = "bad format"; 704 if((title = Srdline(&s, '\n')) == nil){ 705 Error: 706 if(w) 707 closewhist(w); 708 s_free(a->s); 709 a->s = nil; 710 respond(r, err); 711 return; 712 } 713 714 w = emalloc(sizeof(*w)); 715 incref(w); 716 w->title = estrdup(title); 717 718 t = 0; 719 author = estrdup(s_to_c(a->name)); 720 721 comment = nil; 722 while(s.rp && *s.rp && *s.rp != '\n'){ 723 p = Srdline(&s, '\n'); 724 assert(p != nil); 725 switch(p[0]){ 726 case 'A': 727 free(author); 728 author = estrdup(p+1); 729 break; 730 case 'D': 731 t = strtoul(p+1, &p, 10); 732 if(*p != '\0') 733 goto Error; 734 break; 735 case 'C': 736 free(comment); 737 comment = estrdup(p+1); 738 break; 739 } 740 } 741 742 w->doc = emalloc(sizeof(w->doc[0])); 743 w->doc->time = time(0); 744 w->doc->comment = comment; 745 746 if(net = r->pool->srv->aux){ 747 p = emalloc(strlen(author)+10+strlen(net)); 748 strcpy(p, author); 749 strcat(p, " ("); 750 strcat(p, net); 751 strcat(p, ")"); 752 free(author); 753 author = p; 754 } 755 w->doc->author = author; 756 757 if((w->doc->wtxt = Brdpage(Srdline, &s)) == nil){ 758 err = "empty document"; 759 goto Error; 760 } 761 762 w->ndoc = 1; 763 if((n = allocnum(w->title, 0)) < 0) 764 goto Error; 765 sprint(tmp, "D%lud\n", w->doc->time); 766 a->s = s_reset(a->s); 767 a->s = doctext(a->s, w->doc); 768 rv = writepage(n, t, a->s, w->title); 769 s_free(a->s); 770 a->s = nil; 771 a->n = n; 772 closewhist(w); 773 if(rv < 0) 774 responderrstr(r); 775 else 776 respond(r, nil); 777 return; 778 } 779 780 if(s_len(a->s)+r->ifcall.count > Maxfile){ 781 respond(r, "file too large"); 782 s_free(a->s); 783 a->s = nil; 784 return; 785 } 786 a->s = s_nappend(a->s, r->ifcall.data, r->ifcall.count); 787 r->ofcall.count = r->ifcall.count; 788 respond(r, nil); 789 } 790 791 Srv wikisrv = { 792 .attach= fsattach, 793 .destroyfid= fsdestroyfid, 794 .clone= fsclone, 795 .walk1= fswalk1, 796 .open= fsopen, 797 .read= fsread, 798 .write= fswrite, 799 .stat= fsstat, 800 }; 801 802 void 803 usage(void) 804 { 805 fprint(2, "usage: wikifs [-D] [-a addr]... [-m mtpt] [-p perm] [-s service] dir\n"); 806 exits("usage"); 807 } 808 809 void 810 main(int argc, char **argv) 811 { 812 char **addr; 813 int i, naddr; 814 char *buf; 815 char *service, *mtpt; 816 ulong perm; 817 Dir d, *dp; 818 Srv *s; 819 820 naddr = 0; 821 addr = nil; 822 perm = 0; 823 service = nil; 824 mtpt = "/mnt/wiki"; 825 ARGBEGIN{ 826 case 'D': 827 chatty9p++; 828 break; 829 case 'a': 830 if(naddr%8 == 0) 831 addr = erealloc(addr, (naddr+8)*sizeof(addr[0])); 832 addr[naddr++] = EARGF(usage()); 833 break; 834 case 'm': 835 mtpt = EARGF(usage()); 836 break; 837 case 'M': 838 mtpt = nil; 839 break; 840 case 'p': 841 perm = strtoul(EARGF(usage()), nil, 8); 842 break; 843 case 's': 844 service = EARGF(usage()); 845 break; 846 default: 847 usage(); 848 break; 849 }ARGEND 850 851 if(argc != 1) 852 usage(); 853 854 if((dp = dirstat(argv[0])) == nil) 855 sysfatal("dirstat %s: %r", argv[0]); 856 if((dp->mode&DMDIR) == 0) 857 sysfatal("%s: not a directory", argv[0]); 858 free(dp); 859 wikidir = argv[0]; 860 861 currentmap(0); 862 863 for(i=0; i<naddr; i++) 864 listensrv(&wikisrv, addr[i]); 865 866 s = emalloc(sizeof *s); 867 *s = wikisrv; 868 postmountsrv(s, service, mtpt, MREPL|MCREATE); 869 if(perm){ 870 buf = emalloc9p(5+strlen(service)+1); 871 strcpy(buf, "/srv/"); 872 strcat(buf, service); 873 nulldir(&d); 874 d.mode = perm; 875 if(dirwstat(buf, &d) < 0) 876 fprint(2, "wstat: %r\n"); 877 free(buf); 878 } 879 exits(nil); 880 } 881