Plan 9 from Bell Labs’s /usr/web/sources/contrib/akumar/igo/gofs/gofs.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#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);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.