shithub: riscv

ref: e51c4bc6e493a9cd0d9e9d7f1d24c57ae77bab65
dir: /sys/src/cmd/aux/consolefs.c/

View raw version
#include <u.h>
#include <libc.h>
#include <auth.h>
#include <fcall.h>
#include <bio.h>
#include <ndb.h>
#include <thread.h>

/*
 *  This fs presents a 1 level file system.  It contains
 *  up to three files per console (xxx and xxxctl and xxxstat)
 */

typedef struct Console Console;
typedef struct Fid Fid;
typedef struct Request Request;
typedef struct Reqlist Reqlist;
typedef struct Fs Fs;

enum
{
	/* last 5 bits of qid.path */
	Textern=	0,		/* fake parent of top level */
	Ttopdir,			/* top level directory */
	Qctl,
	Qstat,
	Qdata,

	Bufsize=	32*1024,	/* chars buffered per reader */
	Maxcons=	64,		/* maximum consoles */
	Nhash=		64,		/* Fid hash buckets */
};

#define TYPE(x)		(((ulong)x.path) & 0xf)
#define CONS(x)		((((ulong)x.path) >> 4)&0xfff)
#define QID(c, x)	(((c)<<4) | (x))

struct Request
{
	Request	*next;
	Fid	*fid;
	Fs	*fs;
	Fcall	f;
	uchar	buf[1];
};

struct Reqlist
{
	Lock;
	Request	*first;
	Request *last;
};

struct Fid
{
	Lock;
	Fid	*next;			/* hash list */
	Fid	*cnext;			/* list of Fid's on a console */
	int	fid;
	int	ref;

	int	attached;
	int	open;
	char	*user;
	char	mbuf[Bufsize];		/* message */
	int	bufn;
	int	used;
	Qid	qid;

	Console	*c;

	char	buf[Bufsize];
	char	*rp;
	char	*wp;

	Reqlist	r;			/* active read requests */
};

struct Console
{
	Lock;

	char	*name;
	char	*dev;
	int	speed;
	int	cronly;
	int	ondemand;		/* open only on demand */
	int	chat;			/* chat consoles are special */

	int	pid;			/* pid of reader */

	int	fd;
	int	cfd;
	int	sfd;

	Fid	*flist;			/* open fids to broadcast to */
};

struct Fs
{
	Lock;

	int	fd;			/* to kernel mount point */
	int	messagesize;
	Fid	*hash[Nhash];
	Console	*cons[Maxcons];
	int	ncons;
};

extern	void	console(Fs*, char*, char*, int, int, int);
extern	Fs*	fsmount(char*);

extern	void	fsreader(void*);
extern	void	fsrun(void*);
extern	Fid*	fsgetfid(Fs*, int);
extern	void	fsputfid(Fs*, Fid*);
extern	int	fsdirgen(Fs*, Qid, int, Dir*, uchar*, int);
extern	void	fsreply(Fs*, Request*, char*);
extern	void	fskick(Fs*, Fid*);
extern	int	fsreopen(Fs*, Console*);

extern	void	fsversion(Fs*, Request*, Fid*);
extern	void	fsflush(Fs*, Request*, Fid*);
extern	void	fsauth(Fs*, Request*, Fid*);
extern	void	fsattach(Fs*, Request*, Fid*);
extern	void	fswalk(Fs*, Request*, Fid*);
extern	void	fsclwalk(Fs*, Request*, Fid*);
extern	void	fsopen(Fs*, Request*, Fid*);
extern	void	fscreate(Fs*, Request*, Fid*);
extern	void	fsread(Fs*, Request*, Fid*);
extern	void	fswrite(Fs*, Request*, Fid*);
extern	void	fsclunk(Fs*, Request*, Fid*);
extern	void	fsremove(Fs*, Request*, Fid*);
extern	void	fsstat(Fs*, Request*, Fid*);
extern	void	fswstat(Fs*, Request*, Fid*);


