shithub: lola

ref: ee60859199f151377a32b6932bde88cc6fe6007a
dir: /fs.c/

View raw version
#include "inc.h"

enum {
	Qroot,
	Qwsys,

	Qcons,
	Qconsctl,
	Qcursor,
	Qwinid,
	Qwinname,
	Qlabel,
	Qkbd,
	Qmouse,
	Qscreen,
	Qsnarf,
	Qtext,
	Qwdir,
//	Qwctl,
	Qwindow,
	Qtap,

	NQids
};

typedef struct Dirent Dirent;
struct Dirent
{
	int path;
	int type;
	char *name;
};

Dirent dirents[] = {
	Qroot,	QTDIR,	".",
	Qwsys,	QTDIR,	"wsys",
	Qwinid,	QTFILE,	"winid",
	Qwinname,	QTFILE,	"winname",
	Qwdir,	QTFILE,	"wdir",
	Qlabel,	QTFILE,	"label",
	Qsnarf,	QTFILE,	"snarf",
	Qtext,	QTFILE,	"text",
	Qcons,	QTFILE, "cons",
	Qconsctl,	QTFILE, "consctl",
	Qkbd,	QTFILE, "kbd",
	Qmouse,	QTFILE, "mouse",
	Qcursor,	QTFILE, "cursor",
	Qscreen,	QTFILE, "screen",
	Qwindow,	QTFILE, "window",
	Qtap,	QTFILE, "kbdtap",
};

char Eperm[] = "permission denied";
char Eexist[] = "file does not exist";
char Enotdir[] = "not a directory";
char Ebadfcall[] = "bad fcall type";
char Eoffset[] = "illegal offset";
char Enomem[] = "out of memory";

char Eflush[] =		"interrupted";
char Einuse[] =		"file in use";
char Edeleted[] =	"window deleted";
char Etooshort[] =	"buffer too small";
char Eshort[] =		"short i/o request";
char Elong[] = 		"snarf buffer too long";
char Eunkid[] = 	"unknown id in attach";
char Ebadrect[] = 	"bad rectangle in attach";
char Ewindow[] = 	"cannot make window";
char Enowindow[] = 	"window has no image";
char Ebadmouse[] = 	"bad format on /dev/mouse";

/* Extension of a Req, req->aux. also has a thread. */
typedef struct Xreq Xreq;
struct Xreq
{
	Req *req;
	Channel *xc;
	Channel *flush;		/* cancel read/write */
	Xreq *next;
};
#define XR(req) ((Xreq*)(req)->aux)
static Xreq *xreqfree;

/* Extension of a Fid, fid->aux */
typedef struct Xfid Xfid;
struct Xfid
{
	Window *w;
	RuneConvBuf cnv;
};
#define XF(fid) ((Xfid*)(fid)->aux)

typedef struct XreqMsg XreqMsg;
struct XreqMsg
{
	Req *r;
	void (*f)(Req*);
};

static void
xreqthread(void *a)
{
	Xreq *xr = a;
	XreqMsg xm;

	threadsetname("xreg.%p", xr);
	for(;;){
		recv(xr->xc, &xm);
		xr->req = xm.r;
		xm.r->aux = xr;
		(*xm.f)(xm.r);
		/* return to pool */
		xr->req = nil;
		xr->next = xreqfree;
		xreqfree = xr;
	}
}

static Xreq*
getxreq(void)
{
	Xreq *xr;
	if(xreqfree){
		xr = xreqfree;
		xreqfree = xr->next;
	}else{
		xr = emalloc(sizeof(Xreq));
		xr->xc = chancreate(sizeof(XreqMsg), 0);
		xr->flush = chancreate(sizeof(int), 0);
		threadcreate(xreqthread, xr, mainstacksize);
	}
	xr->next = nil;
	return xr;
}

static void
toxreq(Req *r, void (*f)(Req*))
{
	Xreq *xr;
	XreqMsg xm;

	xr = getxreq();
	xm.r = r;
	xm.f = f;
	send(xr->xc, &xm);
}

