ref: 6597dea23c86fb467f951d4a9ec77ab248abfb68
author: halfwit <michaelmisch1985@gmail.com>
date: Sat Jun 8 11:21:26 EDT 2024
initial
--- /dev/null
+++ b/TODO
@@ -1,0 +1,18 @@
+/mnt/altid/clone
+/mnt/altid/stats
+/mnt/altid/[0-9]
+/mnt/altid/[0-9]/feed
+/mnt/altid/[0-9]/...
+
+/mnt/altid/service
+/mnt/altid/service/clone
+/mnt/altid/service/[0-9]
+
+Can read stats for list of servers, etc
+Clone, get your n back
+/mnt/altid/1/ctl, write which service to connect to
+the fs populates with that, you can read in tabs to see what buffers are available
+
+Commands:
+ - markup, will need to import the state machine parser to pre-parse the output into just text
+ - tabs could be a command?
\ No newline at end of file
--- /dev/null
+++ b/alt.h
@@ -1,0 +1,56 @@
+#define CLIENTID(c) ((int)(((Client*)(c)) - client))
+
+typedef struct Buffer Buffer;
+typedef struct Notify Notify;
+typedef struct Service Service;
+typedef struct Client Client;
+
+struct Buffer
+{+ char title[256];
+ char status[256];
+ char feed[256];
+ char *aside;
+};
+
+struct Notify
+{+ char *data;
+ Notify *next;
+};
+
+struct Service
+{+ Buffer *buffer;
+ Notify *notifications;
+ //input callback function here
+ char *name;
+ Service *next;
+};
+
+struct Client
+{+ Buffer *current;
+ int showmarkdown;
+ int ref;
+};
+
+Client* newclient(void);
+void freeclient(Client*);
+char* clientctl(Client*, char*, char*);
+Service* newservice(void);
+void freeservice(Service*);
+char* servicectl(Service*, char*, char*);
+void* emalloc(int);
+char* estrdup(char*);
+
+char *mtpt;
+char *srvpt;
+char *user;
+long time0;
+char *logdir;
+Client client[256];
+int nclient;
+Service service[64];
+int nservice;
+int debug;
--- /dev/null
+++ b/client.c
@@ -1,0 +1,55 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#include "alt.h"
+
+Client*
+newclient(void)
+{+ Client *cl;
+ int i;
+
+ for(i = 0; i < nclient; i++)
+ if(client[i].ref == 0)
+ break;
+ if(i >= nelem(client))
+ return nil;
+ if(i == nclient)
+ nclient++;
+ cl = &client[i];
+ cl->ref++;
+
+ cl->current = nil;
+ cl->showmarkdown = 0;
+
+ return cl;
+}
+
+void
+freeclient(Client *cl)
+{+ if(cl == nil || cl->ref == 0)
+ return;
+ cl->ref--;
+ if(cl->ref > 0)
+ return;
+
+ memset(cl, 0, sizeof(*cl));
+}
+
+char*
+clientctl(Client *cl, char *ctl, char *arg)
+{+ USED(cl);
+ print("Command in: %s\nArgs in: %s\n", ctl, arg);+ if(strcmp(ctl, "buffer") == 0){+
+ } else{+ // Clients should be polling for commands and input alike
+
+ }
+ return nil;
+}
--- /dev/null
+++ b/fs.c
@@ -1,0 +1,497 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <fcall.h>
+
+#include "alt.h"
+
+enum {+ Qroot,
+ Qclone,
+ Qclients,
+ Qctl,
+ Qtitle,
+ Qstatus,
+ Qfeed,
+ Qaside,
+ Qnotify,
+ Qtabs,
+ Qinput,
+ Qservices,
+ Qsclone,
+ Qservice,
+ Qsctl,
+ Qsinput,
+ Qmax,
+};
+
+static char *qinfo[Qmax] = {+ [Qroot] "/",
+ [Qclone] "clone",
+ [Qclients] nil,
+ [Qctl] "ctl",
+ [Qtitle] "title",
+ [Qstatus] "status",
+ [Qfeed] "feed",
+ [Qaside] "aside",
+ [Qnotify] "notify",
+ [Qtabs] "tabs",
+ [Qinput] "input",
+ [Qservices] "services",
+ [Qsclone] "clone",
+ [Qservice] nil,
+ [Qsctl] "ctl",
+ [Qsinput] "input",
+};
+
+typedef struct Fid Fid;
+struct Fid
+{+ int fid;
+ ulong qtype;
+ ulong uniq;
+ int busy;
+ Client *client;
+ Service *service;
+ vlong foffset;
+ Fid *next;
+};
+
+static char *whitespace = "\t\r\n";
+static int chatty9p = 0;
+static Fid *fids;
+static Fcall rhdr, thdr;
+static uchar mdata[8192 + IOHDRSZ];
+static int messagesize = sizeof mdata;
+
+
+static void io(int, int);
+static Qid mkqid(Fid*);
+static Fid *findfid(int);
+static int dostat(Fid*, void*, int);
+
+static char *Auth(Fid*), *Attach(Fid*), *Version(Fid*),
+ *Flush(Fid*), *Walk(Fid*), *Open(Fid*),
+ *Create(Fid*), *Read(Fid*), *Write(Fid*),
+ *Clunk(Fid*), *Remove(Fid*), *Stat(Fid*),
+ *Wstat(Fid*);
+
+static char *(*fcalls[])(Fid*) = {+ [Tattach] Attach,
+ [Tauth] Auth,
+ [Tclunk] Clunk,
+ [Tflush] Flush,
+ [Topen] Open,
+ [Tread] Read,
+ [Tremove] Remove,
+ [Tstat] Stat,
+ [Tversion] Version,
+ [Twalk] Walk,
+ [Twrite] Write,
+ [Twstat] Wstat,
+};
+
+static char *
+Flush(Fid *f)
+{+ USED(f);
+ return 0;
+}
+
+static char *
+Auth(Fid *f)
+{+ // TODO: Implement auth
+ USED(f);
+ return "alt/fs: authentication not required";
+}
+
+static char *
+Attach(Fid *f)
+{+ if(f->busy)
+ Clunk(f);
+ f->client = nil;
+ f->service = nil;
+ f->qtype = Qroot;
+ f->busy = 1;
+ thdr.qid = mkqid(f);
+ return 0;
+}
+
+static char *
+Version(Fid*)
+{+ Fid *f;
+ for(f = fids; f; f = f->next)
+ if(f->busy)
+ Clunk(f);
+ if(rhdr.msize < 256)
+ return "message size too small";
+ if(rhdr.msize > sizeof(mdata))
+ thdr.msize = sizeof mdata;
+ else
+ thdr.msize = rhdr.msize;
+ thdr.version = "9P2000";
+ if(strncmp(rhdr.version, "9P", 2) != 0)
+ thdr.version = "unknown";
+ return 0;
+}
+
+static char*
+Walk(Fid *f)
+{+ char *name, *err;
+ int i, j; //isclient, isservice
+ Fid *nf;
+ ulong qtype;
+
+ if(!f->busy)
+ return "walk of unused fid";
+
+ nf = nil;
+ qtype = f->qtype;
+ if(rhdr.fid != rhdr.newfid){+ nf = findfid(rhdr.newfid);
+ if(nf->busy)
+ return "fid in use";
+ f = nf;
+ }
+ err = nil;
+ i = 0;
+ if(rhdr.nwname > 0){+ // We want to state if we're in a client or a service here in state
+ for(; i<rhdr.nwname; i++){+ if(i >= MAXWELEM){+ err = "too many elements in path";
+ break;
+ }
+ name = rhdr.wname[i];
+ switch(qtype){+ case Qroot:
+ if(strcmp(name, "..") == 0)
+ goto Accept;
+ // TODO: Check if we are at a client
+ if(f->client == nil)
+ goto Out;
+ qtype = Qclients;
+ Accept:
+ thdr.wqid[i] = mkqid(f);
+ break;
+ case Qclients:
+ if(strcmp(name, "..") == 0){+ qtype = Qroot;
+ f->client = nil;
+ goto Accept;
+ }
+ for(j = Qclients + 1; j < Qmax; j++)
+ if(strcmp(name, qinfo[j]) == 0){+ qtype = j;
+ break;
+ }
+ if(j < Qmax)
+ goto Accept;
+ goto Out;
+ //case Qservices:
+ //case Qservice:
+ default:
+ err = "file is not a directory";
+ goto Out;
+ }
+ }
+ Out:
+ if(i < rhdr.nwname && err == nil)
+ err = "file not found";
+ }
+
+ if(err != nil)
+ return err;
+ if(rhdr.fid != rhdr.newfid && i == rhdr.nwname){+ nf->busy = 1;
+ nf->qtype = qtype;
+ nf->client = f->client;
+ nf->service = f->service;
+ //if(nf->client != nil)
+ // incref(nf->client);
+ } else if (nf == nil && rhdr.nwname > 0){+ Clunk(f);
+ f->busy = 1;
+ f->qtype = qtype;
+ //if(f->client != nil)
+ // incref(f->client);
+ }
+ thdr.nwqid = i;
+ return 0;
+}
+
+static char *
+Clunk(Fid *f)
+{+ f->busy = 0;
+ //freeservice, freeclient
+ f->service = nil;
+ f->client = nil;
+ return nil;
+}
+
+static char *
+Open(Fid *f)
+{+ int mode;
+
+ if(!f->busy)
+ return "open of unused fid";
+ mode = rhdr.mode;
+
+ // TODO: clone and sclone and feed
+ // with feed, set up the real fid
+ // with clone + sclone, return Qctl and Qsctl after creating each
+ // though we do want access to setting a name after initialization
+ // we can do numbered and just reject any matching names
+ thdr.qid = mkqid(f);
+ thdr.iounit = messagesize - IOHDRSZ;
+ return 0;
+}
+
+static char *
+Create(Fid *f)
+{+ USED(f);
+ return "permission denied";
+}
+
+static char *
+Read(Fid *f)
+{+ USED(f);
+ // switch on qtype, do the right thing.
+ // Qroot: we want clone, services, and [0..9] clients
+ // Qclient: we want all of our named files, etc
+ // Qservices: we want clone and named services
+ // Qservice: we want input, data, etc
+ return "Open";
+}
+
+static char *
+Write(Fid *f)
+{+ USED(f);
+ // switch on qtype, do the write thing.
+ return "Chimken";
+}
+
+static char *
+Remove(Fid *f)
+{+ Clunk(f);
+ return "permission denied";
+}
+
+static char *
+Stat(Fid *f)
+{+ static uchar statbuf[1024];
+
+ if(!f->busy)
+ return "stat on unused fd";
+ thdr.nstat = dostat(f, statbuf, sizeof statbuf);
+ if(thdr.nstat <= BIT16SZ)
+ return "stat buffer too small";
+ thdr.stat = statbuf;
+ return 0;
+}
+
+static char *
+Wstat(Fid *f)
+{+ //Dir d;
+ //int n;
+ //char buf[1024];
+ // TODO: Anything we want to allow here
+ USED(f);
+ return "permission denied";
+}
+
+static Qid
+mkqid(Fid *f)
+{+ Qid q;
+
+ q.vers = 0;
+ q.path = f->qtype;
+ if(f->service || f->client)
+ q.path |= f->uniq * 0x100;
+
+ switch(f->qtype){+ case Qservice:
+ case Qservices:
+ case Qclients:
+ case Qroot:
+ q.type = QTDIR;
+ default:
+ q.type = QTFILE;
+ }
+ return q;
+}
+
+static int
+dostat(Fid *f, void *p, int n)
+{+ Dir d;
+
+ if(f->qtype == Qservice)
+ d.name = f->service->name;
+ // TODO: look up the n for the client if we're in client
+ else
+ d.name = qinfo[f->qtype];
+ d.uid = d.gid = d.muid = "none";
+ d.qid = mkqid(f);
+ if(d.qid.type & QTDIR)
+ d.mode = 0755|DMDIR;
+ else
+ d.mode = 0644;
+ d.atime = d.mtime = time(0);
+ d.length = 0;
+ return convD2M(&d, p, n);
+}
+
+static Fid *
+findfid(int fid)
+{+ Fid *f, *ff;
+
+ ff = nil;
+ for(f = fids; f; f = f->next)
+ if(f->fid == fid)
+ return f;
+ else if(!ff && !f->busy)
+ ff = f;
+ if(ff != nil){+ ff->fid = fid;
+ return ff;
+ }
+
+ f = emalloc(sizeof *f);
+ f->fid = fid;
+ f->busy = 0;
+ f->client = nil;
+ f->service = nil;
+ f->next = fids;
+ fids = f;
+ return f;
+}
+
+static void
+io(int in, int out)
+{+ char *err;
+ int n;
+
+ while((n = read9pmsg(in, mdata, messagesize)) != 0){+ if(n < 0)
+ fprint(2, "mount read: %r");
+ if(convM2S(mdata, n, &rhdr) != n)
+ fprint(2, "convM2S format error: %r");
+ thdr.data = (char*)mdata + IOHDRSZ;
+ thdr.fid = rhdr.fid;
+ if(!fcalls[rhdr.type])
+ err = "bad fcall request";
+ else
+ err = (*fcalls[rhdr.type])(findfid(rhdr.fid));
+ thdr.tag = rhdr.tag;
+ thdr.type = rhdr.type + 1;
+ if(err){+ thdr.type = Rerror;
+ thdr.ename = err;
+ }
+ n = convS2M(&thdr, mdata, messagesize);
+ if(write(out, mdata, n) != n)
+ fprint(2, "mount write\n");
+ }
+}
+
+static void
+usage(void)
+{+ fprint(2, "usage: %s [-Dd] [-m mtpt] [-s service] [-l logdir]\n", argv0);
+ exits("usage");+}
+
+void
+main(int argc, char *argv[])
+{+ // We could use quotefmtinstall here
+ // add in tabs at very least
+ int p[2];
+
+ user = getuser();
+ mtpt = "/mnt/alt";
+ logdir = "/tmp/alt";
+
+ ARGBEGIN {+ case 'D':
+ chatty9p++;
+ break;
+ case 'm':
+ mtpt = EARGF(usage());
+ break;
+ case 's':
+ srvpt = EARGF(usage());
+ break;
+ case 'l':
+ logdir = EARGF(usage());
+ break;
+ case 'd':
+ debug++;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ argv0 = "alt/fs";
+
+ if(pipe(p) < 0)
+ sysfatal("Can't make pipe: %r");+
+ create(logdir, OREAD, DMDIR | 0755);
+ switch(rfork(RFPROC|RFNAMEG|RFNOTEG|RFNOWAIT|RFENVG|RFFDG|RFMEM)){+ case 0:
+ close(p[0]);
+ io(p[1], p[1]);
+ postnote(PNPROC, 1, "shutdown");
+ exits(0);
+ case -1:
+ sysfatal("fork");+ default:
+ close(p[1]);
+ // TODO: We want to srv if we have srvpt
+ if(mount(p[0], -1, mtpt, MREPL|MCREATE, "") == -1)
+ sysfatal("can't mount: %r");+ exits(0);
+ }
+}
+
+void*
+emalloc(int n)
+{+ void *p;
+
+ if((p = malloc(n)) != nil){+ memset(p, 0, n);
+ return p;
+ }
+ sysfatal("out of memory");+}
+
+char*
+estrdup(char *s)
+{+ char *d;
+ int n;
+
+ n = strlen(s)+1;
+ d = emalloc(n);
+ memmove(d, s, n);
+ return d;
+}
+
--- /dev/null
+++ b/fs.c.bak
@@ -1,0 +1,567 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#include "alt.h"
+
+// TODO: Start turning off output if we aren't connected to anything
+
+enum {+ Qroot,
+ Qclone,
+ Qclients,
+ Qctl,
+ Qtitle,
+ Qstatus,
+ Qfeed,
+ Qaside,
+ Qnotify,
+ Qtabs,
+ Qinput,
+ Qservices,
+ Qsclone,
+ Qservice,
+ Qsctl,
+ Qsinput,
+ Qmax,
+};
+
+static char *nametab[] = {+ "/",
+ "clone",
+ nil,
+ "ctl",
+ "title",
+ "status",
+ "feed",
+ "aside",
+ "notify",
+ "tabs",
+ "input",
+ "services",
+ "clone",
+ nil,
+ "ctl",
+ "input",
+ nil,
+};
+
+typedef struct Altfid Altfid;
+struct Altfid
+{+ int level;
+ Client *client;
+ Service *service;
+ int fd;
+ vlong foffset;
+};
+
+static char *whitespace = "\t\r\n";
+
+void*
+emalloc(int n)
+{+ void *v;
+ v = emalloc9p(n);
+ setmalloctag(v, getcallerpc(&n));
+ memset(v, 0, n);
+ return v;
+}
+
+char*
+estrdup(char *s)
+{+ s = estrdup9p(s);
+ setmalloctag(s, getcallerpc(&s));
+ return s;
+}
+
+static void*
+wfaux(Altfid *f)
+{+ if(f->level < Qclients)
+ return nil;
+ else if(f->level < Qservices)
+ return f->client;
+ return f->service;
+}
+
+static void
+fsmkqid(Qid *q, int level, void *aux)
+{+
+ q->type = 0;
+ q->vers = 0;
+ switch(level){+ case Qroot:
+ case Qclients:
+ case Qservices:
+ case Qservice:
+ q->type = QTDIR;
+ default:
+ q->path = (level<<24) | (((uintptr)aux ^ time0) & 0x00ffffff);
+ }
+}
+
+static void
+fsmkdir(Dir *d, int level, void *aux)
+{+ Service *sv;
+ char buf[1024];
+
+ memset(d, 0, sizeof(*d));
+ fsmkqid(&d->qid, level, aux);
+ d->mode = 0444;
+ d->atime = d->mtime = time0;
+ d->uid = estrdup(user);
+ d->gid = estrdup(user);
+ d->muid = estrdup(user);
+ if(d->qid.type & QTDIR)
+ d->mode |= DMDIR | 0111;
+ switch(level){+ case Qclients:
+ snprint(buf, sizeof(buf), "%d", CLIENTID(aux));
+ d->name = estrdup(buf);
+ break;
+ case Qservice:
+ sv = (Service*)aux;
+ d->name = sv->name;
+ break;
+ case Qctl:
+ case Qsctl:
+ case Qclone:
+ case Qsclone:
+ d->mode = 0666;
+ if(0){+ case Qinput:
+ d->mode = 0222;
+ }
+ default:
+ d->name = estrdup(nametab[level]);
+ }
+}
+
+static void
+fsattach(Req *r)
+{+ Altfid *f;
+
+ if(r->ifcall.aname && r->ifcall.aname[0]){+ respond(r, "invalid attach specifier");
+ return;
+ }
+ f = emalloc(sizeof(*f));
+ f->level = Qroot;
+ fsmkqid(&r->fid->qid, f->level, wfaux(f));
+ r->ofcall.qid = r->fid->qid;
+ r->fid->aux = f;
+ respond(r, nil);
+}
+
+static void
+fsstat(Req *r)
+{+ Altfid *f;
+
+ f = r->fid->aux;
+ fsmkdir(&r->d, f->level, wfaux(f));
+ respond(r, nil);
+}
+
+static char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{+ Altfid *f;
+ int i, j;
+ i = 0;
+
+ if(!(fid->qid.type&QTDIR))
+ return "walk in non-directory";
+
+ f = fid->aux;
+ if(strcmp(name, "..")==0){+ switch(f->level){+ case Qroot:
+ break;
+ case Qclients:
+ freeclient(f->client);
+ f->client = nil;
+ break;
+ case Qservices:
+ f->level = Qroot;
+ break;
+ default:
+ if(f->level > Qservices)
+ f->level = Qservices;
+ else
+ f->level = Qclients;
+ }
+ } else if(strcmp(name, "services")==0){+ i = Qservices;
+ goto Out;
+ } else {+ if(nservice){+ for(j=0; j < nservice; j++){+ if(strcmp(name, service[j].name) == 0){+ f->service = &service[j];
+ i = Qservice;
+ break;
+ }
+ }
+ } else {+ for(i = f->level+1; i < nelem(nametab); i++){+ if(nametab[i]){+ if(strcmp(name, nametab[i]) == 0)
+ goto Out;
+ // anything else?
+ }
+ if(i == Qclients){+ j = atoi(name);
+ if(j >= 0 && j < nclient){+ f->client = &client[j];
+ incref(f->client);
+ goto Out;
+ }
+ }
+ }
+ }
+Out:
+ if(i >= nelem(nametab))
+ return "directory entry not found";
+ f->level = i;
+ }
+ fsmkqid(qid, f->level, wfaux(f));
+ fid->qid = *qid;
+ return nil;
+}
+
+static char*
+fsclone(Fid *oldfid, Fid *newfid)
+{+ Altfid *f, *o;
+ o = oldfid->aux;
+ if(o == nil)
+ return "bad fid";
+ f = emalloc(sizeof(*f));
+ memmove(f, o, sizeof(*f));
+ if(f->client)
+ incref(f->client);
+ newfid->aux = f;
+ return nil;
+}
+
+static void
+fsopen(Req *r)
+{+ Altfid *f;
+ Client *cl;
+ Service *svc;
+ char buf[256];
+
+ // Switch and create on clones, etc
+ f = r->fid->aux;
+ cl = f->client;
+ svc = f->service;
+ USED(svc);
+ switch(f->level){+ case Qclone:
+ if((cl = newclient()) == nil){+ respond(r, "no more clients");
+ return;
+ }
+ f->level = Qctl;
+ f->client = cl;
+ fsmkqid(&r->fid->qid, f->level, wfaux(f));
+ r->ofcall.qid = r->fid->qid;
+ break;
+ case Qsclone:
+ if((svc = newservice()) == nil){+ respond(r, "no more services");
+ }
+ f->level = Qsctl;
+ f->service = svc;
+ fsmkqid(&r->fid->qid, f->level, wfaux(f));
+ r->ofcall.qid = r->fid->qid;
+ break;
+ case Qfeed:
+ if(cl->current){+ snprint(buf, sizeof(buf), "%s/%s", logdir, cl->current->feed);
+ print("%s\n", buf);+ f->fd = open(buf, 0644);
+ f->foffset = 0;
+ }
+ }
+ respond(r, nil);
+}
+
+static int
+rootgen(int i, Dir *d, void *)
+{+ i += Qroot+1;
+ if(i < Qclients){+ fsmkdir(d, i, 0);
+ return 0;
+ }
+ i -= Qclients;
+ if(i < nclient){+ fsmkdir(d, Qclients, &client[i]);
+ return 0;
+ }
+ // Final entry is just our services dir
+ if(i == nclient){+ fsmkdir(d, Qservices, 0);
+ return 0;
+ }
+ return -1;
+}
+
+static int
+servicesgen(int i, Dir *d, void *)
+{+ i += Qservices + 1;
+ if(i < Qservice){+ fsmkdir(d, i, 0);
+ return 0;
+ }
+ i -= Qservices + 2;
+ if(i < nservice){+ fsmkdir(d, Qservice, &service[i]);
+ return 0;
+ }
+ return -1;
+}
+
+static int
+clientgen(int i, Dir *d, void *aux)
+{+ // TODO: Mask the unusable files if we have no current buffer
+ i += Qclients+1;
+ if(i > Qinput)
+ return -1;
+ fsmkdir(d, i, aux);
+ return 0;
+}
+
+static int
+servicegen(int i, Dir *d, void *aux)
+{+ i += Qservice+1;
+print("%d %d\n", i, Qmax);+ if(i >= Qmax)
+ return -1;
+ fsmkdir(d, i, aux);
+ return 0;
+}
+
+static void
+fsread(Req *r)
+{+ char buf[1024];
+ Altfid *f;
+ Client *cl;
+ Service *svc;
+
+ f = r->fid->aux;
+ cl = f->client;
+ svc = f->service;
+
+ if(f->level > Qctl && f->level < Qservices && !cl->current){+ respond(r, "no current buffer selected");
+ return;
+ }
+
+ switch(f->level){+ case Qroot:
+print("Root\n");+ dirread9p(r, rootgen, nil);
+ respond(r, nil);
+ return;
+ case Qclients:
+print("Clients\n");+ dirread9p(r, clientgen, nil);
+ respond(r, nil);
+ return;
+ case Qservices:
+print("Services\n");+ dirread9p(r, servicesgen, nil);
+ respond(r, nil);
+ return;
+ case Qservice:
+print("Service\n");+ dirread9p(r, servicegen, nil);
+ respond(r, nil);
+ return;
+ case Qtitle:
+ snprint(buf, sizeof(buf), "%s\n", cl->current->title);
+ String:
+ readstr(r, buf);
+ respond(r, nil);
+ return;
+ case Qctl:
+ snprint(buf, sizeof(buf), "%d\n", CLIENTID(f->client));
+ goto String;
+ case Qstatus:
+ snprint(buf, sizeof(buf), "%s\n", cl->current->status);
+ goto String;
+ case Qaside:
+ snprint(buf, sizeof(buf), "%s\n", cl->current->aside);
+ goto String;
+ case Qsctl:
+ snprint(buf, sizeof(buf), "%s\n", svc->name);
+ goto String;
+ case Qfeed:
+ pread(f->fd, buf, sizeof(buf), f->foffset);
+ goto String;
+ case Qsinput:
+ // forward any pending input from client
+ // TODO: Channel for input?
+ break;
+ case Qnotify:
+ // TODO: notify fmt %N, install at start
+ //snprint(buf, sizeof(buf), "%N\n", svc->notify);
+ break;
+ case Qtabs:
+ // TODO: tabs fmt %T, install at start
+ //snprint(buf, sizeof(buf), "%T\n", svc);
+ goto String;
+
+ }
+ respond(r, "not implemented");
+}
+
+static void
+fswrite(Req *r)
+{+ int n;
+ Altfid *f;
+ char *s, *t;
+
+ f = r->fid->aux;
+ switch(f->level){+ case Qsctl:
+ case Qctl:
+ n = r->ofcall.count = r->ifcall.count;
+ s = emalloc(n+1);
+ memmove(s, r->ifcall.data, n);
+ while(n > 0 && strchr("\r\n", s[n-1]))+ n--;
+ s[n] = 0;
+ // TODO: We don't use any of this in any meaningful way, remove t from calls
+ t = s;
+ while(*t && strchr(whitespace, *t)==0)
+ t++;
+ while(*t && strchr(whitespace, *t))
+ *t++ = 0;
+ if(f->level == Qctl)
+ t = clientctl(f->client, s, t);
+ else
+ t = servicectl(f->service, s, t);
+ free(s);
+ respond(r, t);
+ return;
+ case Qinput:
+ // TODO: User wrote a string to us, forward to server (cb?)
+ //f->svc->callback(r->ifcall.data, r->ifcall.count);
+ return;
+ }
+ respond(r, "not implemented");
+}
+
+static void
+fsflush(Req *r)
+{+ respond(r, nil);
+}
+
+static void
+fsdestroyfid(Fid *fid)
+{+ Altfid *f;
+
+ if(f = fid->aux){+ fid->aux = nil;
+ if(f->client)
+ freeclient(f->client);
+ // TODO: uncomment so services hold open an FD to show their livelihood
+ //if(f->service)
+ // freeservice(f->service);
+ free(f);
+ }
+}
+
+static void
+fsstart(Srv*)
+{+ /* Overwrite if we have one, force a reconnect of everything */
+ if(mtpt != nil)
+ unmount(nil, mtpt);
+}
+
+static void
+fsend(Srv*)
+{+ postnote(PNGROUP, getpid(), "shutdown");
+ exits(nil);
+}
+
+Srv fs =
+{+ .start=fsstart,
+ .attach=fsattach,
+ .stat=fsstat,
+ .walk1=fswalk1,
+ .clone=fsclone,
+ .open=fsopen,
+ .read=fsread,
+ .write=fswrite,
+ .flush=fsflush,
+ .destroyfid=fsdestroyfid,
+ .end=fsend,
+};
+
+void
+usage(void)
+{+ fprint(2, "usage: %s [-Dd] [-m mtpt] [-s service] [-l logdir]\n", argv0);
+ exits("usage");+}
+
+void
+main(int argc, char *argv[])
+{+ // We could use quotefmtinstall here
+ // add in tabs at very least
+ user = getuser();
+ mtpt = "/mnt/alt";
+ logdir = "/tmp/alt";
+ time0 = time(0);
+
+ ARGBEGIN {+ case 'D':
+ chatty9p++;
+ break;
+ case 'm':
+ mtpt = EARGF(usage());
+ break;
+ case 's':
+ srvpt = EARGF(usage());
+ break;
+ case 'l':
+ logdir = EARGF(usage());
+ break;
+ case 'd':
+ debug++;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ argv0 = "alt/fs";
+
+ create(logdir, OREAD, DMDIR | 0755);
+ postmountsrv(&fs, srvpt, mtpt, MCREATE);
+ exits(nil);
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,21 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin/alt
+
+TARG=\
+ fs\
+
+HFILES=alt.h
+
+OFILES=\
+ fs.$O\
+ buffer.$O\
+ client.$O\
+ service.$O\
+ notification.$O\
+
+</sys/src/cmd/mkmany
+
+install:V:
+ mkdir -p $BIN
+ mk $MKFLAGS fs.install
--- /dev/null
+++ b/service.c
@@ -1,0 +1,44 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#include "alt.h"
+
+Service*
+newservice(void)
+{+ Service *sv;
+ char buf[1024];
+
+ sv = &service[nservice];
+ nservice++;
+
+ sv->buffer = nil;
+ sv->notifications = nil;
+ snprint(buf, sizeof(buf), "default");
+ sv->name = estrdup(buf);
+
+ return sv;
+}
+
+void
+freeservice(Service *s)
+{+ if(s == nil)
+ return;
+ memset(s, 0, sizeof(*s));
+}
+
+char*
+servicectl(Service *svc, char *ctl, char *arg)
+{+ char *target, *buffer;
+ USED(svc, ctl, arg);
+ //ctl is like title:##meskarune
+ target = strstr(":", ctl);+ buffer = strstr(nil, ctl);
+ print("%s and %s\n", buffer, target);+ return nil;
+}
--
⑨