void 	(*fcall[])(Fs*, Request*, Fid*) =
{
	[Tflush]	fsflush,
	[Tversion]	fsversion,
	[Tauth]	fsauth,
	[Tattach]	fsattach,
	[Twalk]		fswalk,
	[Topen]		fsopen,
	[Tcreate]	fscreate,
	[Tread]		fsread,
	[Twrite]	fswrite,
	[Tclunk]	fsclunk,
	[Tremove]	fsremove,
	[Tstat]		fsstat,
	[Twstat]	fswstat
};

char Eperm[] = "permission denied";
char Eexist[] = "file does not exist";
char Enotdir[] = "not a directory";
char Eisopen[] = "file already open";
char Ebadcount[] = "bad read/write count";
char Enofid[] = "no such fid";

char *consoledb = "/lib/ndb/consoledb";
char *mntpt = "/mnt/consoles";

int messagesize = 8192+IOHDRSZ;

void
fatal(char *fmt, ...)
{
	va_list arg;
	char buf[1024];

	write(2, "consolefs: ", 10);
	va_start(arg, fmt);
	vseprint(buf, buf+1024, fmt, arg);
	va_end(arg);
	write(2, buf, strlen(buf));
	write(2, "\n", 1);
	threadexitsall(fmt);
}


void*
emalloc(uint n)
{
	void *p;

	p = malloc(n);
	if(p == nil)
		fatal("malloc failed: %r");
	memset(p, 0, n);
	return p;
}

int debug;
Ndb *db;

/*
 *  any request that can get queued for a delayed reply
 */
Request*
allocreq(Fs *fs, int bufsize)
{
	Request *r;

	r = emalloc(sizeof(Request)+bufsize);
	r->fs = fs;
	r->next = nil;
	return r;
}

/*
 *  for maintaining lists of requests
 */
void
addreq(Reqlist *l, Request *r)
{
	lock(l);
	if(l->first == nil)
		l->first = r;
	else
		l->last->next = r;
	l->last = r;
	r->next = nil;
	unlock(l);
}

/*
 *  remove the first request from a list of requests
 */
Request*
remreq(Reqlist *l)
{
	Request *r;

	lock(l);
	r = l->first;
	if(r != nil)
		l->first = r->next;
	unlock(l);
	return r;
}

/*
 *  remove a request with the given tag from a list of requests
 */
Request*
remtag(Reqlist *l, int tag)
{
	Request *or, **ll;

	lock(l);
	ll = &l->first;
	for(or = *ll; or; or = or->next){
		if(or->f.tag == tag){
			*ll = or->next;
			unlock(l);
			return or;
		}
		ll = &or->next;
	}
	unlock(l);
	return nil;
}

Qid
parentqid(Qid q)
{
	if(q.type & QTDIR)
		return (Qid){QID(0, Textern), 0, QTDIR};
	else
		return (Qid){QID(0, Ttopdir), 0, QTDIR};
}

int
fsdirgen(Fs *fs, Qid parent, int i, Dir *d, uchar *buf, int nbuf)
{
	static char name[64];
	char *p;
	int xcons;

	d->uid = d->gid = d->muid = "network";
	d->length = 0;
	d->atime = time(nil);
	d->mtime = d->atime;
	d->type = 'C';
	d->dev = '0';

	switch(TYPE(parent)){
	case Textern:
		if(i != 0)
			return -1;
		p = "consoles";
		d->mode = DMDIR|0555;
		d->qid.type = QTDIR;
		d->qid.path = QID(0, Ttopdir);
		d->qid.vers = 0;
		break;
	case Ttopdir:
		xcons = i/3;
		if(xcons >= fs->ncons)
			return -1;
		p = fs->cons[xcons]->name;
		switch(i%3){
		case 0:
			if(fs->cons[xcons]->cfd < 0)
				return 0;
			snprint(name, sizeof name, "%sctl", p);
			p = name;
			d->qid.type = QTFILE;
			d->qid.path = QID(xcons, Qctl);
			d->qid.vers = 0;
			break;
		case 1:
			if(fs->cons[xcons]->sfd < 0)
				return 0;
			snprint(name, sizeof name, "%sstat", p);
			p = name;
			d->qid.type = QTFILE;
			d->qid.path = QID(xcons, Qstat);
			d->qid.vers = 0;
			break;
		case 2:
			d->qid.type = QTFILE;
			d->qid.path = QID(xcons, Qdata);
			d->qid.vers = 0;
			break;
		}
		d->mode = 0666;
		break;
	default:
		return -1;
	}
	d->name = p;
	if(buf != nil)
		return convD2M(d, buf, nbuf);
	return 1;
}

