#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)
                       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, "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);
}