#include #include #include #include #include #include <9p.h> enum{ROOT, CTL, QUOTA = 8, MAXP = 4, BUF = 8192}; typedef struct Game Game; typedef struct Player Player; typedef struct Reader Reader; struct Game{ Ref; char data[BUF], *turn, *start, *end; ulong time; uvlong path; Reader *blocking; Game *next; }*g₀; struct Player{char *uid, games, online; Player *next;}*p₀; struct Reader{Req *r; Reader *next;}*lounge; uvlong takeanumber = CTL; char readme[] = "Filesystem for use with the games at:\n" "; srv runjimmyrunrunyoufuckerrun.com jimmy /n/jimmy\n" "; ls /n/jimmy/src/games\n" "or: http://runjimmyrunrunyoufuckerrun.com/src/games\n" "or: /n/griddisk/umbraticus/src/games\n" "\nBasic Usage:\n" "; cat ctl & while() echo -n `{read} >> ctl\t# hang out in the lounge\n" "; ls -l | grep $user\t# list your games\n" "; games/chess -9 chess~glenda~umbraticus\t# play or observe a game\n" "\nOther Details:\n" "• Play is correspondence-style with no time limits.\n" "• Games are begun by creating a new file.\n" "→ The file must be named like gametype~player1~player2~…~playerN.\n" "→ The list of players must include the creator's uid.\n" "→ There is a limit on the number of games a player can be active in.\n" "• Reading a file yields the game's movetext so far, then blocks.\n" "→ Subsequent reads yield any incoming chat messages or new moves.\n" "• Writing to a file either sends a move or a chat message.\n" "→ The owner is whose turn it is: only this uid may send a move.\n" "→ Moves end with a space (leave this to the programs).\n" "• Files disappear once the game is over.\n" "→ In-game commands “resign!” & “draw!” are available to player on move.\n" "• This file (ctl) acts as a players' lounge: see usage suggestion above.\n" "→ Reads subsequent to this message receive chat and status messages.\n" "→ Writes send a chat message to anyone else reading from ctl.\n" "\n\nPlayers Currently Connected:\n"; void tell(Reader **queue, char *s1, char *s2, char *s3, int s3len){ Reader *d; char *s; int n; n = strlen(s1) + strlen(s2) + s3len; s = emalloc9p(n + (queue == &lounge)); strcpy(s, s1); strcat(s, s2); memmove(s + n - s3len, s3, s3len); if(queue == &lounge) s[n++] = '\n'; while(*queue != nil){ d = *queue; *queue = d->next; d->r->ofcall.count = n < d->r->ifcall.count ? n : d->r->ifcall.count; memmove(d->r->ofcall.data, s, d->r->ofcall.count); respond(d->r, nil); free(d); } free(s); } int dirgen(int n, Dir *d, void *aux){ Game *g; char *s; memset(d, 0, sizeof(*d)); g = aux; if(aux == nil){ if(n <= 0){ if(n < 0){ d->name = estrdup9p("/"); d->qid = (Qid){ROOT, 0, QTDIR}; d->mode = 0555; } else{ d->name = estrdup9p("ctl"); d->qid = (Qid){CTL, 0, QTFILE}; d->mode = 0666; d->length = sizeof readme; } d->atime = d->mtime = time(nil); d->uid = estrdup9p("gamesfs"); d->gid = estrdup9p("gamesfs"); d->muid = estrdup9p("gamesfs"); return 0; } for(g = g₀; g != nil && --n; g = g->next); if(g == nil) return -1; } d->qid = (Qid){g->path, 0, QTAPPEND}; d->mtime = d->atime = g->time; d->name = estrdup9p(g->data); if(s = strchr(g->turn, '~')) *s = 0; d->uid = estrdup9p(g->turn); if(s != nil) *s = '~'; d->gid = estrdup9p("gamesfs"); d->muid = estrdup9p("gamesfs"); d->mode = 0666; d->length = g->end - g->start; return 0; } void deref(Game *d){ Game *g; if(decref(d)) return; if(d == g₀) g₀ = d->next; else{ for(g = g₀; g->next != d; g = g->next); g->next = d->next; } free(d); } void fsattach(Req *r){ Player *p; if(r->fid->uid == nil || *r->fid->uid == '\0' || strchr(r->fid->uid, '~') || !strcmp(r->fid->uid, "gameover")) {respond(r, "Sorry, bad user name!"); return;} r->ofcall.qid = (Qid){ROOT, 0, QTDIR}; r->fid->qid = r->ofcall.qid; r->fid->aux = &takeanumber; /* so we can register log off */ tell(&lounge, "† ", r->fid->uid, " attached", 9); for(p = p₀; p != nil && strcmp(p->uid, r->fid->uid); p = p->next); if(p == nil){ p = emalloc9p(sizeof(Player)); p->uid = estrdup9p(r->fid->uid); p->next = p₀; p₀ = p; } /* who cares? else if(p->online && strcmp(r->fid->uid, "glenda")) {respond(r, "Sorry, that user is already here.\n" "We do accept multiple glendas..."); return;}*/ p->online++; respond(r, nil); } char* fswalk1(Fid *f, char *name, Qid *qid){ Game *g; if(f->qid.path != ROOT) return "must walk from root"; if(!strcmp(name, "..")){ *qid = f->qid; return nil; } if(!strcmp(name, "ctl")){ *qid = (Qid){CTL, 0, QTFILE}; f->qid = *qid; f->aux = &lounge; return nil; } for(g = g₀; g != nil && strcmp(name, g->data); g = g->next); if(g == nil || !strcmp(g->turn, "gameover")) return "no such file"; incref(g); *qid = (Qid){g->path, 0, QTAPPEND}; f->qid = *qid; f->aux = g; return nil; } void fsopen(Req *r){ Game *g; if(r->fid->qid.path > CTL){ g = r->fid->aux; if(g == nil || !strcmp(g->turn, "gameover")) {respond(r, "game disappeared"); return;} if((r->ifcall.mode & OEXEC) == ORDWR) tell(&g->blocking, "† ", r->fid->uid, " joined", 7); } respond(r, nil); } void fsread(Req *r){ Reader *block; Player *p; Game *g; char *s; if(r->fid->qid.path == ROOT){ dirread9p(r, dirgen, nil); respond(r, nil); return; } if(r->fid->qid.path == CTL){ if(r->ifcall.offset < sizeof(readme)){ r->ofcall.count = sizeof(readme) - r->ifcall.offset; if(r->ofcall.count > r->ifcall.count) r->ofcall.count = r->ifcall.count; memmove(r->ofcall.data + r->ifcall.offset, readme, r->ofcall.count); respond(r, nil); return; } if(r->ifcall.offset == sizeof(readme)){ r->ofcall.count = 0; s = r->ofcall.data; for(p = p₀; p != nil; p = p->next) if(p->online) s = seprint(s, r->ofcall.data + r->ifcall.count, "%s\n", p->uid); r->ofcall.count = s - r->ofcall.data; respond(r, nil); return; } block = emalloc9p(sizeof(Reader)); block->r = r; block->next = lounge; lounge = block; return; } g = r->fid->aux; if(g == nil) {respond(r, "game disappeared"); return;} if(r->ifcall.offset){ block = emalloc9p(sizeof(Reader)); block->r = r; block->next = g->blocking; g->blocking = block; return; } r->ofcall.count = g->end - g->start; if(r->ofcall.count == 0) {respond(r, nil); return;} if(r->ofcall.count > r->ifcall.count) {respond(r, "movetext can't fit in one read"); return;} memmove(r->ofcall.data, g->start, r->ofcall.count); respond(r, nil); } void fswrite(Req *r){ Player *p; Game *g; char *s, *e; if(r->fid->qid.path == ROOT){respond(r, "permission denied"); return;} if(r->fid->qid.path == CTL){ tell(&lounge, r->fid->uid, " → ", r->ifcall.data, r->ifcall.count); respond(r, nil); return; } g = r->fid->aux; if(g == nil) {respond(r, "game over, man"); return;} r->ofcall.count = r->ifcall.count; if(g->end == g->start){ if(r->ifcall.count >= BUF){respond(r, "steady on!"); return;} g->end += r->ifcall.count; memmove(g->start, r->ifcall.data, r->ifcall.count); respond(r, nil); return; } if(!r->ifcall.count || r->ifcall.data[r->ifcall.count - 1] != ' ' && r->ifcall.data[r->ifcall.count - 1] != '\n'){ tell(&g->blocking, r->fid->uid, " → ", r->ifcall.data, r->ifcall.count); respond(r, nil); return; } if(g->end - g->data + r->ifcall.count >= BUF) {respond(r, "steady on!"); return;} if(s = strchr(g->turn, '~')) *s = 0; if(strcmp(g->turn, r->fid->uid)){ if(s != nil) *s = '~'; respond(r, "not your turn"); return; } if(s != nil){ *s = '~'; g->turn = s + 1; } else g->turn = strchr(g->data, '~') + 1; memmove(g->end, r->ifcall.data, r->ifcall.count); g->end += r->ifcall.count; if(g->end[-1] == '\n'){ g->turn = "gameover"; for(s = strchr(g->data, '~') + 1;;){ if(e = strchr(s, '~')) *e = 0; for(p = p₀; p != nil; p = p->next) if(!strcmp(p->uid, s)){ p->games--; break; } if(e == nil) break; *e = '~'; s = e + 1; } tell(&lounge, r->fid->uid, " ended game ", g->data, strlen(g->data)); deref(g); } g->time = time(nil); tell(&g->blocking, "", "", r->ifcall.data, r->ifcall.count); respond(r, nil); } void fscreate(Req *r){ Player *p; Game *g; char *s, *e; int n; if(r->fid->qid.path != ROOT){respond(r, "must create from root"); return;} s = r->ifcall.name; if(*s++ == '~') {respond(r, "filename not formatted correctly"); return;} for(n = 0; *s; s++) if(*s == '~' && ++n && (s[1] == 0 || s[1] == '~')) {respond(r, "empty player string"); return;} if(n < 2 || n > MAXP) {respond(r, "too many/few in player list"); return;} for(s = strchr(r->ifcall.name, '~') + 1;;){ if(e = strchr(s, '~')) *e = 0; if(!strcmp(s, r->fid->uid)) n = 0; for(p = p₀; p != nil; p = p->next) if(!strcmp(p->uid, s)){ if(p->games < QUOTA) break; if(e != nil) *e = '~'; respond(r, "player's game quota met"); return; } if(e == nil) break; *e = '~'; s = e + 1; } if(n){respond(r, "player list must include your own uid"); return;} for(s = strchr(r->ifcall.name, '~') + 1;;){ if(e = strchr(s, '~')) *e = 0; for(p = p₀; p != nil && strcmp(p->uid, s); p = p->next); if(p == nil){ p = emalloc9p(sizeof(Player)); p->uid = estrdup9p(s); p->next = p₀; p₀ = p; } p->games++; if(e == nil) break; *e = '~'; s = e + 1; } g = emalloc9p(sizeof(Game)); g->ref = 2; g->start = strecpy(g->data, g->data + BUF, r->ifcall.name) + 1; g->end = g->start; g->turn = strchr(g->data, '~') + 1; g->time = time(nil); g->path = ++takeanumber; g->next = g₀; r->fid->aux = g₀ = g; r->fid->qid = (Qid){g->path, 0, QTAPPEND}; r->fid->omode = r->ifcall.mode; r->ofcall.qid = r->fid->qid; respond(r, nil); tell(&lounge, r->fid->uid, " started a new game: ", g->data, g->start - g->data - 1); } void fsflush(Req *r){ Reader **queue, *beep, *boop; if(r->oldreq->fid->qid.path >= CTL){ queue = r->oldreq->fid->qid.path == CTL ? &lounge : &((Game*)r->oldreq->fid->aux)->blocking; beep = *queue; if(beep->r == r->oldreq){ *queue = beep->next; free(beep); } else{ while(beep->next != nil && beep->next->r != r->oldreq) beep = beep->next; if(beep->next != nil){ boop = beep->next; beep->next = boop->next; free(boop); } } } respond(r->oldreq, "interrupted"); respond(r, nil); } void fsstat(Req *r){ if(r->fid->qid.path > CTL) dirgen(0, &r->d, r->fid->aux); else dirgen(r->fid->qid.path - 1, &r->d, nil); respond(r, nil); } void fsdestroyfid(Fid *f){ Player *p; Game *g; if(f->aux == &lounge) f->aux = nil; else if(f->aux == &takeanumber){ f->aux = nil; tell(&lounge, "† ", f->uid, " clunked", 8); for(p = p₀; p != nil && strcmp(p->uid, f->uid); p = p->next); if(p != nil) p->online--; } else if(f->aux != nil){ g = f->aux; if((f->omode & OEXEC) == ORDWR) tell(&g->blocking, "† ", f->uid, " parted", 7); deref(g); f->aux = nil; } } Srv fs = { .attach = fsattach, .walk1 = fswalk1, .open = fsopen, .read = fsread, .write = fswrite, .create = fscreate, .flush = fsflush, .stat = fsstat, .destroyfid = fsdestroyfid }; void usage(void){ fprint(2, "usage: %s [-D] [-a addr] [-s srvname] [-m mtpt]\n", argv0); exits("usage"); } void main(int argc, char **argv){ char *addr, *service, *mtpt; addr = nil; service = nil; mtpt = nil; 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 if(addr) listensrv(&fs, addr); postmountsrv(&fs, service, mtpt, MREPL | MCREATE); exits(nil); }