shithub: guifs

ref: c51962a648b3f38fc378ad0081b1d1aa037142d5
dir: /main.c/

View raw version
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <draw.h>
#include <mouse.h>

#include "guifs.h"

#define Eexist	"file does not exist"
#define Enodir	"not a directory"
#define Eperm	"permission denied"
#define Eoffset	"can't write to this file at non-zero offset"
#define Ebadctl	"bad ctl message"
#define Einuse	"file in use"

QLock guilock = 0;

char *username;

enum {
	Qdir,
	Qclone,
	Qevent,
	Qtype,
	Qprops,

	Qprop,
};

enum {
	Fclone,
	Fevent,
	Ftype,
	Fprops,
	Fmax,
};

void fsattach(Req *);
char *fswalk1(Fid *, char *, Qid *);
char *fsclone(Fid *, Fid *);
void fsopen(Req *);
void fsstat(Req *);
void fsread(Req *);
void fswrite(Req *);
void fsforker(void (*)(void*), void *, int);
void fsdestroyfid(Fid *);

Srv fs = {
	.attach = fsattach,
	.walk1 = fswalk1,
	.clone = fsclone,
	.open = fsopen,
	.stat = fsstat,
	.read = fsread,
	.write = fswrite,

	.destroyfid = fsdestroyfid,
	.forker = fsforker,
};

GuiElement *root;

#define QID_TYPE(q)	((q.path) & 0xFF)
#define QID_PROP(q)	(((q.path) >> 8) & 0xFF)

void *
emalloc(ulong size)
{
	void *p = mallocz(size, 1);
	if(!p)
		sysfatal("malloc failed");
	return p;
}

void *
erealloc(void *p, ulong size)
{
	p = realloc(p, size);
	if(!p)
		sysfatal("realloc failed");
	return p;
}

Qid
mkqid(int type)
{
	static int id = 0;

	Qid q;
	q.vers = 0;
	q.path = (type & 0xFFFF) | (id << 16);
	id++;
	switch(type){
	case Qdir:
	case Qprops:
		q.type = QTDIR;
		break;
	case Qclone:
	case Qevent:
	case Qtype:
		q.type = QTFILE;
		break;
	}
	return q;
}

Qid
mkpropqid(int proptag)
{
	return mkqid(Qprop | ((proptag & 0xFF) << 8));
}

void
settype(GuiElement *g, int type)
{
	
	GuiSpec spec = guispecs[type];
	int nprops = spec.nprops + nbaseprops;
	/* Allocate the props before locking the gui element, as some of the
	 * allocations might cause this thread to block 
	 */

	Prop *props = emalloc(sizeof(Prop) * nprops);
	for(int i = 0; i < nbaseprops; i++){
		int tag = baseprops[i];
		props[i].tag = tag;
		props[i].val = propspecs[tag].def(type, tag);
		props[i].qid = mkpropqid(tag);
	}
	for(int i = 0; i < spec.nprops; i++){
		int tag = spec.proptags[i];
		props[i+nbaseprops].tag = tag;
		props[i+nbaseprops].val = propspecs[tag].def(type, tag);
		props[i+nbaseprops].qid = mkpropqid(tag);
	}

	wlock(&g->lock);
	/* TODO: free old propvals */ 
	free(g->props);
	g->type = type;
	g->nprops = nprops;
	g->props = props;
	wunlock(&g->lock);

	updategui(0); /* redraw everything */
}

GuiElement *
newgui(GuiElement *parent)
{
	GuiElement *g = emalloc(sizeof(GuiElement));
	g->parent = parent;
	g->qid = mkqid(Qdir);
	g->qclone = mkqid(Qclone);
	g->qevent = mkqid(Qevent);
	g->qtype = mkqid(Qtype);
	g->qprops = mkqid(Qprops);

	g->events = chancreate(sizeof(char *), 0);

	settype(g, Gcontainer);

	if(parent){
		g->id = parent->nchildren;
		wlock(&parent->lock);
		parent->nchildren++;
		parent->children = erealloc(parent->children, parent->nchildren * sizeof(GuiElement *));
		parent->children[g->id] = g;
		wunlock(&parent->lock);
	}

	return g;
}

GuiElement *
findchild(GuiElement *g, char *name)
{
	char *r;
	uvlong id = strtoull(name, &r, 10);
	if(*r != 0){
		return nil;
	}

	rlock(&g->lock);
	GuiElement *child = (id < g->nchildren) ? g->children[id] : nil;
	runlock(&g->lock);

	return child;
}

void
fsattach(Req *r)
{
	if(root == nil){
		GuiElement *g = newgui(nil);
		root = g;
		settype(g, Gcontainer);
		updategui(1);
	}

	r->fid->aux = root;
	r->fid->qid = root->qid;
	r->ofcall.qid = r->fid->qid;

	respond(r, nil);
}

