#include #include #include #include #include <9p.h> static void fsattach(Req *); static char* fsclone(Fid *, Fid *); static char* fswalk(Fid *, char *, Qid *); static void fsopen(Req *); static void fscreate(Req *); static void fsremove(Req *); static void fsread(Req *); static void fswrite(Req *); static void fsstat(Req *); static void fswstat(Req *); static void fsflush(Req *); static void fsclunk(Fid *); static void fsend(Srv *); enum { Qroot = 0, NQIDS, STACK = 2048, }; int nextqid = NQIDS; int ngames = 0; typedef struct Qinfo Qinfo; typedef struct GRef GRef; typedef struct Aux Aux; typedef struct Move Move; typedef struct Player Player; typedef struct Game Game; typedef struct Reqlist Reqlist; typedef struct Popmsg Popmsg; struct Qinfo{ Qid qid, pqid; char *name; char *uid; }; struct GRef{ GRef *next; GRef *prev; ulong fid; }; struct Aux{ char *data; Game *g; Player *p; Player *opp; }; struct Move{ Move *next; char *data; }; struct Player{ Move *mtop, *mlast; Move *mcur; int dirty; Qinfo; char *gid; /* modifiable only for player files */ }; struct Game{ Game *next; Game *prev; Player *players[2]; /* black or white */ Qinfo; GRef *ref; int nrefs; int rm; }; struct Reqlist{ Reqlist *next; Reqlist *prev; Req *r; }; struct Popmsg{ int op; void *data; char *rdata; }; Reqlist *rtop, *rlast; Game *groot; Channel *reqchan; int gofd; /* srvfd of main service */ Srv gosrv = { .attach= fsattach, .clone= fsclone, .walk1= fswalk, .open= fsopen, .create= fscreate, .remove= fsremove, .read= fsread, .write= fswrite, .stat= fsstat, .wstat= fswstat, .flush= fsflush, .destroyfid= fsclunk, .end= fsend, /* .auth= fsauth, */ }; static void addgame(char *name, char *uid) { Player **p; int i; if(groot == nil){ groot= emalloc9p(sizeof(*groot)); groot->next = nil; groot->prev = nil; } else { groot->prev = emalloc9p(sizeof(*groot->prev)); groot->prev->next = groot; groot = groot->prev; groot->prev = nil; } groot->qid = (Qid){nextqid, 0, QTDIR}; groot->name = estrdup9p(name); groot->uid = estrdup9p(uid); groot->nrefs = 0; ngames++; nextqid++; p = groot->players; for(i=0; i < 2; i++){ p[i] = emalloc9p(sizeof(*p[i])); p[i]->qid = (Qid){nextqid, 0, QTFILE}; p[i]->pqid = groot->qid; p[i]->uid = estrdup9p(uid); p[i]->gid = estrdup9p(uid); if(!i){ p[i]->name = estrdup9p("W"); p[i]->dirty = 1; /* B goes first */ } else p[i]->name = estrdup9p("B"); nextqid++; } } static void gincref(Game *g, ulong fid) { if(!g->ref){ g->ref = emalloc9p(sizeof *g->ref); g->ref->next = nil; g->ref->prev = nil; } else { g->ref->prev = emalloc9p(sizeof *g->ref->next); g->ref->prev->next = g->ref; g->ref = g->ref->prev; g->ref->prev = nil; } g->ref->fid = fid; g->nrefs++; } static void gdecref(Game *g, ulong fid) { GRef *top; if(!g->nrefs) return; top = g->ref; for(; g->ref; g->ref=g->ref->next){ if(g->ref->fid == fid) break; } if(!g->ref){ g->ref = top; return; } if(!g->ref->prev){ if(!g->ref->next){ free(g->ref); g->ref = nil; g->nrefs = 0; return; } g->ref = g->ref->next; free(g->ref->prev); g->ref->prev = nil; g->nrefs--; return; } if(!g->ref->next){ g->ref = g->ref->prev; free(g->ref->next); g->ref->next = nil; g->ref = top; g->nrefs--; return; } g->ref->prev->next = g->ref->next; g->ref->next->prev = g->ref->prev; free(g->ref); g->ref = top; g->nrefs--; } static void rmmoves(Player *p) { Move *m, *mtop; mtop = p->mtop; for(m=mtop; m != nil;){ mtop = m->next; free(m->data); free(m); m = mtop; } } static void rmplayers(Game *g) { Player **p; int i; p = g->players; for(i=0; i<2; i++){ rmmoves(p[i]); free(p[i]->uid); free(p[i]->gid); free(p[i]->name); free(p[i]); p[i] = nil; } } static void rmgame(Game *g) { ngames--; rmplayers(g); free(g->name); g->name = nil; free(g->uid); g->uid = nil; if(g == groot){ if(!g->next){ groot = nil; return; } groot = g->next; groot->prev = nil; return; } if(!g->next){ g->prev->next = nil; return; } g->prev->next = g->next; g->next->prev = g->prev; } Game * findgame(char *name, int path) { Game *gl; if(name==nil && path < NQIDS) return nil; for(gl=groot; gl != nil; gl=gl->next){ if(name != nil) if(strcmp(gl->name, name)==0) return gl; if(path >= NQIDS) if(gl->qid.path == path) return gl; } return nil; } static void addmove(Player *p, char *s) { if(p->mtop == nil){ p->mtop = emalloc9p(sizeof(*p->mtop)); p->mlast = p->mtop; } else { p->mlast->next = emalloc9p(sizeof(*p->mlast->next)); p->mlast = p->mlast->next; p->mlast->next = nil; } p->mlast->data = estrdup9p(s); } static void reply(Req *r, char *data) { if(r == nil) /* unusual case where */ return; /* write flushes early */ if(data == nil){ respond(r, "interrupted"); return; } r->ofcall.data = data; r->ofcall.count = strlen(data); respond(r, nil); } static void addreq(Req *r) { if(rtop == nil){ rtop = emalloc9p(sizeof(*rtop)); rtop->r = r; rlast = rtop; rlast->next = nil; rlast->prev = nil; return; } rlast->next = emalloc9p(sizeof(*rlast->next)); rlast->next->prev = rlast; rlast = rlast->next; rlast->r = r; rlast->next = nil; } static void remreq(Reqlist **rl) { Reqlist *tmp; if(rl == nil) return; else if(*rl == nil) return; if(*rl==rtop){ if(rtop == rlast){ free(rtop); *rl = rtop = rlast = nil; return; } rtop=rtop->next; free(rtop->prev); rtop->prev = nil; *rl = rtop; return; } if(*rl==rlast){ *rl = (*rl)->prev; free(rlast); (*rl)->next = nil; rlast = *rl; return; } (*rl)->prev->next = (*rl)->next; (*rl)->next->prev = (*rl)->prev; tmp = *rl; *rl = (*rl)->next; free(tmp); } static void popreqsop(Popmsg msg) { Reqlist *rl; Req *r; Qid *qh; switch(msg.op){ case 0: /* reply to all reads */ while(rtop!=nil){ reply(rtop->r, msg.rdata); remreq(&rtop); } break; case 1: /* reply to one specified read */ if((r = msg.data) == nil) break; for(rl=rtop; rl!=nil; rl=rl->next){ if(rl->r == r){ reply(rl->r, msg.rdata); remreq(&rl); break; } } break; case 2: /* reply to all reads on one file */ if((qh = msg.data) == nil) break; for(rl=rtop; rl!=nil;){ if(rl->r != nil && qh->path == rl->r->fid->qid.path){ reply(rl->r, msg.rdata); remreq(&rl); continue; } rl = rl->next; } free(msg.rdata); break; } } static void popreqsproc(void *) { Popmsg msg; threadsetname("popreads"); /* indeed, he does */ while(recv(reqchan, &msg)) popreqsop(msg); } static void fsattach(Req *r) { Aux *a; if(!r->fid->uid || strcmp(r->fid->uid, "")==0){ respond(r, "must specify user name"); return; } a = emalloc9p(sizeof *a); a->g = nil; a->p = nil; r->fid->aux = a; r->ofcall.qid = (Qid){Qroot, 0, QTDIR}; r->fid->qid = r->ofcall.qid; respond(r, nil); } static char* fsclone(Fid *ofid, Fid *fid) { Aux *a; a = emalloc9p(sizeof *a); *a = *(Aux*)ofid->aux; fid->aux = a; return nil; } static char* fswalk(Fid *fid, char *name, Qid *qid) { Aux *a; Game *gl; int path; a = fid->aux; path = (int)fid->qid.path; /* without integral switches */ if(path == Qroot){ /* this is just shabby */ if(strcmp(name, "..")==0) return nil; else if((gl = findgame(name, path)) != nil){ fid->qid = gl->qid; a->g = gl; gincref(a->g, fid->fid); } else return "path not found"; } else if(a->g && !a->g->rm){ /* switch style */ if(path == a->g->qid.path){ if(strcmp(name, "W")==0){ a->p = a->g->players[0]; a->opp = a->g->players[1]; fid->qid = a->g->players[0]->qid; } else if(strcmp(name, "B")==0){ a->p = a->g->players[1]; a->opp = a->g->players[0]; fid->qid = a->g->players[1]->qid; } else if(strcmp(name, "..")==0) fid->qid = (Qid){Qroot, 0, QTDIR}; else return "path not found"; } else if(a->p && path == a->p->qid.path){ if(strcmp(name, "..")==0) fid->qid = a->p->pqid; else return "path not found"; } else return "path not found"; } else return "path not found"; *qid = fid->qid; return nil; } static void fsopen(Req *r) { Aux *a; a = r->fid->aux; if(a->p != nil && r->fid->qid.path == a->p->qid.path){ switch(r->ifcall.mode&OEXEC){ /* disregard higher bits */ case OREAD: a->p->mcur = a->p->mtop; break; case ORDWR: a->p->mcur = a->p->mtop; /* follow through */ case OWRITE: /* save fswrite the drama */ if(strcmp(a->p->gid, r->fid->uid)){ /* chmod 464 */ respond(r, "access permission denied"); return; } break; case OEXEC: respond(r, "access permission denied"); return; } } r->ofcall.qid = r->fid->qid; r->ofcall.iounit = 10; respond(r, nil); } static void fscreate(Req *r) { if(!r->fid->uid){ respond(r, "guest permission denied"); return; } if(r->fid->qid.path != Qroot){ respond(r, "create prohibited"); return; } if(r->ifcall.perm&DMDIR){ if(!findgame(r->ifcall.name, -1)){ addgame(r->ifcall.name, r->fid->uid); r->fid->qid = groot->qid; r->ofcall.qid = r->fid->qid; } respond(r, nil); return; } respond(r, "create prohibited"); /* else, rm condition */ } static void fsremove(Req *r) { Aux *a; int path; a = r->fid->aux; path = r->fid->qid.path; if(a->g && a->g->qid.path == path && strcmp(a->g->uid, r->fid->uid)==0) { if(a->g->nrefs){ a->g->rm = 1; rmgame(a->g); respond(r, nil); return; } rmgame(a->g); free(a->g); respond(r, nil); return; } respond(r, "only removal of directories is permitted"); } int rootgen(int off, Dir *d, Game **game) { Game *g; if(ngames <= off) return -1; g = *game; memset(d, 0, sizeof *d); d->atime = d->mtime = time(0); if(g->uid){ d->uid = estrdup9p(g->uid); d->gid = estrdup9p(g->uid); }else{ d->uid = estrdup9p("igo"); d->gid = estrdup9p("igo"); } d->muid = estrdup9p(""); d->name = estrdup9p(g->name); d->mode = DMDIR|0755; d->qid = g->qid; *game = (*game)->next; return 0; } int gamegen(int off, Dir *d, Aux *a) { if(off > 1) return -1; memset(d, 0, sizeof *d); d->atime = d->mtime = time(0); if(a->g->players[off]->uid) d->uid = estrdup9p(a->g->players[off]->uid); else d->uid = estrdup9p("igo"); if(a->g->players[off]->gid) d->gid = estrdup9p(a->g->players[off]->gid); else d->gid = estrdup9p("igo"); d->muid = estrdup9p(""); d->name = estrdup9p(a->g->players[off]->name); d->mode = DMAPPEND|0464; d->qid.path = a->g->players[off]->qid.path; d->qid.vers = 0; d->qid.type = QTFILE; return 0; } static void fsread(Req *r) { Aux *a; Game *g; Move *mv; int path; path = r->fid->qid.path; a = r->fid->aux; if(a->p && path == a->p->qid.path){ mv = a->p->mcur; if(mv != nil && mv->data != nil) { reply(r, mv->data); a->p->mcur = mv->next; return; } addreq(r); return; } if(path == Qroot){ g = groot; dirread9p(r, rootgen, &g); } else dirread9p(r, gamegen, a); respond(r, nil); } static void fswrite(Req *r) { Aux *a; int path; Qid *qh; Popmsg hreq; int count; path = r->fid->qid.path; qh = &(r->fid->qid); count = r->ifcall.count; a = r->fid->aux; if(a->p && a->opp && path == a->p->qid.path) { if(a->p->dirty){ respond(r, "let opponent move"); return; } a->data = emalloc9p(count+1); strncpy(a->data, r->ifcall.data, count); a->data[count] = '\0'; /* in case of garbled data */ r->ofcall.count = strlen(a->data); a->p->dirty = 1; a->opp->dirty = 0; addmove(a->p, a->data); /* β: in case Tclunk never comes */ respond(r, nil); hreq = (Popmsg){2, qh, strdup(a->data)}; send(reqchan, &hreq); free(a->data); /* see β */ a->data = nil; return; } respond(r, nil); } static void fsstat(Req *r) { Aux *a; int path; char *uid = nil; char *gid = nil; Dir *d; d = &(r->d); memset(d, 0, sizeof *d); path = r->fid->qid.path; d->qid = r->fid->qid; a = r->fid->aux; if(path == Qroot){ d->name = estrdup9p("/"); d->mode = DMDIR|0777; } else if(a->g && !a->g->rm){ /* switch style */ if(path == a->g->qid.path){ d->name = estrdup9p(a->g->name); d->mode = DMDIR|0755; if(a->g->uid) uid = estrdup9p(a->g->uid); gid = estrdup9p(a->g->uid); } /* with completely unique */ else if(path == a->p->qid.path){ /* QIDs, only one of these */ d->name = estrdup9p(a->p->name); /* is possible */ if(a->p->gid) gid = estrdup9p(a->p->gid); if(a->p->uid){ uid = estrdup9p(a->p->uid); d->mode = DMAPPEND|0464; } else d->mode = DMAPPEND|0444; } } else { respond(r, "path not found"); return; } if(!uid) uid = estrdup9p("igo"); if(!gid) gid = estrdup9p("igo"); d->atime = d->mtime = time(0); d->uid = estrdup9p(uid); d->gid = estrdup9p(gid); d->muid = estrdup9p(""); free(uid); free(gid); respond(r, nil); } static void fswstat(Req *r) { Aux *a; int path; a = r->fid->aux; path = r->fid->qid.path; if(a->g && a->g->rm){ respond(r, "path not found"); /* homogeny of messages */ return; } if(a->p && a->p->qid.path == path && strcmp(a->p->uid, r->fid->uid)==0 && r->d.gid && r->d.gid[0]) { if(a->p->gid && a->p->gid[0]) free(a->p->gid); a->p->gid = estrdup9p(r->d.gid); respond(r, nil); return; } respond(r, "wstat prohibited"); } static void fsflush(Req *r) { Req *or; int rtype, ftype; Popmsg hreq; or = r->oldreq; rtype = or->ifcall.type; ftype = or->fid->qid.type; if(ftype == QTFILE && rtype == Tread){ hreq = (Popmsg){1, or, nil}; send(reqchan, &hreq); } else /* flush all else */ reply(or, nil); respond(r, nil); } static void fsclunk(Fid *f) { Aux *a; a = f->aux; if(a){ if(a->g && a->g->nrefs){ gdecref(a->g, f->fid); } if(a->g && a->g->rm && !a->g->nrefs){ free(a->g); if(a->data) free(a->data); free(a); return; } if(a->data) free(a->data); free(a); } } /* if main service, then * reply to all reads and * close all threads */ static void fsend(Srv *s) { Popmsg hreq; if(s->srvfd != gofd) return; if(rtop != nil){ hreq = (Popmsg){0, nil, nil}; send(reqchan, &hreq); } threadexitsall(nil); } void usage(void) { fprint(2, "usage: %s [-D] [-a addr] [-m mtpt] [-s srv]\n", argv0); exits("usage"); } void threadmain(int argc, char **argv) { char *addr, *service; char *mtpt; Srv *s; addr = nil; service = nil; mtpt = "/n/gofs"; ARGBEGIN{ case 'D': chatty9p++; break; case 'a': addr = EARGF(usage()); break; case 'm': mtpt = EARGF(usage()); break; case 's': service = EARGF(usage()); break; default: usage(); break; }ARGEND reqchan = chancreate(sizeof(Popmsg), 0); procrfork(popreqsproc, nil, STACK, RFNAMEG); if(addr) threadlistensrv(&gosrv, addr); s = emalloc9p(sizeof *s); *s = gosrv; threadpostmountsrv(s, service, mtpt, MREPL|MCREATE); gofd = s->srvfd; threadexits(0); }