#include #include #include #include #include #include #include /* * This fs presents a 1 level file system. It contains * 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; 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 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: 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: 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 0; } /* * 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; } } 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; } } c = emalloc(sizeof(Console)); fs->cons[fs->ncons++] = c; c->name = strdup(name); c->dev = strdup(dev); 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")) 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); 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]; 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, 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++){ if(fsdirgen(fs, qid, i, &d, nil, 0) < 0){ err = Eexist; break; } if(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 }; 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); 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(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; pqid, i, &d, p, e-p); if(m <= BIT16SZ) break; if(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; if(f->attached == 0){ fsreply(fs, r, Enofid); return; } if(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: if(f->c->cronly) for(i = 0; i < r->f.count; i++) if(r->f.data[i] == '\n') r->f.data[i] = '\r'; 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; Qid q; Dir d; q = parentqid(f->qid); for(i = 0; ; i++){ r->f.stat = r->buf+IOHDRSZ; if((r->f.nstat = fsdirgen(fs, q, i, &d, r->f.stat, messagesize-IOHDRSZ)) <= BIT16SZ){ fsreply(fs, r, Eexist); return; } if(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); }