#include #include #include #include #include #include "wiki.h" #include #include #include <9p.h> enum { Qindexhtml, Qindextxt, Qraw, Qhistoryhtml, Qhistorytxt, Qdiffhtml, Qedithtml, Qwerrorhtml, Qwerrortxt, Qhttplogin, Nfile, }; static char *filelist[] = { "index.html", "index.txt", "current", "history.html", "history.txt", "diff.html", "edit.html", "werror.html", "werror.txt", ".httplogin", }; static int needhist[Nfile] = { [Qhistoryhtml] 1, [Qhistorytxt] 1, [Qdiffhtml] 1, }; /* * The qids are <8-bit type><16-bit page number><16-bit page version><8-bit file index>. */ enum { /* <8-bit type> */ Droot = 1, D1st, D2nd, Fnew, Fmap, F1st, F2nd, }; uvlong mkqid(int type, int num, int vers, int file) { return ((uvlong)type<<40) | ((uvlong)num<<24) | (vers<<8) | file; } int qidtype(uvlong path) { return (path>>40)&0xFF; } int qidnum(uvlong path) { return (path>>24)&0xFFFF; } int qidvers(uvlong path) { return (path>>8)&0xFFFF; } int qidfile(uvlong path) { return path&0xFF; } typedef struct Aux Aux; struct Aux { String *name; Whist *w; int n; ulong t; String *s; Map *map; }; static void fsattach(Req *r) { Aux *a; if(r->ifcall.aname && r->ifcall.aname[0]){ respond(r, "invalid attach specifier"); return; } a = emalloc(sizeof(Aux)); r->fid->aux = a; a->name = s_copy(r->ifcall.uname); r->ofcall.qid = (Qid){mkqid(Droot, 0, 0, 0), 0, QTDIR}; r->fid->qid = r->ofcall.qid; respond(r, nil); } static String * httplogin(void) { String *s=s_new(); Biobuf *b; if((b = wBopen(".httplogin", OREAD)) == nil) goto Return; while(s_read(b, s, Bsize) > 0) ; Bterm(b); Return: return s; } static char* fswalk1(Fid *fid, char *name, Qid *qid) { char *q; int i, isdotdot, n, t; uvlong path; Aux *a; Whist *wh; String *s; isdotdot = strcmp(name, "..")==0; n = strtoul(name, &q, 10); path = fid->qid.path; a = fid->aux; switch(qidtype(path)){ case 0: return "wikifs: bad path in server (bug)"; case Droot: if(isdotdot){ *qid = fid->qid; return nil; } if(strcmp(name, "new")==0){ *qid = (Qid){mkqid(Fnew, 0, 0, 0), 0, 0}; return nil; } if(strcmp(name, "map")==0){ *qid = (Qid){mkqid(Fmap, 0, 0, 0), 0, 0}; return nil; } if((*q!='\0' || (wh=getcurrent(n))==nil) && (wh=getcurrentbyname(name))==nil) return "file does not exist"; *qid = (Qid){mkqid(D1st, wh->n, 0, 0), wh->doc->time, QTDIR}; a->w = wh; return nil; case D1st: if(isdotdot){ *qid = (Qid){mkqid(Droot, 0, 0, 0), 0, QTDIR}; return nil; } /* handle history directories */ if(*q == '\0'){ if((wh = gethistory(qidnum(path))) == nil) return "file does not exist"; for(i=0; indoc; i++) if(wh->doc[i].time == n) break; if(i==wh->ndoc){ closewhist(wh); return "file does not exist"; } closewhist(a->w); a->w = wh; a->n = i; *qid = (Qid){mkqid(D2nd, qidnum(path), i, 0), wh->doc[i].time, QTDIR}; return nil; } /* handle files other than index */ for(i=0; iw); a->w = wh; } *qid = (Qid){mkqid(F1st, qidnum(path), 0, i), a->w->doc->time, 0}; goto Gotfile; } } return "file does not exist"; case D2nd: if(isdotdot){ /* * Can't use a->w[a->ndoc-1] because that * might be a failed write rather than the real one. */ *qid = (Qid){mkqid(D1st, qidnum(path), 0, 0), 0, QTDIR}; if((wh = getcurrent(qidnum(path))) == nil) return "file does not exist"; closewhist(a->w); a->w = wh; a->n = 0; return nil; } for(i=0; i<=Qraw; i++){ if(strcmp(name, filelist[i])==0){ *qid = (Qid){mkqid(F2nd, qidnum(path), qidvers(path), i), a->w->doc->time, 0}; goto Gotfile; } } return "file does not exist"; default: return "bad programming"; } /* not reached */ Gotfile: t = qidtype(qid->path); switch(qidfile(qid->path)){ case Qindexhtml: s = tohtml(a->w, a->w->doc+a->n, t==F1st? Tpage : Toldpage); break; case Qindextxt: s = totext(a->w, a->w->doc+a->n, t==F1st? Tpage : Toldpage); break; case Qraw: s = s_copy(a->w->title); s = s_append(s, "\n"); s = doctext(s, &a->w->doc[a->n]); break; case Qhistoryhtml: s = tohtml(a->w, a->w->doc+a->n, Thistory); break; case Qhistorytxt: s = totext(a->w, a->w->doc+a->n, Thistory); break; case Qdiffhtml: s = tohtml(a->w, a->w->doc+a->n, Tdiff); break; case Qedithtml: s = tohtml(a->w, a->w->doc+a->n, Tedit); break; case Qwerrorhtml: s = tohtml(a->w, a->w->doc+a->n, Twerror); break; case Qwerrortxt: s = totext(a->w, a->w->doc+a->n, Twerror); break; case Qhttplogin: s = httplogin(); break; default: return "internal error"; } a->s = s; return nil; } static void fsopen(Req *r) { int t; uvlong path; Aux *a; Fid *fid; Whist *wh; fid = r->fid; path = fid->qid.path; t = qidtype(fid->qid.path); if((r->ifcall.mode != OREAD && t != Fnew && t != Fmap) || (r->ifcall.mode&ORCLOSE)){ respond(r, "permission denied"); return; } a = fid->aux; switch(t){ case Droot: currentmap(0); rlock(&maplock); a->map = map; incref(map); runlock(&maplock); respond(r, nil); break; case D1st: if((wh = gethistory(qidnum(path))) == nil){ respond(r, "file does not exist"); return; } closewhist(a->w); a->w = wh; a->n = a->w->ndoc-1; r->ofcall.qid.vers = wh->doc[a->n].time; r->fid->qid = r->ofcall.qid; respond(r, nil); break; case D2nd: respond(r, nil); break; case Fnew: a->s = s_copy(""); respond(r, nil); break; case Fmap: case F1st: case F2nd: respond(r, nil); break; default: respond(r, "programmer error"); break; } } static char* fsclone(Fid *old, Fid *new) { Aux *a; a = emalloc(sizeof(*a)); *a = *(Aux*)old->aux; if(a->s) s_incref(a->s); if(a->w) incref(a->w); if(a->map) incref(a->map); if(a->name) s_incref(a->name); new->aux = a; new->qid = old->qid; return nil; } static void fsdestroyfid(Fid *fid) { Aux *a; a = fid->aux; if(a==nil) return; if(a->name) s_free(a->name); if(a->map) closemap(a->map); if(a->s) s_free(a->s); if(a->w) closewhist(a->w); free(a); fid->aux = nil; } static void fillstat(Dir *d, uvlong path, ulong tm, ulong length) { char tmp[32], *p; int type; memset(d, 0, sizeof(Dir)); d->uid = estrdup9p("wiki"); d->gid = estrdup9p("wiki"); switch(qidtype(path)){ case Droot: case D1st: case D2nd: type = QTDIR; break; default: type = 0; break; } d->qid = (Qid){path, tm, type}; d->atime = d->mtime = tm; d->length = length; if(qidfile(path) == Qedithtml) d->atime = d->mtime = time(0); switch(qidtype(path)){ case Droot: d->name = estrdup("/"); d->mode = DMDIR|0555; break; case D1st: d->name = numtoname(qidnum(path)); if(d->name == nil) d->name = estrdup(""); for(p=d->name; *p; p++) if(*p==' ') *p = '_'; d->mode = DMDIR|0555; break; case D2nd: snprint(tmp, sizeof tmp, "%lud", tm); d->name = estrdup(tmp); d->mode = DMDIR|0555; break; case Fmap: d->name = estrdup("map"); d->mode = 0666; break; case Fnew: d->name = estrdup("new"); d->mode = 0666; break; case F1st: d->name = estrdup(filelist[qidfile(path)]); d->mode = 0444; break; case F2nd: d->name = estrdup(filelist[qidfile(path)]); d->mode = 0444; break; default: print("bad qid path 0x%.8llux\n", path); break; } } static void fsstat(Req *r) { Aux *a; Fid *fid; ulong t; t = 0; fid = r->fid; if((a = fid->aux) && a->w) t = a->w->doc[a->n].time; fillstat(&r->d, fid->qid.path, t, a->s ? s_len(a->s) : 0); respond(r, nil); } typedef struct Bogus Bogus; struct Bogus { uvlong path; Aux *a; }; static int rootgen(int i, Dir *d, void *aux) { Aux *a; Bogus *b; b = aux; a = b->a; switch(i){ case 0: /* new */ fillstat(d, mkqid(Fnew, 0, 0, 0), a->map->t, 0); return 0; case 1: /* map */ fillstat(d, mkqid(Fmap, 0, 0, 0), a->map->t, 0); return 0; default: /* first-level directory */ i -= 2; if(i >= a->map->nel) return -1; fillstat(d, mkqid(D1st, a->map->el[i].n, 0, 0), a->map->t, 0); return 0; } } static int firstgen(int i, Dir *d, void *aux) { ulong t; Bogus *b; int num; Aux *a; b = aux; num = qidnum(b->path); a = b->a; t = a->w->doc[a->n].time; if(i < Nfile){ /* file in first-level directory */ fillstat(d, mkqid(F1st, num, 0, i), t, 0); return 0; } i -= Nfile; if(i < a->w->ndoc){ /* second-level (history) directory */ fillstat(d, mkqid(D2nd, num, i, 0), a->w->doc[i].time, 0); return 0; } //i -= a->w->ndoc; return -1; } static int secondgen(int i, Dir *d, void *aux) { Bogus *b; uvlong path; Aux *a; b = aux; path = b->path; a = b->a; if(i <= Qraw){ /* index.html, index.txt, raw */ fillstat(d, mkqid(F2nd, qidnum(path), qidvers(path), i), a->w->doc[a->n].time, 0); return 0; } //i -= Qraw; return -1; } static void fsread(Req *r) { char *t, *s; uvlong path; Aux *a; Bogus b; a = r->fid->aux; path = r->fid->qid.path; b.a = a; b.path = path; switch(qidtype(path)){ default: respond(r, "cannot happen (bad qid)"); return; case Droot: if(a == nil || a->map == nil){ respond(r, "cannot happen (no map)"); return; } dirread9p(r, rootgen, &b); respond(r, nil); return; case D1st: if(a == nil || a->w == nil){ respond(r, "cannot happen (no wh)"); return; } dirread9p(r, firstgen, &b); respond(r, nil); return; case D2nd: dirread9p(r, secondgen, &b); respond(r, nil); return; case Fnew: if(a->s){ respond(r, "protocol botch"); return; } /* fall through */ case Fmap: t = numtoname(a->n); if(t == nil){ respond(r, "unknown name"); return; } for(s=t; *s; s++) if(*s == ' ') *s = '_'; readstr(r, t); free(t); respond(r, nil); return; case F1st: case F2nd: if(a == nil || a->s == nil){ respond(r, "cannot happen (no s)"); return; } readbuf(r, s_to_c(a->s), s_len(a->s)); respond(r, nil); return; } } typedef struct Sread Sread; struct Sread { char *rp; }; static char* Srdline(void *v, int c) { char *p, *rv; Sread *s; s = v; if(s->rp == nil) rv = nil; else if(p = strchr(s->rp, c)){ *p = '\0'; rv = s->rp; s->rp = p+1; }else{ rv = s->rp; s->rp = nil; } return rv; } static void responderrstr(Req *r) { char buf[ERRMAX]; rerrstr(buf, sizeof buf); if(buf[0] == '\0') strcpy(buf, "unknown error"); respond(r, buf); } static void fswrite(Req *r) { char *author, *comment, *net, *err, *p, *title, tmp[40]; int rv, n; ulong t; Aux *a; Fid *fid; Sread s; String *stmp; Whist *w; fid = r->fid; a = fid->aux; switch(qidtype(fid->qid.path)){ case Fmap: stmp = s_nappend(s_reset(nil), r->ifcall.data, r->ifcall.count); a->n = nametonum(s_to_c(stmp)); s_free(stmp); if(a->n < 0) respond(r, "name not found"); else respond(r, nil); return; case Fnew: break; default: respond(r, "cannot happen"); return; } if(a->s == nil){ respond(r, "protocol botch"); return; } if(r->ifcall.count==0){ /* do final processing */ s.rp = s_to_c(a->s); w = nil; err = "bad format"; if((title = Srdline(&s, '\n')) == nil){ Error: if(w) closewhist(w); s_free(a->s); a->s = nil; respond(r, err); return; } w = emalloc(sizeof(*w)); incref(w); w->title = estrdup(title); t = 0; author = estrdup(s_to_c(a->name)); comment = nil; while(s.rp && *s.rp && *s.rp != '\n'){ p = Srdline(&s, '\n'); assert(p != nil); switch(p[0]){ case 'A': free(author); author = estrdup(p+1); break; case 'D': t = strtoul(p+1, &p, 10); if(*p != '\0') goto Error; break; case 'C': free(comment); comment = estrdup(p+1); break; } } w->doc = emalloc(sizeof(w->doc[0])); w->doc->time = time(0); w->doc->comment = comment; if(net = r->pool->srv->aux){ p = emalloc(strlen(author)+10+strlen(net)); strcpy(p, author); strcat(p, " ("); strcat(p, net); strcat(p, ")"); free(author); author = p; } w->doc->author = author; if((w->doc->wtxt = Brdpage(Srdline, &s)) == nil){ err = "empty document"; goto Error; } w->ndoc = 1; if((n = allocnum(w->title, 0)) < 0) goto Error; sprint(tmp, "D%lud\n", w->doc->time); a->s = s_reset(a->s); a->s = doctext(a->s, w->doc); rv = writepage(n, t, a->s, w->title); s_free(a->s); a->s = nil; a->n = n; closewhist(w); if(rv < 0) responderrstr(r); else respond(r, nil); return; } if(s_len(a->s)+r->ifcall.count > Maxfile){ respond(r, "file too large"); s_free(a->s); a->s = nil; return; } a->s = s_nappend(a->s, r->ifcall.data, r->ifcall.count); r->ofcall.count = r->ifcall.count; respond(r, nil); } Srv wikisrv = { .attach= fsattach, .destroyfid= fsdestroyfid, .clone= fsclone, .walk1= fswalk1, .open= fsopen, .read= fsread, .write= fswrite, .stat= fsstat, }; void usage(void) { fprint(2, "usage: wikifs [-D] [-a addr]... [-m mtpt] [-p perm] [-s service] dir\n"); exits("usage"); } void main(int argc, char **argv) { char **addr; int i, naddr; char *buf; char *service, *mtpt; ulong perm; Dir d, *dp; Srv *s; naddr = 0; addr = nil; perm = 0; service = nil; mtpt = "/mnt/wiki"; ARGBEGIN{ case 'D': chatty9p++; break; case 'a': if(naddr%8 == 0) addr = erealloc(addr, (naddr+8)*sizeof(addr[0])); addr[naddr++] = EARGF(usage()); break; case 'm': mtpt = EARGF(usage()); break; case 'M': mtpt = nil; break; case 'p': perm = strtoul(EARGF(usage()), nil, 8); break; case 's': service = EARGF(usage()); break; default: usage(); break; }ARGEND if(argc != 1) usage(); if((dp = dirstat(argv[0])) == nil) sysfatal("dirstat %s: %r", argv[0]); if((dp->mode&DMDIR) == 0) sysfatal("%s: not a directory", argv[0]); free(dp); wikidir = argv[0]; currentmap(0); for(i=0; i