/* * This file is part of Jehanne. * * Copyright (C) 2015 Giacomo Tesio * * Jehanne is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License. * * Jehanne is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Jehanne. If not, see . */ #include #include #include <9P2000.h> #include "console.h" enum { Maxfdata = IODATASZ, Miniosize = IOHDRSZ+ScreenBufferSize, Maxiosize = IOHDRSZ+Maxfdata, }; typedef enum { Initializing, Mounted, Unmounted, /* fsserve() loop while status < Unmounted */ } Status; int systemwide; static Status status; static int rawmode; static int fspid; static void *data; enum { Qroot, Qcons, Qconsctl, Nqid, Qinput, Qoutput, Nhqid, }; static struct Qtab { char *name; int mode; int type; int length; } qtab[Nhqid] = { "/", DMDIR|0555, QTDIR, 0, "cons", 0666, 0, 0, "consctl", 0222, 0, 0, "", 0, 0, 0, "gconsin", /* data from input will be written here */ DMAPPEND|DMEXCL|0222, 0, 0, "gconsout", /* data to output will be read here */ DMEXCL|0444, 0, 0, }; /* message size for the exported name space * * between Miniosize and Maxiosize */ static int messagesize = Maxiosize; /* Initialized on Qinput open */ static Buffer *input; /* written on Twrite(Qinput), read on Tread(Qcons) */ /* Initialized on Qoutput open */ static Buffer *output; /* written on Twrite(Qcons), read on Tread(Qoutput) */ static uint32_t nextoutputcol; /* linked list of known fids * * NOTE: we don't free() Fids, because there's no appropriate point * in 9P2000 to do that, except the Tclunk of the attach fid, * that in our case corresponds to shutdown * (the kernel is our single client, we are doomed to trust it) */ typedef struct Fid Fid; struct Fid { int32_t fd; Qid qid; int16_t opened; /* -1 when not open */ Fid *next; }; #define ISCLOSED(f) (f != nil && f->opened == -1) static Fid *fids; static Fid **ftail; static Fid *external; /* attach fid of the last mount() in gconsole.c */ static Fid *inputfid; /* from Topen(Qinput) */ static Fid *outputfid; /* from Topen(Qoutput) */ static Fid* createFid(int32_t fd, Qid qid) { Fid *fid; fid = (Fid*)malloc(sizeof(Fid)); if(fid){ fid->fd = fd; fid->qid = qid; fid->opened = -1; fid->next = nil; *ftail = fid; ftail = &fid->next; } return fid; } static Fid* findFid(int32_t fd) { Fid *fid; fid = fids; while(fid != nil && fid->fd != fd) fid = fid->next; return fid; } /* utilities */ static int readmessage(int fd, Fcall *req) { int n; n = read9pmsg(fd, data, Maxiosize); if(n > 0) if(convM2S(data, n, req) == 0){ debug("readmessage: convM2S returns 0\n"); return -1; } else { debug("serve9p %d: <-%F\n", fspid, req); return 1; } if(n < 0){ debug("readmessage: read9pmsg: %r\n"); return -1; } return 0; } static int sendmessage(int fd, Fcall *rep) { int n; static uint8_t repdata[Maxiosize]; n = convS2M(rep, repdata, Maxiosize); if(n == 0) { debug("sendmessage: convS2M error\n"); return 0; } if(write(fd, repdata, n) != n) { debug("sendmessage: write\n"); return 0; } debug("serve9p %d: ->%F\n", fspid, rep); return 1; } /* queue of pending reads */ typedef struct AsyncOp AsyncOp; struct AsyncOp { uint32_t tag; uint32_t count; AsyncOp *next; }; typedef struct OpQueue OpQueue; struct OpQueue { AsyncOp *head; AsyncOp **tail; }; #define qempty(q) (q->tail == &q->head) static OpQueue consreads; static OpQueue conswrites; static AsyncOp *outputread; /* only one process can access Qoutput */ static void qinit(OpQueue *q) { q->tail = &q->head; } static AsyncOp * opalloc(uint32_t tag, uint32_t count) { AsyncOp *read; read = (AsyncOp*)malloc(sizeof(AsyncOp)); if(!read) return nil; read->tag = tag; read->count = count; read->next = nil; return read; } static int enqueue(OpQueue *queue, uint32_t tag, uint32_t count) { AsyncOp *read; debug("enqueue(%#p, %d, %d)\n", queue, tag, count); if((read = opalloc(tag, count)) == nil) return 0; *queue->tail = read; queue->tail = &read->next; return 1; } static void dequeue(OpQueue *queue, uint32_t tag) { AsyncOp *op, *prev; if(qempty(queue)) return; prev = nil; op = queue->head; while(op && op->tag != tag){ prev = op; op = op->next; } if(op == nil) return; if(prev == nil) queue->head = op->next; else prev->next = op->next; if(queue->tail == &op->next) queue->tail = &prev->next; else queue->tail = &queue->head; free(op); } static int syncOutput(int connection, Fcall *rep) { uint32_t l; //, i; int w; char *d; AsyncOp *aw; OpQueue *queue; if(outputread == nil || bempty(output)) return 1; /* continue */ queue = &conswrites; l = outputread->count; d = bread(output, &l); rep->type = Rread; rep->tag = outputread->tag; rep->count = l; rep->data = d; w = sendmessage(connection, rep); if(w <= 0){ /* we had an error on the connection: stop to fsserve() */ debug("serve9p %d: syncOutput: %d bytes ready, but sendmessage returns %d\n", fspid, rep->count, w); return w; } free(outputread); outputread = nil; if(bempty(output)){ w = 0; rep->type = Rwrite; while(!qempty(queue)){ aw = queue->head; rep->tag = aw->tag; rep->count = aw->count; w = sendmessage(connection, rep); if(w <= 0){ /* we had an error on the connection: stop to fsserve() */ debug("serve9p %d: syncOutput: %d bytes written, but sendmessage returns %d\n", fspid, rep->count, w); return w; } queue->head = aw->next; if(queue->head == nil){ queue->tail = &queue->head; } free(aw); } } return 1; } /* distribute available data to pending Tread(cons) */ static int syncCons(int connection, Fcall *rep) { AsyncOp *ar; OpQueue *queue; uint32_t l; int w; char *d; queue = &consreads; if(qempty(queue) || bempty(input)) return 1; /* continue */ /* here we have a pending queue and either (checked by fscons) * * - a full line verified by blineready, or * - a partial line with either rawmode active or linecontrol inactive * * in both case we have something to send and somebody ready to receive */ debug("syncCons(queue = %#p)\n", queue); rep->type = Rread; w = 0; while(!qempty(queue) && !bempty(input)){ ar = queue->head; l = ar->count; if(rawmode) d = bread(input, &l); else d = breadln(input, &l); rep->tag = ar->tag; rep->count = l; rep->data = d; w = sendmessage(connection, rep); if(w <= 0){ /* we had an error on the connection: stop to fsserve() */ debug("serve9p %d: syncCons: %d bytes ready, but sendmessage returns %d\n", fspid, rep->count, w); return w; } queue->head = ar->next; if(queue->head == nil){ queue->tail = &queue->head; } free(ar); } return 1; } static int closeOutput(int connection, Fcall *rep) { int w; if(!outputread) return 1; rep->type = Rread; rep->count = 0; rep->data = nil; rep->tag = outputread->tag; w = sendmessage(connection, rep); if(w <= 0){ /* we had an error on the connection: stop to fsserve() */ debug("serve9p %d: qclose: sendmessage returns %d\n", fspid, w); return w; } free(outputread); outputread = nil; return 1; } static int closeConsReaders(int connection, Fcall *rep) { OpQueue *queue; AsyncOp *op; int w; queue = &consreads; if(qempty(queue)) return 1; debug("qclose(queue = %#p)\n", queue, data); rep->type = Rread; rep->count = 0; rep->data = nil; op = queue->head; while(op != nil){ queue->head = op->next; rep->tag = op->tag; w = sendmessage(connection, rep); if(w <= 0){ /* we had an error on the connection: stop to fsserve() */ debug("serve9p %d: qclose: sendmessage returns %d\n", fspid, w); return w; } free(op); op = queue->head; } queue->tail = &queue->head; return 1; } /* line buffering and control */ static void rawappender(char c, Buffer* b) { b->data[b->written++] = c; } /* build the input line when linecontrol is on */ static void lineprinter(char c, Buffer* b) { switch(c){ case '\n': case '\r': b->linewidth = 0; if(crnl){ b->data[b->written++] = '\r'; b->data[b->written++] = '\n'; return; } break; case '\b': /* decrease line width a bit */ if(b->linewidth) --b->linewidth; /* overwrite with a space */ b->data[b->written++] = '\b'; b->data[b->written++] = ' '; break; case '\t': /* turns \t to spaces */ do { lineprinter(' ', b); } while(b->linewidth&7); return; default: ++b->linewidth; break; } b->data[b->written++] = c; } /* build the input line when linecontrol is on * * note that this is only used for input, thus do not * compute linewidth, since \b would have variable width * according to the position (thanks \t!). */ static void linebuilder(char c, Buffer* b) { if (b->ctrld < b->size) return; /* ignore everything after ^D, until it has been consumed */ if(crnl && c == '\n' && b->written > 0 && b->data[b->written-1] == '\n') return; /* avoid duplications */ switch(c){ case '\004': // ctrl-d b->ctrld = b->written; break; case 0x15: // ctrl-u b->written = b->read; break; case 0x7f: // backspace case '\b': if(!bempty(b)) --b->written; break; case '\n': ++b->newlines; /* only newline count for us */ b->data[b->written++] = c; break; case '\r': if(crnl){ /* turn \r to \n */ b->data[b->written++] = '\n'; ++b->newlines; } else { b->data[b->written++] = c; } break; default: b->data[b->written++] = c; break; } } /* 9p message handlers */ static char * invalidioreq(Fcall *req) { if(req->count > messagesize || req->count < 0) return "bad read/write count"; return nil; } static char* gethostowner(void) { int f, r; char *res; res = (char*)malloc(256); if(res == nil) sysfatal("out of memory"); f = open("#c/hostowner", OREAD); if(f < 0) sysfatal("open(#c/hostowner) %r"); r = read(f, res, 255); if(r < 0) sysfatal("read(#c/hostowner)"); res[r] = '\0'; close(f); return res; } static int fillstat(uint64_t path, Dir *d) { struct Qtab *t; static char *filesowner; if(filesowner == nil) filesowner = gethostowner(); memset(d, 0, sizeof(Dir)); d->uid = filesowner; d->gid = filesowner; d->muid = ""; d->qid = (Qid){path, 0, 0}; d->atime = time(0); t = qtab + path; d->name = t->name; d->qid.type = t->type; d->mode = t->mode; d->length = t->length; return 1; } static int32_t rootread(Fid *fid, uint8_t *buf, int64_t off, int32_t cnt, int blen) { int32_t m, n; int64_t i, pos; Dir d; n = 0; pos = 0; for (i = 1; i < Nqid; i++){ fillstat(i, &d); m = convD2M(&d, &buf[n], blen-n); if(off <= pos){ if(m <= BIT16SZ || m > cnt) break; n += m; cnt -= m; } pos += m; } return n; } static int rerror(Fcall *rep, char *err) { debug("rerror: %s\n", err); rep->type = Rerror; rep->ename = err; return 1; } static int rpermission(Fcall *req, Fcall *rep) { return rerror(rep, "permission denied"); } static int rattach(Fcall *req, Fcall *rep) { char *spec; Fid *f; spec = req->aname; if(spec && spec[0]) return rerror(rep, "bad attach specifier"); if(external != nil && !systemwide){ /* when not system wide (aka screenconsole), we expect 3 valid Tattach: * 1 for the process that will send us the input, writing Qinput * 1 for the process that will print our output, reading Qoutput * 1 for the rest of the children */ return rerror(rep, "device busy"); } f = findFid(req->fid); if(f == nil) f = createFid(req->fid, (Qid){Qroot, 0, QTDIR}); if(f == nil) return rerror(rep, "out of memory"); if(external == nil && input != nil && output != nil){ external = f; status = Mounted; } rep->type = Rattach; rep->qid = f->qid; return 1; } static int rauth(Fcall *req, Fcall *rep) { return rerror(rep, "authentication not required"); } static int rversion(Fcall *req, Fcall *rep) { if(req->msize < Miniosize) return rerror(rep, "message size too small"); messagesize = req->msize; if(messagesize > Maxiosize) messagesize = Maxiosize; rep->type = Rversion; rep->msize = messagesize; if(*req->version == 0 || strncmp(req->version, "9P2000", 6) == 0) rep->version = "9P2000"; else rep->version = "unknown"; return 1; } static int rflush(Fcall *req, Fcall *rep) { if(outputread != nil && req->tag == outputread->tag){ free(outputread); outputread = nil; }else dequeue(&consreads, req->oldtag); rep->type = Rflush; return 1; } static int rwalk(Fcall *req, Fcall *rep) { Fid *f, *n; Qid q; f = findFid(req->fid); if(f == nil) return rerror(rep, "bad fid"); if(req->nwname > 1 || (req->nwname == 1 && f->qid.path != Qroot)) return rerror(rep, "walk in non directory"); if(f->opened != -1) return rerror(rep, "fid in use"); if(req->nwname == 1){ if (strcmp(qtab[Qcons].name, req->wname[0]) == 0) { q = (Qid){Qcons, 0, 0}; } else if (strcmp(qtab[Qconsctl].name, req->wname[0]) == 0) { q = (Qid){Qconsctl, 0, 0}; } else if (!input && strcmp(qtab[Qinput].name, req->wname[0]) == 0) { q = (Qid){Qinput, 0, 0}; } else if (!output && strcmp(qtab[Qoutput].name, req->wname[0]) == 0) { q = (Qid){Qoutput, 0, 0}; } else { return rerror(rep, "file does not exist"); } } else { q = f->qid; } if(req->fid == req->newfid){ n = f; } else { n = findFid(req->newfid); if(n == nil) n = createFid(req->newfid, q); else if(n->opened != -1) return rerror(rep, "newfid already in use"); if(n == nil) return rerror(rep, "out of memory"); } n->qid = q; rep->type = Rwalk; rep->nwqid = req->nwname; if(req->nwname) rep->wqid[0] = q; return 1; } static int ropen(Fcall *req, Fcall *rep) { static int need[4] = { 4, /* NP_OREAD */ 2, /* NP_OWRITE */ 6, /* NP_ORDWR */ 1 /* NP_OEXEC */ }; struct Qtab *t; Fid *f; int n; if(req->mode&NP_OZEROES) return rerror(rep, "invalid 9P2000 open mode"); f = findFid(req->fid); if(f == nil) return rerror(rep, "bad fid"); if(f->opened != -1) return rerror(rep, "already open"); t = qtab + f->qid.path; n = need[req->mode & 3]; if((n & t->mode) != n) return rpermission(req, rep); rep->iounit = 0; switch(f->qid.path) { case Qinput: inputfid = f; if(linecontrol){ input = balloc(Maxfdata); input->add = linebuilder; } else { input = balloc(Maxfdata); input->add = rawappender; } break; case Qoutput: outputfid = f; output = balloc(Maxfdata*4); /* space for multiple verbose writers */ if(linecontrol){ output->add = lineprinter; } else { output->add = rawappender; } break; case Qcons: if(ISCLOSED(inputfid) && (req->mode & NP_OREAD) == NP_OREAD) return rerror(rep, "input device closed"); if(ISCLOSED(outputfid) && (req->mode & NP_OWRITE) == NP_OWRITE) return rerror(rep, "output device closed"); if((req->mode & NP_OWRITE) == NP_OWRITE) rep->iounit = ScreenBufferSize; break; default: break; } f->opened = req->mode; rep->type = Ropen; rep->qid = f->qid; return 1; } static int rread(Fcall *req, Fcall *rep) { Fid *f; char *err; if(err = invalidioreq(req)) return rerror(rep, err); f = findFid(req->fid); if(f == nil) return rerror(rep, "bad fid"); if(ISCLOSED(f) || (f->opened & 3) % 2 != 0) return rerror(rep, "i/o error"); rep->type = Rread; if(req->count == 0){ rep->count = 0; rep->data = nil; return 1; } switch(f->qid.path){ case Qroot: rep->count = rootread(f, (uint8_t*)data, req->offset, req->count, Maxfdata); rep->data = data + req->offset; return 1; case Qcons: if(ISCLOSED(inputfid)){ rep->count = 0; rep->data = nil; return 1; } if(!enqueue(&consreads, req->tag, req->count)) return rerror(rep, "out of memory"); return 0; case Qoutput: assert(outputread == nil); outputread = opalloc(req->tag, req->count); if(!outputread) return rerror(rep, "out of memory"); return 0; default: return rerror(rep, "i/o error"); } } static int rwrite(Fcall *req, Fcall *rep) { static char backspace = '\b'; int maxlength, old, new; Fid *f; char *s; if(s = invalidioreq(req)) return rerror(rep, s); f = findFid(req->fid); if(f == nil) return rerror(rep, "bad fid"); if(ISCLOSED(f) || (f->opened & NP_OWRITE) != NP_OWRITE) return rerror(rep, "i/o error"); switch(f->qid.path){ case Qcons: if(ISCLOSED(outputfid)) rep->count = 0; else if(bspace(output) < req->count * 2) return rerror(rep, "device busy"); else { while(output->linewidth > nextoutputcol) bwrite(output, &backspace, 1); if(req->count > ScreenBufferSize) req->count = ScreenBufferSize; rep->count = bwrite(output, req->data, req->count); nextoutputcol = output->linewidth; if(!bempty(input)) bwrite(output, input->data + input->read, input->written - input->read); if(bpending(output) > ScreenBufferSize * 3){ /* the output buffer contains too much data: * slow down writers by deferring Rwrites */ if(!enqueue(&conswrites, req->tag, rep->count)) return rerror(rep, "out of memory"); return 0; } } break; case Qconsctl: if(blind) return rerror(rep, "no raw mode when blind"); if(strncmp("rawon", req->data, 5) == 0){ input->ctrld = input->size; input->add = rawappender; output->add = rawappender; rawmode = 1; } else if(strncmp("rawoff", req->data, 6) == 0) { input->ctrld = input->size; input->add = linebuilder; output->add = lineprinter; rawmode = 0; } else return rerror(rep, "unknown control message"); rep->count = req->count; break; case Qinput: if(ISCLOSED(outputfid) || status == Unmounted){ rep->count = 0; } else if(rawmode || blind){ rep->count = bwrite(input, req->data, req->count); } else if(!linecontrol) { /* life is easy: * * we have to give a feedback to the user but we do * not have to handle control characters */ rep->count = bwrite(input, req->data, req->count); bwrite(output, req->data, rep->count); } else { /* we have to send a feedback to the user but also * handle control characters * * we need enough spaces in both input and output */ maxlength = req->count; if((new = bspace(input)) < maxlength) maxlength = new; if((new = bspace(output) / 2) < maxlength) maxlength = new; old = bpending(input); rep->count = bwrite(input, req->data, maxlength); new = bpending(input); if(new == old + rep->count){ /* no control characters */ maxlength = bwrite(output, req->data, maxlength); /* we knew we have enough space, abort if not */ assert(maxlength == rep->count); } else { /* sync visible output and input buffer * * we have to delete previously pending input chars * from output, and then write them again */ while(output->linewidth > nextoutputcol) bwrite(output, &backspace, 1); if(new > 0) bwrite(output, input->data, new); } } break; default: return rerror(rep, "i/o error"); } rep->type = Rwrite; return 1; } static int rclunk(Fcall *req, Fcall *rep) { Fid *f; f = findFid(req->fid); if(f != nil){ if(f == external){ debug("serve9p: external clients gone\n"); status = Unmounted; } f->opened = -1; } rep->type = Rclunk; return 1; } static int rstat(Fcall *req, Fcall *rep) { Dir d; Fid *f; static uint8_t mdata[Maxiosize]; f = findFid(req->fid); if(f == nil || f->qid.path >= Nqid) return rerror(rep, "bad fid"); fillstat(f->qid.path, &d); rep->type = Rstat; rep->nstat = convD2M(&d, mdata, Maxiosize); rep->stat = mdata; return 1; } static int (*fcalls[])(Fcall *, Fcall *) = { [Tversion] rversion, [Tauth] rauth, [Tattach] rattach, [Tflush] rflush, [Twalk] rwalk, [Topen] ropen, [Tcreate] rpermission, [Tread] rread, [Twrite] rwrite, [Tclunk] rclunk, [Tremove] rpermission, [Tstat] rstat, [Twstat] rpermission, }; int fsinit(int *mnt, int *mntdev) { int tmp[2]; pipe(tmp); *mnt = tmp[0]; *mntdev = '9'; return tmp[1]; } /* fsserve is the main loop */ void fsserve(int connection) { int r, w, syncrep; Fcall rep; Fcall *req; fspid = getpid(); req = malloc(sizeof(Fcall)+Maxfdata); if(req == nil) sysfatal("out of memory"); data = malloc(messagesize); if(data == nil) sysfatal("out of memory"); ftail = &fids; qinit(&consreads); qinit(&conswrites); status = Initializing; debug("serve9p %d: started\n", fspid); do { debug("serve9p %d: wait for a new request\n", fspid); if((r = readmessage(connection, req)) <= 0){ debug("serve9p %d: readmessage returns %d\n", fspid, r); break; } rep.tag = req->tag; if(req->type < Tversion || req->type > Twstat) syncrep = rerror(&rep, "bad fcall type"); else syncrep = (*fcalls[req->type])(req, &rep); if(syncrep && (w = sendmessage(connection, &rep)) <= 0){ debug("serve9p %d: sendmessage returns %d\n", fspid, w); break; } /* first, display output to the user... */ debug("serve9p %d: display output to the user... \n", fspid); if((status == Unmounted || ISCLOSED(outputfid)) && (w = closeOutput(connection, &rep)) <= 0){ debug("serve9p %d: closeOutput returns %d\n", fspid, w); break; } else if((w = syncOutput(connection, &rep)) <= 0){ debug("serve9p %d: syncOutput returns %d\n", fspid, w); break; } /* then distribute available input among cons readers... */ debug("serve9p %d: distribute available input among cons readers... \n", fspid); if((status == Unmounted || ISCLOSED(inputfid)) && (w = closeConsReaders(connection, &rep)) <= 0){ debug("serve9p %d: closeConsReaders returns %d\n", fspid, w); break; } else if(!linecontrol || rawmode || blineready(input)){ if((w = syncCons(connection, &rep)) <= 0){ debug("serve9p %d: syncCons(input) returns %d\n", fspid, w); break; } } /* We can exit (properly) only when the following conditions hold * * - the kernel decided that nobody need us anymore * (status == Unmounted, see rclunk and rattach) * - the inputfid has been closed (so that the Qinput writer * exited, releasing the actual input device) * - the outputfid has been closed (so that the Qoutput reader * exited, releasing the actual output device) * * Even if no processes in the namespace are currently using * cons or consctl, as far as the namespace exists one of its * processes could still open cons (or consctl). * * Thus we exit when the kernel decides that nobody will * need our services (aka, all the children sharing the * mountpoint that we serve have exited). * * (AND obviously if an unexpected error occurred) */ } while(systemwide || status < Unmounted || !ISCLOSED(inputfid) || !ISCLOSED(outputfid)); if(r < 0) sysfatal("serve9p: readmessage"); if(w < 0) sysfatal("serve9p: sendmessage"); close(connection); debug("serve9p %d: close(%d)\n", fspid, connection); debug("serve9p %d: shut down\n", fspid); exits(nil); }