10 * This fs presents a 1 level file system. It contains
11 * up to three files per console (xxx and xxxctl and xxxstat)
14 typedef struct Console Console;
15 typedef struct Fid Fid;
16 typedef struct Request Request;
17 typedef struct Reqlist Reqlist;
22 /* last 5 bits of qid.path */
23 Textern= 0, /* fake parent of top level */
24 Ttopdir, /* top level directory */
29 Bufsize= 32*1024, /* chars buffered per reader */
30 Maxcons= 64, /* maximum consoles */
31 Nhash= 64, /* Fid hash buckets */
34 #define TYPE(x) (((ulong)x.path) & 0xf)
35 #define CONS(x) ((((ulong)x.path) >> 4)&0xfff)
36 #define QID(c, x) (((c)<<4) | (x))
57 Fid *next; /* hash list */
58 Fid *cnext; /* list of Fid's on a console */
65 char mbuf[Bufsize]; /* message */
76 Reqlist r; /* active read requests */
87 int ondemand; /* open only on demand */
88 int chat; /* chat consoles are special */
90 int pid; /* pid of reader */
96 Fid *flist; /* open fids to broadcast to */
103 int fd; /* to kernel mount point */
106 Console *cons[Maxcons];
110 extern void console(Fs*, char*, char*, int, int, int);
111 extern Fs* fsmount(char*);
113 extern void fsreader(void*);
114 extern void fsrun(void*);
115 extern Fid* fsgetfid(Fs*, int);
116 extern void fsputfid(Fs*, Fid*);
117 extern int fsdirgen(Fs*, Qid, int, Dir*, uchar*, int);
118 extern void fsreply(Fs*, Request*, char*);
119 extern void fskick(Fs*, Fid*);
120 extern int fsreopen(Fs*, Console*);
122 extern void fsversion(Fs*, Request*, Fid*);
123 extern void fsflush(Fs*, Request*, Fid*);
124 extern void fsauth(Fs*, Request*, Fid*);
125 extern void fsattach(Fs*, Request*, Fid*);
126 extern void fswalk(Fs*, Request*, Fid*);
127 extern void fsclwalk(Fs*, Request*, Fid*);
128 extern void fsopen(Fs*, Request*, Fid*);
129 extern void fscreate(Fs*, Request*, Fid*);
130 extern void fsread(Fs*, Request*, Fid*);
131 extern void fswrite(Fs*, Request*, Fid*);
132 extern void fsclunk(Fs*, Request*, Fid*);
133 extern void fsremove(Fs*, Request*, Fid*);
134 extern void fsstat(Fs*, Request*, Fid*);
135 extern void fswstat(Fs*, Request*, Fid*);
138 void (*fcall[])(Fs*, Request*, Fid*) =
141 [Tversion] fsversion,
155 char Eperm[] = "permission denied";
156 char Eexist[] = "file does not exist";
157 char Enotdir[] = "not a directory";
158 char Eisopen[] = "file already open";
159 char Ebadcount[] = "bad read/write count";
160 char Enofid[] = "no such fid";
162 char *consoledb = "/lib/ndb/consoledb";
163 char *mntpt = "/mnt/consoles";
165 int messagesize = 8192+IOHDRSZ;
168 fatal(char *fmt, ...)
173 write(2, "consolefs: ", 10);
175 vseprint(buf, buf+1024, fmt, arg);
177 write(2, buf, strlen(buf));
190 fatal("malloc failed: %r");
199 * any request that can get queued for a delayed reply
202 allocreq(Fs *fs, int bufsize)
206 r = emalloc(sizeof(Request)+bufsize);
213 * for maintaining lists of requests
216 addreq(Reqlist *l, Request *r)
229 * remove the first request from a list of requests
245 * remove a request with the given tag from a list of requests
248 remtag(Reqlist *l, int tag)
254 for(or = *ll; or; or = or->next){
255 if(or->f.tag == tag){
270 return (Qid){QID(0, Textern), 0, QTDIR};
272 return (Qid){QID(0, Ttopdir), 0, QTDIR};
276 fsdirgen(Fs *fs, Qid parent, int i, Dir *d, uchar *buf, int nbuf)
278 static char name[64];
282 d->uid = d->gid = d->muid = "network";
284 d->atime = time(nil);
289 switch(TYPE(parent)){
294 d->mode = DMDIR|0555;
296 d->qid.path = QID(0, Ttopdir);
301 if(xcons >= fs->ncons)
303 p = fs->cons[xcons]->name;
306 if(fs->cons[xcons]->cfd < 0)
308 snprint(name, sizeof name, "%sctl", p);
310 d->qid.type = QTFILE;
311 d->qid.path = QID(xcons, Qctl);
315 if(fs->cons[xcons]->sfd < 0)
317 snprint(name, sizeof name, "%sstat", p);
319 d->qid.type = QTFILE;
320 d->qid.path = QID(xcons, Qstat);
324 d->qid.type = QTFILE;
325 d->qid.path = QID(xcons, Qdata);
336 return convD2M(d, buf, nbuf);
341 * mount the user interface and start a request processor
352 fs = emalloc(sizeof(Fs));
355 fatal("opening pipe: %r");
357 /* start up the file system process */
360 proccreate(fsrun, v, 16*1024);
362 /* Typically mounted before /srv exists */
363 if(access("#s/consoles", AEXIST) < 0){
364 srv = create("#s/consoles", OWRITE, 0666);
368 n = sprint(buf, "%d", pfd[1]);
369 if(write(srv, buf, n) < 0)
370 fatal("write srv: %r");
375 mount(pfd[1], -1, mntpt, MBEFORE, "");
384 fsreopen(Fs* fs, Console *c)
390 if(postnote(PNPROC, c->pid, "reopen") != 0)
391 fprint(2, "postnote failed: %r\n");
404 if(c->flist == nil && c->ondemand)
407 c->fd = open(c->dev, ORDWR);
411 snprint(buf, sizeof(buf), "%sctl", c->dev);
412 c->cfd = open(buf, ORDWR);
413 fprint(c->cfd, "b%d", c->speed);
415 snprint(buf, sizeof(buf), "%sstat", c->dev);
416 c->sfd = open(buf, OREAD);
420 proccreate(fsreader, v, 16*1024);
426 change(Fs *fs, Console *c, int doreopen, int speed, int cronly, int ondemand)
430 if(speed != c->speed){
434 if(ondemand != c->ondemand){
435 c->ondemand = ondemand;
446 * create a console interface
449 console(Fs* fs, char *name, char *dev, int speed, int cronly, int ondemand)
455 if(fs->ncons >= Maxcons)
456 fatal("too many consoles, too little time");
459 for(i = 0; i < fs->ncons; i++){
461 if(strcmp(name, c->name) == 0){
462 if(strcmp(dev, c->dev) != 0){
465 c->dev = strdup(dev);
469 change(fs, c, doreopen, speed, cronly, ondemand);
473 #ifdef sapedoesntlikethis
475 * The code below prevents this from working. I can't
476 * think of scenarios where the code below actually helps
479 * console=borneo dev=/dev/eia1
482 * console=tottie dev=/dev/eia1
486 for(i = 0; i < fs->ncons; i++){
488 if(strcmp(dev, c->dev) == 0){
489 /* at least a rename */
491 c->name = strdup(name);
493 change(fs, c, doreopen, speed, cronly, ondemand);
498 c = emalloc(sizeof(Console));
499 fs->cons[fs->ncons] = c;
501 c->name = strdup(name);
502 c->dev = strdup(dev);
503 if(strcmp(c->dev, "/dev/null") == 0)
510 change(fs, c, 1, speed, cronly, ondemand);
514 * buffer data from console to a client.
515 * circular q with writer able to catch up to reader.
516 * the reader may miss data but always sees an in order sequence.
519 fromconsole(Fid *f, char *p, int n)
527 ep = f->buf + sizeof(f->buf);
538 /* we overtook the read pointer, push it up so readers always
539 * see the tail of what was written
552 * broadcast a list of members to all listeners
555 bcastmembers(Fs *fs, Console *c, char *msg, Fid *f)
561 sprint(buf, "[%s%s", msg, f->user);
562 for(fl = c->flist; fl != nil && strlen(buf) + 64 < sizeof(buf); fl = fl->cnext){
566 strcat(buf, fl->user);
571 for(fl = c->flist; fl; fl = fl->cnext){
572 fromconsole(fl, buf, n);
578 handler(void*, char *msg)
580 if(strstr(msg, "reopen") != nil ||
581 strstr(msg, "write on closed pipe") != nil)
587 * a process to read console output and broadcast it (one per console)
607 n = read(c->fd, buf, sizeof(buf));
611 for(fl = c->flist; fl; fl = fl->cnext){
612 fromconsole(fl, buf, n);
624 int cronly, speed, ondemand;
628 /* start a listener for each console */
638 for(nt = t; nt; nt = nt->entry){
639 if(strcmp(nt->attr, "console") == 0)
641 else if(strcmp(nt->attr, "dev") == 0)
643 else if(strcmp(nt->attr, "speed") == 0)
644 speed = atoi(nt->val);
645 else if(strcmp(nt->attr, "cronly") == 0)
647 else if(strcmp(nt->attr, "openondemand") == 0)
650 if(dev != nil && cons != nil)
651 console(fs, cons, dev, speed, cronly, ondemand);
659 * a request processor (one per Fs)
677 d = dirstat(consoledb);
678 if(d != nil && d->mtime != dbmtime){
683 r = allocreq(fs, messagesize);
684 while((n = read9pmsg(fs->fd, r->buf, messagesize)) == 0)
689 if(convM2S(r->buf, n, &r->f) == 0){
690 fprint(2, "can't convert %ux %ux %ux\n", r->buf[0],
691 r->buf[1], r->buf[2]);
697 f = fsgetfid(fs, r->f.fid);
700 fprint(2, "%F path %llux\n", &r->f, f->qid.path);
704 (*fcall[t])(fs, r, f);
709 fsgetfid(Fs *fs, int fid)
714 for(f = fs->hash[fid%Nhash]; f; f = f->next){
722 nf = emalloc(sizeof(Fid));
723 nf->next = fs->hash[fid%Nhash];
724 fs->hash[fid%Nhash] = nf;
734 fsputfid(Fs *fs, Fid *f)
743 for(l = &fs->hash[f->fid%Nhash]; nf = *l; l = &nf->next)
754 fsauth(Fs *fs, Request *r, Fid*)
756 fsreply(fs, r, "consolefs: authentication not required");
760 fsversion(Fs *fs, Request *r, Fid*)
763 if(r->f.msize < 256){
764 fsreply(fs, r, "message size too small");
767 messagesize = r->f.msize;
768 if(messagesize > 8192+IOHDRSZ)
769 messagesize = 8192+IOHDRSZ;
770 r->f.msize = messagesize;
771 if(strncmp(r->f.version, "9P2000", 6) != 0){
772 fsreply(fs, r, "unrecognized 9P version");
775 r->f.version = "9P2000";
781 fsflush(Fs *fs, Request *r, Fid *f)
785 or = remtag(&f->r, r->f.oldtag);
787 fsputfid(fs, or->fid);
794 fsattach(Fs *fs, Request *r, Fid *f)
797 f->qid.path = QID(0, Ttopdir);
801 f->user = strdup(r->f.uname);
803 f->user = strdup("none");
805 /* hold down the fid till the clunk */
816 fswalk(Fs *fs, Request *r, Fid *f)
820 int i, n, nqid, nwname;
821 Qid qid, wqid[MAXWELEM];
825 if(f->attached == 0){
826 fsreply(fs, r, Enofid);
831 if(r->f.fid != r->f.newfid){
832 nf = fsgetfid(fs, r->f.newfid);
833 nf->attached = f->attached;
836 nf->user = strdup(f->user);
845 nwname = r->f.nwname;
848 for(; err == nil && nqid < nwname; nqid++){
849 if(nqid >= MAXWELEM){
850 err = "too many name elements";
853 name = r->f.wname[nqid];
854 if(strcmp(name, "..") == 0)
855 qid = parentqid(qid);
856 else if(strcmp(name, ".") != 0){
858 n = fsdirgen(fs, qid, i, &d, nil, 0);
863 if(n > 0 && strcmp(name, d.name) == 0){
871 if(nf != nil && nqid < nwname)
877 memmove(r->f.wqid, wqid, nqid*sizeof(Qid));
883 ingroup(char *user, char *group)
888 t = ndbsearch(db, &s, "group", group);
891 for(nt = t; nt; nt = nt->entry){
892 if(strcmp(nt->attr, "uid") == 0)
893 if(strcmp(nt->val, user) == 0)
901 userok(char *u, char *cname)
906 t = ndbsearch(db, &s, "console", cname);
910 for(nt = t; nt; nt = nt->entry){
911 if(strcmp(nt->attr, "uid") == 0)
912 if(strcmp(nt->val, u) == 0)
914 if(strcmp(nt->attr, "gid") == 0)
915 if(ingroup(u, nt->val))
930 * broadcast a message to all listeners
933 bcastmsg(Fs *fs, Console *c, char *msg, int n)
937 for(fl = c->flist; fl; fl = fl->cnext){
938 fromconsole(fl, msg, n);
944 fsopen(Fs *fs, Request *r, Fid *f)
949 if(f->attached == 0){
950 fsreply(fs, r, Enofid);
955 fsreply(fs, r, Eisopen);
959 mode = r->f.mode & 3;
961 if((QTDIR & f->qid.type) && mode != OREAD){
962 fsreply(fs, r, Eperm);
966 switch(TYPE(f->qid)){
968 c = fs->cons[CONS(f->qid)];
969 if(!userok(f->user, c->name)){
970 fsreply(fs, r, Eperm);
977 sprint(f->mbuf, "[%s] ", f->user);
978 f->bufn = strlen(f->mbuf);
982 bcastmembers(fs, c, "+", f);
988 c = fs->cons[CONS(f->qid)];
989 if(!userok(f->user, c->name)){
990 fsreply(fs, r, Eperm);
996 c = fs->cons[CONS(f->qid)];
997 if(!userok(f->user, c->name)){
998 fsreply(fs, r, Eperm);
1006 r->f.iounit = messagesize-IOHDRSZ;
1008 fsreply(fs, r, nil);
1012 fscreate(Fs *fs, Request *r, Fid*)
1014 fsreply(fs, r, Eperm);
1018 fsread(Fs *fs, Request *r, Fid *f)
1026 if(f->attached == 0){
1027 fsreply(fs, r, Enofid);
1031 if((int)r->f.count < 0){
1032 fsreply(fs, r, Ebadcount);
1036 if(QTDIR & f->qid.type){
1037 p = r->buf + IOHDRSZ;
1039 offset = r->f.offset;
1041 for(i=0; p<e; i++, off+=m){
1042 m = fsdirgen(fs, f->qid, i, &d, p, e-p);
1045 if(m > BIT16SZ && off >= offset)
1048 r->f.data = (char*)r->buf + IOHDRSZ;
1049 r->f.count = (char*)p - r->f.data;
1051 switch(TYPE(f->qid)){
1057 r->f.data = (char*)r->buf+IOHDRSZ;
1061 if(r->f.count > sizeof(sbuf))
1062 r->f.count = sizeof(sbuf);
1063 i = pread(f->c->sfd, sbuf, r->f.count, r->f.offset);
1065 errstr(sbuf, sizeof sbuf);
1066 fsreply(fs, r, sbuf);
1073 fsreply(fs, r, Eexist);
1077 fsreply(fs, r, nil);
1081 fswrite(Fs *fs, Request *r, Fid *f)
1085 if(f->attached == 0){
1086 fsreply(fs, r, Enofid);
1090 if((int)r->f.count < 0){
1091 fsreply(fs, r, Ebadcount);
1095 if(QTDIR & f->qid.type){
1096 fsreply(fs, r, Eperm);
1100 switch(TYPE(f->qid)){
1102 fsreply(fs, r, Eperm);
1105 write(f->c->cfd, r->f.data, r->f.count);
1108 for(i = 0; i < r->f.count; i++){
1109 if(r->f.data[i] == '\n'){
1110 if(f->c->chat && f->used)
1113 r->f.data[i] = '\r';
1123 if(f->bufn + r->f.count > Bufsize){
1124 r->f.count -= (f->bufn + r->f.count) % Bufsize;
1127 strncat(f->mbuf, r->f.data, r->f.count);
1128 f->bufn += r->f.count;
1130 bcastmsg(fs, f->c, f->mbuf, f->bufn);
1131 sprint(f->mbuf, "[%s] ", f->user);
1132 f->bufn = strlen(f->mbuf);
1137 write(f->c->fd, r->f.data, r->f.count);
1140 fsreply(fs, r, nil);
1144 fsclunk(Fs *fs, Request *r, Fid *f)
1149 if(f->open && TYPE(f->qid) == Qdata){
1150 while((nr = remreq(&f->r)) != nil){
1156 for(l = &f->c->flist; *l; l = &fl->cnext){
1163 bcastmembers(fs, f->c, "-", f);
1164 if(f->c->ondemand && f->c->flist == nil)
1168 fsreply(fs, r, nil);
1173 fsremove(Fs *fs, Request *r, Fid*)
1175 fsreply(fs, r, Eperm);
1179 fsstat(Fs *fs, Request *r, Fid *f)
1185 q = parentqid(f->qid);
1187 r->f.stat = r->buf+IOHDRSZ;
1188 n = fsdirgen(fs, q, i, &d, r->f.stat, messagesize-IOHDRSZ);
1190 fsreply(fs, r, Eexist);
1194 if(r->f.nstat > BIT16SZ && d.qid.path == f->qid.path)
1197 fsreply(fs, r, nil);
1201 fswstat(Fs *fs, Request *r, Fid*)
1203 fsreply(fs, r, Eperm);
1207 fsreply(Fs *fs, Request *r, char *err)
1210 uchar buf[8192+IOHDRSZ];
1216 n = convS2M(&r->f, buf, messagesize);
1218 fprint(2, "%F path %llux n=%d\n", &r->f, r->fid->qid.path, n);
1219 fsputfid(fs, r->fid);
1220 if(write(fs->fd, buf, n) != n)
1226 * called whenever input or a read request has been received
1229 fskick(Fs *fs, Fid *f)
1232 char *p, *rp, *wp, *ep;
1236 while(f->rp != f->wp){
1243 ep = &f->buf[Bufsize];
1244 for(i = 0; i < r->f.count && rp != wp; i++){
1250 r->f.data = (char*)r->buf;
1251 r->f.count = p - (char*)r->buf;
1252 fsreply(fs, r, nil);
1260 fprint(2, "usage: consolefs [-d] [-m mount-point] [-c console-db]\n");
1261 threadexitsall("usage");
1265 threadmain(int argc, char **argv)
1267 fmtinstall('F', fcallfmt);
1275 if(consoledb == nil)
1285 db = ndbopen(consoledb);
1287 fatal("can't open %s: %r", consoledb);