/*
 *  mount the user interface and start a request processor
 */
Fs*
fsmount(char *mntpt)
{
	Fs *fs;
	int pfd[2], srv;
	char buf[32];
	int n;
	static void *v[2];

	fs = emalloc(sizeof(Fs));

	if(pipe(pfd) < 0)
		fatal("opening pipe: %r");

	/* start up the file system process */
	v[0] = fs;
	v[1] = pfd;
	proccreate(fsrun, v, 16*1024);

	/* Typically mounted before /srv exists */
	if(access("/srv/consoles", AEXIST) < 0){
		srv = create("/srv/consoles", OWRITE, 0666);
		if(srv < 0)
			fatal("post: %r");

		n = sprint(buf, "%d", pfd[1]);
		if(write(srv, buf, n) < 0)
			fatal("write srv: %r");

		close(srv);
	}

	mount(pfd[1], -1, mntpt, MBEFORE, "");
	close(pfd[1]);
	return fs;
}

/*
 *  reopen a console
 */
int
fsreopen(Fs* fs, Console *c)
{
	char buf[128];
	static void *v[2];

	if(c->pid){
		if(postnote(PNPROC, c->pid, "reopen") != 0)
			fprint(2, "postnote failed: %r\n");
		c->pid = 0;
	}

	if(c->fd >= 0){
		close(c->fd);
		close(c->cfd);
		close(c->sfd);
		c->cfd = -1;
		c->fd = -1;
		c->sfd = -1;
	}

	if(c->flist == nil && c->ondemand)
		return 0;

	c->fd = open(c->dev, ORDWR);
	if(c->fd < 0)
		return -1;

	snprint(buf, sizeof(buf), "%sctl", c->dev);
	c->cfd = open(buf, ORDWR);
	fprint(c->cfd, "b%d", c->speed);

	snprint(buf, sizeof(buf), "%sstat", c->dev);
	c->sfd = open(buf, OREAD);

	v[0] = fs;
	v[1] = c;
	proccreate(fsreader, v, 16*1024);

	return 0;
}

void
change(Fs *fs, Console *c, int doreopen, int speed, int cronly, int ondemand)
{
	lock(c);

	if(speed != c->speed){
		c->speed = speed;
		doreopen = 1;
	}
	if(ondemand != c->ondemand){
		c->ondemand = ondemand;
		doreopen = 1;
	}
	c->cronly = cronly;
	if(doreopen)
		fsreopen(fs, c);

	unlock(c);
}

/*
 *  create a console interface
 */