static Xfid*
getxfid(Window *w)
{
	Xfid *xf;
	xf = emalloc(sizeof(Xfid));
	memset(&xf->cnv, 0, sizeof(xf->cnv));
	xf->w = w;
	incref(w);
	return xf;
}

#define QID(w, q) ((w)<<8|(q))
#define QWIN(q) ((q)>>8)
#define QFILE(q) ((q)&0xFF)

static void
fsattach(Req *r)
{
	Window *w;
	char *end;
	int id;

	w = nil;
	if(strcmp(r->ifcall.aname, "new") == 0){
		w = wcreate(rectaddpt(newrect(), screen->r.min));
wsetpid(w, -1, 1);
wsetname(w);
		flushimage(display, 1);
		decref(w);	/* don't delete, xfid will take it */
	}else if(id = strtol(r->ifcall.aname, &end, 10), *end == '\0'){
		w = wfind(id);
	}
	if(w == nil){
		respond(r, "bad attach name");
		return;
	}

	r->fid->aux = getxfid(w);
	r->fid->qid = (Qid){QID(w->id,Qroot),0,QTDIR};
	r->ofcall.qid = r->fid->qid;
	respond(r, nil);
}

static char*
fsclone(Fid *fid, Fid *newfid)
{
	if(XF(fid))
		newfid->aux = getxfid(XF(fid)->w);
	return nil;
}

int
skipfile(char *name)
{
	return gotscreen && strcmp(name, "screen") == 0 ||
	   snarffd >= 0 && strcmp(name, "snarf") == 0 ||
	   !servekbd && strcmp(name, "kbd") == 0;
}


static char*
fswalk1(Fid *fid, char *name, Qid *qid)
{
	int i;
	Dirent *d;
	Xfid *xf;
	Window *w;
	int dir;

	xf = fid->aux;
	w = xf->w;
	dir = QFILE(fid->qid.path);
	if(dir == Qroot){
		if(strcmp(name, "..") == 0){
			/* This sucks because we don't know which window we came from
			 * error out for now */
			return "vorwärts immer, rückwärts nimmer";
		}
		for(i = 0; i < nelem(dirents); i++){
			d = &dirents[i];
			if(!skipfile(d->name) && strcmp(name, d->name) == 0){
				fid->qid = (Qid){QID(w->id,d->path), 0, d->type};
				*qid = fid->qid;
				return nil;
			}
		}
	}else if(dir == Qwsys){
		char *end;
		int id;
		if(strcmp(name, "..") == 0){
			fid->qid = (Qid){QID(w->id,Qroot), 0, QTDIR};
			*qid = fid->qid;
			return nil;
		}
		if(id = strtol(name, &end, 10), *end == '\0'){
			w = wfind(id);
			if(w){
				incref(w);
				wrelease(xf->w);
				xf->w = w;
				fid->qid = (Qid){QID(w->id,Qroot), 0, QTDIR};
				*qid = fid->qid;
				return nil;
			}
		}
	}
	return "no such file";
}

static int
genrootdir(int n, Dir *d, void *a)
{
	Window *w = a;
	int i;

	n++;	/* -1 is root dir */
	i = 0;
	while(n--){
		i++;
		if(i >= nelem(dirents))
			return -1;
		/* we know the last file is never skipped */
		while(skipfile(dirents[i].name)) i++;
	}

	d->atime = time(nil);
	d->mtime = d->atime;
	d->uid = estrdup9p(getuser());
	d->gid = estrdup9p(d->uid);
	d->muid = estrdup9p(d->uid);
	d->qid = (Qid){QID(w->id,dirents[i].path), 0, dirents[i].type};
	d->mode = 0664;
	if(dirents[i].type & QTDIR)
		d->mode |= 0111;
	d->name = estrdup9p(dirents[i].name);
	d->length = 0;
	return 0;
}

