]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/wikifs/fs.c
kernel: make noswap flag exclude processes from killbig() if not eve, reset noswap...
[plan9front.git] / sys / src / cmd / wikifs / fs.c
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 }