void
console(Fs* fs, char *name, char *dev, int speed, int cronly, int ondemand)
{
	Console *c;
	char *x;
	int i, doreopen;

	if(fs->ncons >= Maxcons)
		fatal("too many consoles, too little time");

	doreopen = 0;
	for(i = 0; i < fs->ncons; i++){
		c = fs->cons[i];
		if(strcmp(name, c->name) == 0){
			if(strcmp(dev, c->dev) != 0){
				/* new device */
				x = c->dev;
				c->dev = strdup(dev);
				free(x);
				doreopen = 1;
			}
			change(fs, c, doreopen, speed, cronly, ondemand);
			return;
		}
	}
#ifdef sapedoesntlikethis
	/*
	 * The code below prevents this from working.  I can't
	 * think of scenarios where the code below actually helps
	 *	Sape
	 *
	 * console=borneo dev=/dev/eia1
	 * 	speed=9600
	 * 	openondemand=1
	 * console=tottie dev=/dev/eia1
	 * 	speed=115200
	 * 	openondemand=1
	 */
	for(i = 0; i < fs->ncons; i++){
		c = fs->cons[i];
		if(strcmp(dev, c->dev) == 0){
			/* at least a rename */
			x = c->name;
			c->name = strdup(name);
			free(x);
			change(fs, c, doreopen, speed, cronly, ondemand);
			return;
		}
	}
#endif
	c = emalloc(sizeof(Console));
	fs->cons[fs->ncons] = c;
	fs->ncons++;
	c->name = strdup(name);
	c->dev = strdup(dev);
	if(strcmp(c->dev, "/dev/null") == 0) 
		c->chat = 1;
	else 
		c->chat = 0;
	c->fd = -1;
	c->cfd = -1;
	c->sfd = -1;
	change(fs, c, 1, speed, cronly, ondemand);
}

/*
 *  buffer data from console to a client.
 *  circular q with writer able to catch up to reader.
 *  the reader may miss data but always sees an in order sequence.
 */
void
fromconsole(Fid *f, char *p, int n)
{
	char *rp, *wp, *ep;
	int pass;

	lock(f);
	rp = f->rp;
	wp = f->wp;
	ep = f->buf + sizeof(f->buf);
	pass = 0;
	while(n--){
		*wp++ = *p++;
		if(wp >= ep)
			wp = f->buf;
		if(rp == wp)
			pass = 1;
	}
	f->wp = wp;

	/*  we overtook the read pointer, push it up so readers always
	 *  see the tail of what was written
	 */
	if(pass){
		wp++;
		if(wp >= ep)
			f->rp = f->buf;
		else
			f->rp = wp;
	}
	unlock(f);
}

/*
 *  broadcast a list of members to all listeners
 */
void
bcastmembers(Fs *fs, Console *c, char *msg, Fid *f)
{
	int n;
	Fid *fl;
	char buf[512];

	sprint(buf, "[%s%s", msg, f->user);
	for(fl = c->flist; fl != nil && strlen(buf) + 64 < sizeof(buf); fl = fl->cnext){
		if(f == fl)
			continue;
		strcat(buf, ", ");
		strcat(buf, fl->user);
	}
	strcat(buf, "]\n");

	n = strlen(buf);
	for(fl = c->flist; fl; fl = fl->cnext){
		fromconsole(fl, buf, n);
		fskick(fs, fl);
	}
}

void
handler(void*, char *msg)
{
	if(strstr(msg, "reopen") != nil ||
	   strstr(msg, "write on closed pipe") != nil)
		noted(NCONT);
	noted(NDFLT);
}

/*
 *  a process to read console output and broadcast it (one per console)
 */
void
fsreader(void *v)
{
	int n;
	Fid *fl;
	char buf[1024];
	Fs *fs;
	Console *c;
	void **a;

	a = v;
	fs = a[0];
	c = a[1];
	c->pid = getpid();
	notify(handler);
	if(c->chat)
		threadexits(nil);
	for(;;){
		n = read(c->fd, buf, sizeof(buf));
		if(n < 0)
			break;
		lock(c);
		for(fl = c->flist; fl; fl = fl->cnext){
			fromconsole(fl, buf, n);
			fskick(fs, fl);
		}
		unlock(c);
	}
}

void
readdb(Fs *fs)
{
	Ndbtuple *t, *nt;
	char *dev, *cons;
	int cronly, speed, ondemand;

	ndbreopen(db);

	/* start a listener for each console */
	for(;;){
		t = ndbparse(db);
		if(t == nil)
			break;
		dev = nil;
		cons = nil;
		speed = 9600;
		cronly = 0;
		ondemand = 0;
		for(nt = t; nt; nt = nt->entry){
			if(strcmp(nt->attr, "console") == 0)
				cons = nt->val;
			else if(strcmp(nt->attr, "dev") == 0)
				dev = nt->val;
			else if(strcmp(nt->attr, "speed") == 0)
				speed = atoi(nt->val);
			else if(strcmp(nt->attr, "cronly") == 0)
				cronly = 1;
			else if(strcmp(nt->attr, "openondemand") == 0)
				ondemand = 1;
		}
		if(dev != nil && cons != nil)
			console(fs, cons, dev, speed, cronly, ondemand);
		ndbfree(t);
	}
}

