#include <u.h>
#include <libc.h>
#include <bio.h>
#include <String.h>
#include <thread.h>
#include "wiki.h"
#include <auth.h>
#include <fcall.h>
#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; i<wh->ndoc; 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; i<nelem(filelist); i++){
if(strcmp(name, filelist[i])==0){
if(needhist[i]){
if((wh = gethistory(qidnum(path))) == nil)
return "file does not exist";
closewhist(a->w);
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("<dead>");
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<naddr; i++)
listensrv(&wikisrv, addr[i]);
s = emalloc(sizeof *s);
*s = wikisrv;
postmountsrv(s, service, mtpt, MREPL|MCREATE);
if(perm){
buf = emalloc9p(5+strlen(service)+1);
strcpy(buf, "/srv/");
strcat(buf, service);
nulldir(&d);
d.mode = perm;
if(dirwstat(buf, &d) < 0)
fprint(2, "wstat: %r\n");
free(buf);
}
exits(nil);
}
|