static int
genwsysdir(int n, Dir *d, void*)
{
	d->atime = time(nil);
	d->mtime = d->atime;
	d->uid = estrdup9p(getuser());
	d->gid = estrdup9p(d->uid);
	d->muid = estrdup9p(d->uid);

	if(n == -1){
		d->qid = (Qid){Qwsys, 0, QTDIR};
		d->mode = 0775;
		d->name = estrdup9p("wsys");
		d->length = 0;
		return 0;
	}
	if(n < nwindows){
		int id = windows[n]->id;
		d->qid = (Qid){QID(id,Qroot), 0, QTDIR};
		d->mode = 0775;
		d->name = smprint("%d", id);
		d->length = 0;
		return 0;
	}

	return -1;
}

static int ntsnarf;
static char *tsnarf;

static void
fsopen(Req *r)
{
	Window *w;

	w = XF(r->fid)->w;

	/* TODO: check and sanitize mode */

	if(w == nil || w->deleted){
		respond(r, Edeleted);
		return;
	}

	switch(QFILE(r->fid->qid.path)){
	case Qsnarf:
		r->ifcall.mode &= ~OTRUNC;
		if(r->ifcall.mode==ORDWR || r->ifcall.mode==OWRITE)
			ntsnarf = 0;
		break;

	case Qconsctl:
		if(w->consctlopen){
			respond(r, Einuse);
			return;
		}
		w->consctlopen = TRUE;
		break;

	case Qkbd:
		if(w->kbdopen){
			respond(r, Einuse);
			return;
		}
		w->kbdopen = TRUE;
		break;

	case Qmouse:
		if(w->mouseopen){
			respond(r, Einuse);
			return;
		}
// TODO: copy comment from rio
		w->resized = FALSE;
		w->mouseopen = TRUE;
		break;

	case Qtap:
		r->ifcall.mode &= (OREAD|OWRITE|ORDWR);
		chanprint(ctltap, "%c%c", Tapon, r->ifcall.mode);
		respond(r, recvp(resptap));
		return;
	}

	respond(r, nil);
}

static void
fsclose(Fid *fid)
{
	Xfid *xf;
	Window *w;
	Text *x;

	xf = XF(fid);
	if(xf == nil)
		return;
	w = xf->w;
	x = &w->text;

	switch(QFILE(fid->qid.path)){
	/* replace snarf buffer when /dev/snarf is closed */
	case Qsnarf:
		if(fid->omode==ORDWR || fid->omode==OWRITE){
			setsnarf(tsnarf, ntsnarf);
			ntsnarf = 0;
		}
		break;

	case Qconsctl:
		if(x->rawmode){
			x->rawmode = 0;
			wsendmsg(w, Rawoff, ZR, nil);
		}
		if(w->holdmode > 0){
			w->holdmode = 1;
			wsendmsg(w, Holdoff, ZR, nil);
		}
		w->consctlopen = FALSE;
		break;

	case Qkbd:
		w->kbdopen = FALSE;
		break;

	case Qmouse:
		w->mouseopen = FALSE;
		w->resized = FALSE;
		wsendmsg(w, Refresh, ZR, nil);
		break;

	case Qcursor:
		w->cursorp = nil;
		wsetcursor(w);
		break;

	case Qtap:
		chanprint(ctltap, "%c%c", Tapoff, fid->omode);
		recvp(resptap);
		break;
	}

	wrelease(xf->w);
	free(xf->cnv.buf);
	free(xf);
	fid->aux = nil;
}

static int
readimgdata(Image *i, char *t, Rectangle r, int offset, int n)
{
	int ww, oo, y, m;
	uchar *tt;

	ww = bytesperline(r, i->depth);
	r.min.y += offset/ww;
	if(r.min.y >= r.max.y)
		return 0;
	y = r.min.y + (n + ww-1)/ww;
	if(y < r.max.y)
		r.max.y = y;
	m = ww * Dy(r);
	oo = offset % ww;
	if(oo == 0 && n >= m)
		return unloadimage(i, r, (uchar*)t, n);
	if((tt = malloc(m)) == nil)
		return -1;
	m = unloadimage(i, r, tt, m) - oo;
	if(m > 0){
		if(n < m) m = n;
		memmove(t, tt + oo, m);
	}
	free(tt);
	return m;
}