int dbmtime;

/*
 *  a request processor (one per Fs)
 */
void
fsrun(void *v)
{
	int n, t;
	Request *r;
	Fid *f;
	Dir *d;
	void **a = v;
	Fs* fs;
	int *pfd;

	fs = a[0];
	pfd = a[1];
	fs->fd = pfd[0];
	notify(handler);
	for(;;){
		d = dirstat(consoledb);
		if(d != nil && d->mtime != dbmtime){
			dbmtime = d->mtime;
			readdb(fs);
		}
		free(d);
		r = allocreq(fs, messagesize);
		n = read9pmsg(fs->fd, r->buf, messagesize);
		if(n == 0)
			threadexitsall("unmounted");
		if(n < 0)
			fatal("mount read: %r");
		if(convM2S(r->buf, n, &r->f) != n)
			fatal("convM2S format error: %r");

		f = fsgetfid(fs, r->f.fid);
		r->fid = f;
		if(debug)
			fprint(2, "%F path %llux\n", &r->f, f->qid.path);

		t = r->f.type;
		r->f.type++;
		(*fcall[t])(fs, r, f);
	}
}

Fid*
fsgetfid(Fs *fs, int fid)
{
	Fid *f, *nf;

	lock(fs);
	for(f = fs->hash[fid%Nhash]; f; f = f->next){
		if(f->fid == fid){
			f->ref++;
			unlock(fs);
			return f;
		}
	}

	nf = emalloc(sizeof(Fid));
	nf->next = fs->hash[fid%Nhash];
	fs->hash[fid%Nhash] = nf;
	nf->fid = fid;
	nf->ref = 1;
	nf->wp = nf->buf;
	nf->rp = nf->wp;
	unlock(fs);
	return nf;
}

void
fsputfid(Fs *fs, Fid *f)
{
	Fid **l, *nf;

	lock(fs);
	if(--f->ref > 0){
		unlock(fs);
		return;
	}
	for(l = &fs->hash[f->fid%Nhash]; nf = *l; l = &nf->next)
		if(nf == f){
			*l = f->next;
			break;
		}
	unlock(fs);
	free(f->user);
	free(f);
}

void
fsauth(Fs *fs, Request *r, Fid*)
{
	fsreply(fs, r, "consolefs: authentication not required");
}

void
fsversion(Fs *fs, Request *r, Fid*)
{

	if(r->f.msize < 256){
		fsreply(fs, r, "message size too small");
		return;
	}
	messagesize = r->f.msize;
	if(messagesize > 8192+IOHDRSZ)
		messagesize = 8192+IOHDRSZ;
	r->f.msize = messagesize;
	if(strncmp(r->f.version, "9P", 2) != 0)
		r->f.version = "unknown";
	else
		r->f.version = "9P2000";
	fsreply(fs, r, nil);
}

void
fsflush(Fs *fs, Request *r, Fid *f)
{
	Request *or;

	or = remtag(&f->r, r->f.oldtag);
	if(or != nil){
		fsputfid(fs, or->fid);
		free(or);
	}
	fsreply(fs, r, nil);
}

void
fsattach(Fs *fs, Request *r, Fid *f)
{
	f->qid.type = QTDIR;
	f->qid.path = QID(0, Ttopdir);
	f->qid.vers = 0;

	if(r->f.uname[0])
		f->user = strdup(r->f.uname);
	else
		f->user = strdup("none");

	/* hold down the fid till the clunk */
	f->attached = 1;
	lock(fs);
	f->ref++;
	unlock(fs);

	r->f.qid = f->qid;
	fsreply(fs, r, nil);
}