char *
fswalk1(Fid *fid, char *name, Qid *qid)
{
	GuiElement *g = fid->aux;
	GuiElement *child;
	char *err = nil;

	switch(QID_TYPE(fid->qid)){
	case Qdir:
		if(strcmp(name, "..") == 0){
			if(g->parent == nil) // Root element
				*qid = g->qid;
			else{
				fid->aux = g->parent;
				*qid = g->parent->qid;
			}
		}else if(strcmp(name, "clone") == 0){
			rlock(&g->lock);
			if(guispecs[g->type].leafnode)
				err = Eexist;
			else
				*qid = g->qclone;
			runlock(&g->lock);
		}else if(strcmp(name, "event") == 0)
			*qid = g->qevent;
		else if(strcmp(name, "type") == 0)
			*qid = g->qtype;
		else if(strcmp(name, "props") == 0)
			*qid = g->qprops;
		else if(child = findchild(g, name)){
			fid->aux = child;
			*qid = child->qid;
		}else
			err = Eexist;
		break;
	case Qprops:
		if(strcmp(name, "..") == 0)
			*qid = g->qid;
		else{
			rlock(&g->lock);
			int ok = 0;
			for(int i = 0; i < g->nprops && ok == 0; i++){
				PropSpec spec = propspecs[g->props[i].tag];
				if(strcmp(name, spec.name) == 0){
					*qid = g->props[i].qid;
					ok = 1;
				}
			}
			runlock(&g->lock);
			if(!ok)
				err = Eexist;
		}
		break;
	default:
		err = Enodir;
		break;
	}
	return err;
}

char *
fsclone(Fid *old, Fid *new)
{
	new->aux = old->aux;
	return nil;
}

void
fsopen(Req *r)
{
	GuiElement *g = r->fid->aux;
	char *err = nil;

	switch(QID_TYPE(r->fid->qid)){
	case Qdir:
	case Qevent:
	case Qclone:
	case Qprops:
		if(r->ifcall.mode != OREAD){
			err = Eperm;
			goto Lend;
		}
		break;
	}

	if(QID_TYPE(r->fid->qid) == Qclone){
		/* Create a new child gui element */
		GuiElement *child = newgui(g);

		assert(r->fid->qid.vers == child->id);

		/* Update qid version, so a read reports the correct child id */
		assert(memcmp(&r->fid->qid, &g->qclone, sizeof(Qid)) == 0);
		g->qclone.vers++;
		r->fid->qid = g->qclone;
		r->ofcall.qid = g->qclone;
	}

	if(QID_TYPE(r->fid->qid) == Qevent){
		wlock(&g->lock);
		if(g->listening){
			err = Einuse;
			r->fid->aux = nil;
		}else
			g->listening = 1;
		wunlock(&g->lock);
	}

Lend:
	respond(r, err);
}

void
fsstat(Req *r)
{
	r->d.qid = r->fid->qid;
	r->d.uid = estrdup9p(username);
	r->d.gid = estrdup9p(username);
	r->d.muid = estrdup9p(username);
	switch(QID_TYPE(r->fid->qid)){
	case Qdir:
		r->d.name = estrdup9p("/");
		r->d.mode = 0555|DMDIR;
		break;
	case Qprops:
		r->d.name = estrdup9p("/");
		r->d.mode = 0555|DMDIR;
		break;
	}

	respond(r, nil);
}

int
dirtreegen(int n, Dir *d, void *aux)
{
	GuiElement *g = aux;

	d->uid = estrdup9p(username);
	d->gid = estrdup9p(username);
	d->muid = estrdup9p(username);

	rlock(&g->lock);
	if(guispecs[g->type].leafnode)
		n++;
	runlock(&g->lock);

	if(n < Fmax){
		d->length = 0;

		switch(n){
		case Fclone:
			d->mode = 0444;
			d->name = estrdup9p("clone");
			d->qid = g->qclone;
			break;
		case Fevent:
			d->mode = 0444|DMEXCL;
			d->name = estrdup9p("event");
			d->qid = g->qevent;
			break;
		case Ftype:
			d->mode = 0666;
			d->name = estrdup9p("type");
			d->qid = g->qtype;
			break;
		case Fprops:
			d->mode = 0555|DMDIR;
			d->name = estrdup9p("props");
			d->qid = g->qprops;
			break;
		}
		return 0;
	}else
		n -= Fmax;

	rlock(&g->lock);
	int done = n >= g->nchildren;

	if(!done){
		GuiElement *child = g->children[n];

		d->mode = DMDIR|0555;
		d->qid = child->qid;

		char buf[64];
		snprint(buf, sizeof(buf), "%d", child->id);
		d->name = estrdup9p(buf);
	}
	runlock(&g->lock);

	return done ? -1 : 0;
}

int
proptreegen(int n, Dir *d, void *aux)
{
	GuiElement *g = aux;

	d->uid = estrdup9p(username);
	d->gid = estrdup9p(username);
	d->muid = estrdup9p(username);

	int done;

	rlock(&g->lock);
	done = n >= g->nprops;

	if(!done){
		PropSpec spec = propspecs[g->props[n].tag];
		d->mode = 0666;
		d->name = estrdup9p(spec.name);
		d->qid = g->props[n].qid;
	}
	runlock(&g->lock);

	return done ? -1 : 0;
}