/* Fill request from image,
 * returns only either header or data */
char*
readimg(Req *r, Image *img)
{
	char *head;
	char cbuf[30];
	Rectangle rect;
	int n;

	rect = img->r;
	if(r->ifcall.offset < 5*12){
		head = smprint("%11s %11d %11d %11d %11d ",
			chantostr(cbuf, img->chan),
			rect.min.x, rect.min.y, rect.max.x, rect.max.y);
		readstr(r, head);
		free(head);
	}else{
		/* count is unsigned, so check with n */
		n = readimgdata(img, r->ofcall.data, rect, r->ifcall.offset-5*12, r->ifcall.count);
		if(n < 0)
			return Enomem;
		r->ofcall.count = n;
	}
	return nil;
}

static char*
readblocking(Req *r, Channel *readchan)
{
	Window *w;
	Channel *chan;
	Stringpair pair;
	enum { Adata, Agone, Aflush, NALT };
	Alt alts[NALT+1];

	w = XF(r->fid)->w;

	alts[Adata] = ALT(readchan, &chan, CHANRCV);
	if(w)
		alts[Agone] = ALT(w->gone, nil, CHANRCV);
	else
		alts[Agone] = ALT(nil, nil, CHANNOP);
	alts[Aflush] = ALT(XR(r)->flush, nil, CHANRCV);
	alts[NALT].op = CHANEND;
	switch(alt(alts)){
	case Adata:
		pair.s = r->ofcall.data;
		pair.ns = r->ifcall.count;
		send(chan, &pair);
		recv(chan, &pair);
		r->ofcall.count = min(r->ifcall.count, pair.ns);
		return nil;
	case Agone:
		return Edeleted;
	case Aflush:
		return Eflush;
	}
	return nil;	/* can't happen */
}

static void
xread(Req *r)
{
	Window *w;
	char *data;

	w = XF(r->fid)->w;

	if(w == nil || w->deleted){
		respond(r, Edeleted);
		return;
	}

	switch(QFILE(r->fid->qid.path)){
	case Qwinid:
		data = smprint("%11d ", w->id);
		readstr(r, data);
		free(data);
		break;
	case Qwinname:
		readstr(r, w->name);
		break;
	case Qlabel:
		readstr(r, w->label);
		break;
	case Qsnarf:
		data = smprint("%.*S", nsnarf, snarf);
		readstr(r, data);
		free(data);
		break;
	case Qtext:
		data = smprint("%.*S", w->text.nr, w->text.r);
		readstr(r, data);
		free(data);
		break;
	case Qcons:
		respond(r, readblocking(r, w->consread));
		return;
	case Qkbd:
		respond(r, readblocking(r, w->kbdread));
		return;
	case Qmouse:
		respond(r, readblocking(r, w->mouseread));
		return;
	case Qcursor:
		respond(r, "cursor read not implemented");
		return;
	case Qscreen:
		respond(r, readimg(r, screen));
		return;
	case Qwindow:
		respond(r, readimg(r, w->img));
		return;
	case Qtap:
		respond(r, readblocking(r, totap));
		return;
	default:
		respond(r, "cannot read");
		return;
	}
	respond(r, nil);
}