void
fswalk(Fs *fs, Request *r, Fid *f)
{
	char *name;
	Dir d;
	int i, n, nqid, nwname;
	Qid qid, wqid[MAXWELEM];
	Fid *nf;
	char *err;

	if(f->attached == 0){
		fsreply(fs, r, Enofid);
		return;
	}

	nf = nil;
	if(r->f.fid != r->f.newfid){
		nf = fsgetfid(fs, r->f.newfid);
		nf->attached = f->attached;
		nf->open = f->open;
		nf->qid = f->qid;
		nf->user = strdup(f->user);
		nf->c = f->c;
		nf->wp = nf->buf;
		nf->rp = nf->wp;
		f = nf;
	}

	qid = f->qid;
	err = nil;
	nwname = r->f.nwname;
	nqid = 0;
	if(nwname > 0){
		for(; err == nil && nqid < nwname; nqid++){
			if(nqid >= MAXWELEM){
				err = "too many name elements";
				break;
			}
			name = r->f.wname[nqid];
			if(strcmp(name, "..") == 0)
				qid = parentqid(qid);
			else if(strcmp(name, ".") != 0){
				for(i = 0; ; i++){
					n = fsdirgen(fs, qid, i, &d, nil, 0);
					if(n < 0){
						err = Eexist;
						break;
					}
					if(n > 0 && strcmp(name, d.name) == 0){
						qid = d.qid;
						break;
					}
				}
			}
			wqid[nqid] = qid;
		}
		if(nf != nil && nqid < nwname)
			fsputfid(fs, nf);
		if(nqid == nwname)
			f->qid = qid;
	}

	memmove(r->f.wqid, wqid, nqid*sizeof(Qid));
	r->f.nwqid = nqid;
	fsreply(fs, r, err);
}

int
ingroup(char *user, char *group)
{
	Ndbtuple *t, *nt;
	Ndbs s;

	t = ndbsearch(db, &s, "group", group);
	if(t == nil)
		return 0;
	for(nt = t; nt; nt = nt->entry){
		if(strcmp(nt->attr, "uid") == 0)
		if(strcmp(nt->val, user) == 0)
			break;
	}
	ndbfree(t);
	return nt != nil;
}

int
userok(char *u, char *cname)
{
	Ndbtuple *t, *nt;
	Ndbs s;

	t = ndbsearch(db, &s, "console", cname);
	if(t == nil)
		return 0;

	for(nt = t; nt; nt = nt->entry){
		if(strcmp(nt->attr, "uid") == 0)
		if(strcmp(nt->val, u) == 0)
			break;
		if(strcmp(nt->attr, "gid") == 0)
		if(ingroup(u, nt->val))
			break;
	}
	ndbfree(t);

	return nt != nil;
}

int m2p[] ={
	[OREAD]		4,
	[OWRITE]	2,
	[ORDWR]		6
};

/*
 *  broadcast a message to all listeners
 */
void
bcastmsg(Fs *fs, Console *c, char *msg, int n)
{
	Fid *fl;

	for(fl = c->flist; fl; fl = fl->cnext){
		fromconsole(fl, msg, n);
		fskick(fs, fl);
	}
}

