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
mkqid(int type,int num,int vers,int file)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
qidtype(uvlong path)65 qidtype(uvlong path)
66 {
67 return (path>>40)&0xFF;
68 }
69
70 int
qidnum(uvlong path)71 qidnum(uvlong path)
72 {
73 return (path>>24)&0xFFFF;
74 }
75
76 int
qidvers(uvlong path)77 qidvers(uvlong path)
78 {
79 return (path>>8)&0xFFFF;
80 }
81
82 int
qidfile(uvlong path)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
fsattach(Req * r)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 *
httplogin(void)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*
fswalk1(Fid * fid,char * name,Qid * qid)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
fsopen(Req * r)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*
fsclone(Fid * old,Fid * new)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
fsdestroyfid(Fid * fid)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
fillstat(Dir * d,uvlong path,ulong tm,ulong length)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
fsstat(Req * r)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
rootgen(int i,Dir * d,void * aux)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
firstgen(int i,Dir * d,void * aux)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
secondgen(int i,Dir * d,void * aux)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
fsread(Req * r)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*
Srdline(void * v,int c)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
responderrstr(Req * r)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
fswrite(Req * r)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
usage(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
main(int argc,char ** argv)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