static void
xwrite(Req *r)
{
	Xfid *xf;
	Window *w;
	Text *x;
	vlong offset;
	u32int count;
	char *data, *p, *e;
	Point pt;
	Channel *kbd;
	Stringpair pair;
	enum { Adata, Agone, Aflush, NALT };
	Alt alts[NALT+1];

	xf = XF(r->fid);
	w = xf->w;
	x = &w->text;
	offset = r->ifcall.offset;
	count = r->ifcall.count;
	data = r->ifcall.data;
	r->ofcall.count = count;

	if(w == nil || w->deleted){
		respond(r, Edeleted);
		return;
	}
	int f = QFILE(r->fid->qid.path);
	switch(f){
	case Qcons:
		alts[Adata] = ALT(w->conswrite, &kbd, CHANRCV);
		alts[Agone] = ALT(w->gone, nil, CHANRCV);
		alts[Aflush] = ALT(XR(r)->flush, nil, CHANRCV);
		alts[NALT].op = CHANEND;
		switch(alt(alts)){
		case Adata:
			cnvsize(&xf->cnv, count);
			memmove(xf->cnv.buf+xf->cnv.n, data, count);
			xf->cnv.n += count;
			pair = b2r(&xf->cnv);
			send(kbd, &pair);
			break;
		case Agone:
			respond(r, Edeleted);
			return;
		case Aflush:
			respond(r, Eflush);
			return;
		}
		break;

	case Qconsctl:
		if(strncmp(data, "holdon", 6) == 0){
			wsendmsg(w, Holdon, ZR, nil);
			break;
		}
		if(strncmp(data, "holdoff", 7) == 0){
			wsendmsg(w, Holdoff, ZR, nil);
			break;
		}
		if(strncmp(data, "rawon", 5) == 0){
			if(w->holdmode){
				w->holdmode = 1;
				wsendmsg(w, Holdoff, ZR, nil);
			}
			if(x->rawmode++ == 0)
				wsendmsg(w, Rawon, ZR, nil);
			break;
		}
		if(strncmp(data, "rawoff", 6) == 0){
			if(--x->rawmode == 0)
				wsendmsg(w, Rawoff, ZR, nil);
			break;
		}
		respond(r, "unknown control message");
		return;

	case Qmouse:
		if(data[0] != 'm'){
			respond(r, Ebadmouse);
			return;
		}
		p = nil;
		pt.x = strtoul(data+1, &p, 0);
		if(p == nil){
			respond(r, Eshort);
			return;
		}
		pt.y = strtoul(p, nil, 0);
		wmovemouse(w, pt);
		break;

	case Qcursor:
		if(count < 2*4+2*2*16)
			w->cursorp = nil;
		else{
			w->cursor.offset.x = BGLONG(data+0*4);
			w->cursor.offset.y = BGLONG(data+1*4);
			memmove(w->cursor.clr, data+2*4, 2*2*16);
			w->cursorp = &w->cursor;
		}
		cursor = (void*)(uintptr)~0;	/* invalide cache */
		wsetcursor(w);
		break;

	case Qlabel:
		if(offset != 0){
			respond(r, "non-zero offset writing label");
			return;
		}
		w->label = realloc(w->label, count+1);
		memmove(w->label, data, count);
		w->label[count] = 0;
		break;

	case Qsnarf:
		if(count == 0)
			break;
		/* always append only */
		if(ntsnarf > MAXSNARF){	/* avoid thrashing when people cut huge text */
			respond(r, Elong);
			return;
		}
		p = realloc(tsnarf, ntsnarf+count);
		if(p == nil){
			respond(r, Enomem);
			return;
		}
		tsnarf = p;
		memmove(tsnarf+ntsnarf, data, count);
		ntsnarf += count;
		break;

	case Qwdir:
		if(count > 0 && data[count-1] == '\n')
			data[--count] = '\0';
		if(count == 0)
			break;
		/* assume data comes in a single write */
		if(data[0] == '/')
			p = smprint("%.*s", count, data);
		else
			p = smprint("%s/%.*s", w->dir, count, data);
		if(p == nil){
			respond(r, Enomem);
			return;
		}
		free(w->dir);
		w->dir = cleanname(p);
		break;

	case Qtap:
		if(count < 2){
			respond(r, "malformed key");
			return;
		}
		e = data + count;
		for(p = data; p < e; p += strlen(p)+1){
			switch(*p){
			case '\0':
				r->ofcall.count = p - data;
				respond(r, "null message type");
				return;
			case Tapfocus:
				/* cleanup our own pollution */
				break;
			default:
				chanprint(fromtap, "%s", p);
				break;	
			}
		}
		break;

	default:
		respond(r, "cannot write");
		return;
	}
	respond(r, nil);
}