void
fsopen(Fs *fs, Request *r, Fid *f)
{
	int mode;
	Console *c;

	if(f->attached == 0){
		fsreply(fs, r, Enofid);
		return;
	}

	if(f->open){
		fsreply(fs, r, Eisopen);
		return;
	}

	mode = r->f.mode & 3;

	if((QTDIR & f->qid.type) && mode != OREAD){
		fsreply(fs, r, Eperm);
		return;
	}

	switch(TYPE(f->qid)){
	case Qdata:
		c = fs->cons[CONS(f->qid)];
		if(!userok(f->user, c->name)){
			fsreply(fs, r, Eperm);
			return;
		}
		f->rp = f->buf;
		f->wp = f->buf;
		f->c = c;
		lock(c);
		sprint(f->mbuf, "[%s] ", f->user);
		f->bufn = strlen(f->mbuf);
		f->used = 0;
		f->cnext = c->flist;
		c->flist = f;
		bcastmembers(fs, c, "+", f);
		if(c->pid == 0)
			fsreopen(fs, c);
		unlock(c);
		break;
	case Qctl:
		c = fs->cons[CONS(f->qid)];
		if(!userok(f->user, c->name)){
			fsreply(fs, r, Eperm);
			return;
		}
		f->c = c;
		break;
	case Qstat:
		c = fs->cons[CONS(f->qid)];
		if(!userok(f->user, c->name)){
			fsreply(fs, r, Eperm);
			return;
		}
		f->c = c;
		break;
	}

	f->open = 1;
	r->f.iounit = messagesize-IOHDRSZ;
	r->f.qid = f->qid;
	fsreply(fs, r, nil);
}

void
fscreate(Fs *fs, Request *r, Fid*)
{
	fsreply(fs, r, Eperm);
}

void
fsread(Fs *fs, Request *r, Fid *f)
{
	uchar *p, *e;
	int i, m, off;
	vlong offset;
	Dir d;
	char sbuf[ERRMAX];

	if(f->attached == 0){
		fsreply(fs, r, Enofid);
		return;
	}

	if((int)r->f.count < 0){
		fsreply(fs, r, Ebadcount);
		return;
	}

	if(QTDIR & f->qid.type){
		p = r->buf + IOHDRSZ;
		e = p + r->f.count;
		offset = r->f.offset;
		off = 0;
		for(i=0; p<e; i++, off+=m){
			m = fsdirgen(fs, f->qid, i, &d, p, e-p);
			if(m < 0)
				break;
			if(m > BIT16SZ && off >= offset)
				p += m;
		}
		r->f.data = (char*)r->buf + IOHDRSZ;
		r->f.count = (char*)p - r->f.data;
	} else {
		switch(TYPE(f->qid)){
		case Qdata:
			addreq(&f->r, r);
			fskick(fs, f);
			return;
		case Qctl:
			r->f.data = (char*)r->buf+IOHDRSZ;
			r->f.count = 0;
			break;
		case Qstat:
			if(r->f.count > sizeof(sbuf))
				r->f.count = sizeof(sbuf);
			i = pread(f->c->sfd, sbuf, r->f.count, r->f.offset);
			if(i < 0){
				errstr(sbuf, sizeof sbuf);
				fsreply(fs, r, sbuf);
				return;
			}
			r->f.data = sbuf;
			r->f.count = i;
			break;
		default:
			fsreply(fs, r, Eexist);
			return;
		}
	}
	fsreply(fs, r, nil);
}

void
fswrite(Fs *fs, Request *r, Fid *f)
{
	int i, eol = 0;

	if(f->attached == 0){
		fsreply(fs, r, Enofid);
		return;
	}

	if((int)r->f.count < 0){
		fsreply(fs, r, Ebadcount);
		return;
	}

	if(QTDIR & f->qid.type){
		fsreply(fs, r, Eperm);
		return;
	}

	switch(TYPE(f->qid)){
	default:
		fsreply(fs, r, Eperm);
		return;
	case Qctl:
		write(f->c->cfd, r->f.data, r->f.count);
		break;
	case Qdata:
		for(i = 0; i < r->f.count; i++){
			if(r->f.data[i] == '\n'){
				if(f->c->chat && f->used)
					eol = 1;
				if(f->c->cronly)
					r->f.data[i] = '\r';
			}
			else
				f->used = 1;
		}
		if(f->c->chat){
			fskick(fs, f);
			if(!f->used)
				break;
	
			if(f->bufn + r->f.count > Bufsize){
				r->f.count -= (f->bufn + r->f.count) % Bufsize;
				eol = 1;
			}
			strncat(f->mbuf, r->f.data, r->f.count);
			f->bufn += r->f.count;
			if(eol){
				bcastmsg(fs, f->c, f->mbuf, f->bufn);
				sprint(f->mbuf, "[%s] ", f->user);
				f->bufn = strlen(f->mbuf);
				f->used = 0;
			}
		}
		else
			write(f->c->fd, r->f.data, r->f.count);
		break;
	}
	fsreply(fs, r, nil);
}

