summaryrefslogtreecommitdiff
path: root/sys/src/cmd/aux/consolefs.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/aux/consolefs.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/aux/consolefs.c')
-rwxr-xr-xsys/src/cmd/aux/consolefs.c1289
1 files changed, 1289 insertions, 0 deletions
diff --git a/sys/src/cmd/aux/consolefs.c b/sys/src/cmd/aux/consolefs.c
new file mode 100755
index 000000000..180431bd4
--- /dev/null
+++ b/sys/src/cmd/aux/consolefs.c
@@ -0,0 +1,1289 @@
+#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("#s/consoles", AEXIST) < 0){
+ srv = create("#s/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)
+ fatal("unmounted");
+
+ if(convM2S(r->buf, n, &r->f) == 0){
+ fprint(2, "can't convert %ux %ux %ux\n", r->buf[0],
+ r->buf[1], r->buf[2]);
+ free(r);
+ continue;
+ }
+
+
+ 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, "9P2000", 6) != 0){
+ fsreply(fs, r, "unrecognized 9P version");
+ return;
+ }
+ 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);
+}