static void
fsread(Req *r)
{
	if((r->fid->qid.type & QTDIR) == 0){
		toxreq(r, xread);
		return;
	}

	switch(QFILE(r->fid->qid.path)){
	case Qroot:
		dirread9p(r, genrootdir, XF(r->fid)->w);
		break;
	case Qwsys:
		dirread9p(r, genwsysdir, nil);
		break;
	}
	respond(r, nil);
}

static void
fswrite(Req *r)
{
	toxreq(r, xwrite);
}

static void
fsflush(Req *r)
{
	Xreq *xr;
	int dummy = 0;

	xr = XR(r->oldreq);
	assert(xr);

	/* TODO: not entirely sure this is right.
	 * is it possible no-one is listening? */
	send(xr->flush, &dummy);
	respond(r, nil);
}

static void
fsstat(Req *r)
{
	int f;

	f = QFILE(r->fid->qid.path);
	genrootdir(f-1, &r->d, XF(r->fid)->w);
	respond(r, nil);
}

Srv fsys = {
	.attach		fsattach,
	.open		fsopen,
	.read		fsread,
	.write		fswrite,
	.stat		fsstat,
	.flush		fsflush,
	.walk1		fswalk1,
	.clone		fsclone,
	.destroyfid	fsclose,
	nil
};

void
post(char *name, int srvfd)
{
	char buf[80];
	int fd;

	snprint(buf, sizeof buf, "/srv/%s", name);
	fd = create(buf, OWRITE|ORCLOSE|OCEXEC, 0600);
	if(fd < 0)
		panic(buf);
	if(fprint(fd, "%d", srvfd) < 0)
		panic("post");
	putenv("wsys", buf);
	/* leave fd open */
}

static Ioproc *io9p;

/* copy & paste from /sys/src/libc/9sys/read9pmsg.c
 * changed to use ioreadn instead of readn */
int
read9pmsg(int fd, void *abuf, uint n)
{
	int m, len;
	uchar *buf;

	buf = abuf;

	/* read count */
	m = ioreadn(io9p, fd, buf, BIT32SZ);
	if(m != BIT32SZ){
		if(m < 0)
			return -1;
		return 0;
	}

	len = GBIT32(buf);
	if(len <= BIT32SZ || len > n){
		werrstr("bad length in 9P2000 message header");
		return -1;
	}
	len -= BIT32SZ;
	m = ioreadn(io9p, fd, buf+BIT32SZ, len);
	if(m < len)
		return 0;
	return BIT32SZ+m;
}

/*
 * Build pipe with OCEXEC set on second fd.
 * Can't put it on both because we want to post one in /srv.
 */
int
cexecpipe(int *p0, int *p1)
{
	/* pipe the hard way to get close on exec */
	if(bind("#|", "/mnt/temp", MREPL) == -1)
		return -1;
	*p0 = open("/mnt/temp/data", ORDWR);
	*p1 = open("/mnt/temp/data1", ORDWR|OCEXEC);
	unmount(nil, "/mnt/temp");
	if(*p0<0 || *p1<0)
		return -1;
	return 0;
}

int fsysfd;
char srvpipe[64];

void
fs(void)
{
	io9p = ioproc();

	if(cexecpipe(&fsysfd, &fsys.infd) < 0)
		panic("pipe");
	fsys.outfd = fsys.infd;
	snprint(srvpipe, sizeof(srvpipe), "lola.%s.%lud", getuser(), (ulong)getpid());
	post(srvpipe, fsysfd);
//	chatty9p++;
	srv(&fsys);
}

int
fsmount(int id)
{	char buf[32];

	close(fsys.infd);	/* close server end so mount won't hang if exiting */
	snprint(buf, sizeof buf, "%d", id);
	if(mount(fsysfd, -1, "/mnt/wsys", MREPL, buf) == -1){
		fprint(2, "mount failed: %r\n");
		return -1;
	}
	if(bind("/mnt/wsys", "/dev", MBEFORE) == -1){
		fprint(2, "bind failed: %r\n");
		return -1;
	}
	return 0;
}