void
fsclunk(Fs *fs, Request *r, Fid *f)
{
	Fid **l, *fl;
	Request *nr;

	if(f->open && TYPE(f->qid) == Qdata){
		while((nr = remreq(&f->r)) != nil){
			fsputfid(fs, f);
			free(nr);
		}

		lock(f->c);
		for(l = &f->c->flist; *l; l = &fl->cnext){
			fl = *l;
			if(fl == f){
				*l = fl->cnext;
				break;
			}
		}
		bcastmembers(fs, f->c, "-", f);
		if(f->c->ondemand && f->c->flist == nil)
			fsreopen(fs, f->c);
		unlock(f->c);
	}
	fsreply(fs, r, nil);
	fsputfid(fs, f);
}

void
fsremove(Fs *fs, Request *r, Fid*)
{
	fsreply(fs, r, Eperm);
}

void
fsstat(Fs *fs, Request *r, Fid *f)
{
	int i, n;
	Qid q;
	Dir d;

	q = parentqid(f->qid);
	for(i = 0; ; i++){
		r->f.stat = r->buf+IOHDRSZ;
		n = fsdirgen(fs, q, i, &d, r->f.stat, messagesize-IOHDRSZ);
		if(n < 0){
			fsreply(fs, r, Eexist);
			return;
		}
		r->f.nstat = n;
		if(r->f.nstat > BIT16SZ && d.qid.path == f->qid.path)
			break;
	}
	fsreply(fs, r, nil);
}

void
fswstat(Fs *fs, Request *r, Fid*)
{
	fsreply(fs, r, Eperm);
}

void
fsreply(Fs *fs, Request *r, char *err)
{
	int n;
	uchar buf[8192+IOHDRSZ];

	if(err){
		r->f.type = Rerror;
		r->f.ename = err;
	}
	n = convS2M(&r->f, buf, messagesize);
	if(debug)
		fprint(2, "%F path %llux n=%d\n", &r->f, r->fid->qid.path, n);
	fsputfid(fs, r->fid);
	if(write(fs->fd, buf, n) != n)
		fatal("unmounted");
	free(r);
}

/*
 *  called whenever input or a read request has been received
 */
void
fskick(Fs *fs, Fid *f)
{
	Request *r;
	char *p, *rp, *wp, *ep;
	int i;

	lock(f);
	while(f->rp != f->wp){
		r = remreq(&f->r);
		if(r == nil)
			break;
		p = (char*)r->buf;
		rp = f->rp;
		wp = f->wp;
		ep = &f->buf[Bufsize];
		for(i = 0; i < r->f.count && rp != wp; i++){
			*p++ = *rp++;
			if(rp >= ep)
				rp = f->buf;
		}
		f->rp = rp;
		r->f.data = (char*)r->buf;
		r->f.count = p - (char*)r->buf;
		fsreply(fs, r, nil);
	}
	unlock(f);
}

void
usage(void)
{
	fprint(2, "usage: consolefs [-d] [-m mount-point] [-c console-db]\n");
	threadexitsall("usage");
}

void
threadmain(int argc, char **argv)
{
	fmtinstall('F', fcallfmt);

	ARGBEGIN{
	case 'd':
		debug++;
		break;
	case 'c':
		consoledb = ARGF();
		if(consoledb == nil)
			usage();
		break;
	case 'm':
		mntpt = ARGF();
		if(mntpt == nil)
			usage();
		break;
	}ARGEND;

	db = ndbopen(consoledb);
	if(db == nil)
 		fatal("can't open %s: %r", consoledb);

	fsmount(mntpt);
}