void
fsread(Req *r)
{
	GuiElement *g = r->fid->aux;
	char buf[256];

	switch(QID_TYPE(r->fid->qid)){
	case Qdir:
		dirread9p(r, dirtreegen, g);
		break;
	case Qclone:
		snprint(buf, sizeof(buf), "%uld\n", r->fid->qid.vers-1);
		readstr(r, buf);
		break;
	case Qevent:
		{
			/* get all the messages we can, and add them to g->currentevents */
			char *event;
			int mustrecv = 0;
			ulong currentsize;
			ulong eventsize;
Lretry:
			srvrelease(&fs);
			if(mustrecv){
				recv(g->events, &event);
				goto Lgotevent;
			}

			while(nbrecv(g->events, &event)){
Lgotevent:			currentsize = g->currentevents ? strlen(g->currentevents) : 0;
				eventsize = strlen(event);

				wlock(&g->lock);
				g->currentevents = erealloc(g->currentevents, currentsize+eventsize+1);
				memcpy(g->currentevents+currentsize, event, eventsize);
				g->currentevents[currentsize+eventsize] = 0;
				wunlock(&g->lock);
				free(event);
			}

			rlock(&g->lock);
			if(g->currentevents == nil){
				runlock(&g->lock);
				recv(g->events, &event);
				goto Lgotevent;
			}else{
				srvacquire(&fs);
				readstr(r, g->currentevents);
				if(r->ofcall.count == 0){
					runlock(&g->lock);
					mustrecv = 1;
					goto Lretry;
				}
			}
			runlock(&g->lock);
		}
		break;
	case Qtype:
		rlock(&g->lock);
		snprint(buf, sizeof(buf), "%s\n", guispecs[g->type].name);
		runlock(&g->lock);
		readstr(r, buf);
		break;
	case Qprops:
		dirread9p(r, proptreegen, g);
		break;
	case Qprop:
		{
			int tag = QID_PROP(r->fid->qid);
			PropSpec spec = propspecs[tag];
			PropVal val = getprop(g, tag, 1);
			char *str = spec.print(val);
			readstr(r, str);
			free(str);
		}
		break;
	}
	respond(r, nil);
}

void
fswrite(Req *r)
{
	GuiElement *g = r->fid->aux;
	char *err = nil;
	
	switch(QID_TYPE(r->fid->qid)){
	case Qtype:
		{
			char *buf = emalloc(r->ifcall.count + 1);
			buf[r->ifcall.count] = 0;
			memcpy(buf, r->ifcall.data, r->ifcall.count);

			int type;
			for(type = 0; type < Gmax; type++){
				char *name = guispecs[type].name;
				int len = strlen(name);
				if(strncmp(buf, name, len) == 0 && allspace(buf+len)){
					rlock(&g->lock);
					int canswitch = (g->nchildren == 0 || guispecs[type].leafnode == 0);
					runlock(&g->lock);

					if(canswitch)
						settype(g, type);
					else
						err = "old node has children, and new node type can't";
					break;
				}
			}
			if(type == Gmax)
				err = "no such gui element type";
			free(buf);
		}
		break;
	case Qprop:
		{
			int tag = QID_PROP(r->fid->qid);
			PropSpec spec = propspecs[tag];
			PropVal val;

			char *buf = emalloc(r->ifcall.count + 1);
			buf[r->ifcall.count] = 0;
			memcpy(buf, r->ifcall.data, r->ifcall.count);
			err = spec.parse(buf, &val);
			if(err == nil)
				setprop(g, tag, val, 1);
			free(buf);
		}
	}

	respond(r, err);
}

void
fsforker(void (*fn)(void*), void *arg, int rflag)
{
	/* same as threadsrvforker, but stay in the same note group */
	rflag &= ~RFNOTEG;
	procrfork(fn, arg, 32*1024, rflag);
}

void
fsdestroyfid(Fid *fid)
{
	GuiElement *g = fid->aux;

	if(g != nil && QID_TYPE(fid->qid) == Qevent){
		wlock(&g->lock);
		g->listening = 0;
		free(g->currentevents);
		wunlock(&g->lock);
	}
}

void
usage(void)
{
	fprint(2, "usage: %s [-D] [-m mountpoint] [-s srvname] command\n", argv0);
	exits("usage");
}

void
threadmain(int argc, char **argv)
{
	char *mtpt = "/mnt/gui";
	char *srvname = nil;
	ARGBEGIN{
	case 'D':
		chatty9p++;
		break;
	case 'm':
		mtpt = EARGF(usage());
		break;
	case 's':
		srvname = EARGF(usage());
		break;
	default:
		usage();
	}ARGEND;

	if(argc == 0)
		usage();

	username = getuser();
	initgraphics();
	threadpostmountsrv(&fs, srvname, mtpt, MREPL);

	int pid = fork();
	switch(pid){
	case 0:		/* child process */
		exec(argv[0], argv);
		sysfatal("exec: %r");
		break;
	case -1:	/* error */
		sysfatal("fork: %r");
		break;
	}

	/* parent process */
	int wpid;
	do
		wpid = waitpid();
	while(wpid != -1 && wpid != pid);
	postnote(PNGROUP, getpid(), "interrupt");
	exits(nil);
}