ref: 063d86a3015f5f254389e61b57e7f14822e889c3
parent: 50bc9f2c47441facf926715dca4e73da381d6a5d
author: Michael Misch <michaelmisch1985@gmail.com>
date: Sun Feb 22 00:02:41 EST 2026
Move up a directory d
--- /dev/null
+++ b/fs/buffer.c
@@ -1,0 +1,113 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#include "svc.h"
+
+static void
+bufferFree(Buffer *match)
+{+ if(match->name)
+ free(match->name);
+ free(match);
+}
+
+char *
+bufferDrop(Buffer *base, char *name)
+{+ Buffer *mp, *bp;
+ qlock(&base->l);
+ for(bp = base; bp->next; bp = bp->next){+ mp = bp->next;
+ if(strcmp(bp->next->name, name) == 0){+ if(mp && mp->next)
+ bp->next = mp->next;
+ else
+ bp->next = nil;
+ if(mp)
+ bufferFree(mp);
+ }
+ }
+ qunlock(&base->l);
+ return nil;
+}
+
+char *
+bufferPush(Buffer *base, char *name)
+{+ Buffer *b, *ep;
+ char p[1024];
+
+ qlock(&base->l);
+ for(ep = base; ep->next; ep = ep->next){+ if(ep && strcmp(ep->name, name) == 0){+ qunlock(&base->l);
+ return "buffer exists";
+ }
+ if(ep->next == nil)
+ break;
+ }
+
+ b = mallocz(sizeof(*b), 1);
+ strcpy(b->name, name);
+ b->notify = nil;
+ b->unread = 0;
+ b->tag = -1;
+ b->rz.l = &b->l;
+ snprint(p, sizeof(p), "%s/%s/%s", logdir, base->name, name);
+ if(access(p, 0) == 0)
+ b->fd = open(p, OWRITE);
+ else
+ b->fd = create(p, OWRITE, 0644);
+ seek(b->fd, 0, 2);
+ ep->next = b;
+ qunlock(&base->l);
+ return nil;
+}
+
+Buffer *
+bufferSearch(Buffer *base, char *name)
+{+ Buffer *sp;
+ qlock(&base->l);
+ for(sp = base; sp; sp = sp->next)
+ if(strcmp(sp->name, name) == 0){+ qunlock(&base->l);
+ return sp;
+ }
+ qunlock(&base->l);
+ return nil;
+}
+
+Buffer *
+bufferSearchTag(Buffer *base, ulong tag)
+{+ Buffer *sp;
+ qlock(&base->l);
+ for(sp = base; sp; sp = sp->next)
+ if(sp->tag == tag){+ qunlock(&base->l);
+ return sp;
+ }
+ qunlock(&base->l);
+ return nil;
+}
+
+Buffer*
+bufferCreate(Channel *cmds, Channel *input)
+{+ Buffer *b;
+
+ b = mallocz(sizeof(*b), 1);
+ b->cmds = cmds;
+ b->input = input;
+ b->tag = -1;
+ b->unread = 0;
+ b->notify = nil;
+ b->next = nil;
+ b->rz.l = &b->l;
+
+ return b;
+}
--- /dev/null
+++ b/fs/client.c
@@ -1,0 +1,431 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#include "svc.h"
+
+enum {+ Qcroot,
+ Qtabs,
+ Qctl,
+ Qinput,
+ Qtitle,
+ Qstatus,
+ Qaside,
+ Qfeed,
+ Qnotify,
+ Qmax,
+};
+
+static char *cltab[] = {+ "/",
+ "tabs",
+ "ctl",
+ "input",
+ "title",
+ "status",
+ "aside",
+ "feed",
+ "notify",
+ nil,
+};
+
+typedef struct Clfid Clfid;
+typedef struct Client Client;
+
+struct Clfid
+{+ int level;
+ int tag;
+ Client *cl;
+};
+
+struct Client
+{+ Ref;
+
+ Buffer *current;
+ int fd;
+ int showmarkdown;
+};
+
+static Client client[256];
+static Buffer *root;
+static int flushtag;
+static int nclient;
+static int time0;
+static Srv *fs;
+
+static Client*
+newclient(char *aname)
+{+ 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 = bufferSearch(root, aname);
+ if(cl->current)
+ cl->current->tag = 0;
+ cl->showmarkdown = 0;
+ cl->fd = -1;
+
+ return cl;
+}
+
+static void
+freeclient(Client *cl)
+{+ if(cl == nil || cl->ref == 0)
+ return;
+ cl->ref--;
+ if(cl->ref > 0)
+ return;
+
+ memset(cl, 0, sizeof(*cl));
+}
+
+static void
+clmkqid(Qid *q, int level, void *aux)
+{+ q->type = 0;
+ q->vers = 0;
+ if(level == Qcroot)
+ q->type = QTDIR;
+ else
+ q->path = (level<<24) | (((uintptr)aux ^ time0) & 0x00ffffff);
+}
+
+static void
+clmkdir(Dir *d, int level, void *aux)
+{+ memset(d, 0, sizeof(*d));
+ clmkqid(&d->qid, level, aux);
+ d->mode = 0444;
+ d->atime = d->mtime = time(0);
+ d->uid = estrdup(user);
+ d->gid = estrdup(user);
+ d->muid = estrdup(user);
+ if(d->qid.type & QTDIR)
+ d->mode |= DMDIR | 0111;
+ switch(level){+ case Qctl:
+ d->mode = 0666;
+ default:
+ d->name = estrdup(cltab[level]);
+ }
+}
+
+void
+clattach(Req *r)
+{+ Clfid *f;
+ char path[1024];
+
+ f = mallocz(sizeof(*f), 1);
+ f->cl = newclient(r->ifcall.aname);
+
+ /* Attach request has buffer */
+ if(r->ifcall.aname[0]){+ memset(path, 0, sizeof(path));
+ snprint(path, sizeof(path), "%s/%s/%s", logdir, root->name, r->ifcall.aname);
+ f->cl->fd = open(path, OREAD);
+ }
+ f->level = Qcroot;
+ clmkqid(&r->fid->qid, f->level, nil);
+ r->ofcall.qid = r->fid->qid;
+ r->fid->aux = f;
+ respond(r, nil);
+}
+
+
+
+void
+clstat(Req *r)
+{+ Clfid *f;
+
+ f = r->fid->aux;
+ clmkdir(&r->d, f->level, f->cl);
+ respond(r, nil);
+}
+
+
+char*
+clwalk1(Fid *fid, char *name, Qid *qid)
+{+ Clfid *f;
+ int i;
+
+ if(!(fid->qid.type&QTDIR))
+ return "walk in non-directory";
+
+ f = fid->aux;
+ for(i=f->level+1; i < nelem(cltab); i++){+ if(cltab[i]){+ if(strcmp(name, cltab[i]) == 0)
+ break;
+ }
+ }
+ if(i >= nelem(cltab))
+ return "directory entry not found";
+ f->level = i;
+ clmkqid(qid, f->level, f->cl);
+ fid->qid = *qid;
+ return nil;
+}
+
+char *
+clclone(Fid *oldfid, Fid *newfid)
+{+ Clfid *f, *o;
+
+ o = oldfid->aux;
+ if(o == nil)
+ return "bad fid";
+ f = mallocz(sizeof(*f), 1);
+ memmove(f, o, sizeof(*f));
+ if(f->cl)
+ incref(f->cl);
+ newfid->aux = f;
+ return nil;
+}
+
+void
+clopen(Req *r)
+{+ respond(r, nil);
+}
+
+static int
+rootgen(int i, Dir *d, void *aux)
+{+ i += Qcroot+1;
+ if(i < Qmax){+ clmkdir(d, i, aux);
+ return 0;
+ }
+ return -1;
+}
+
+void
+clread(Req *r)
+{+ Clfid *f;
+ Buffer *b;
+ Notify *np;
+ char buf[1024];
+ int n;
+
+ f = r->fid->aux;
+ n = 0;
+ switch(f->level){+ case Qcroot:
+ dirread9p(r, rootgen, f->cl);
+ respond(r, nil);
+ return;
+ case Qfeed:
+ // Catch EOF
+ if(f->cl->fd < 0 || !f->cl->current){+ respond(r, "no feed");
+ return;
+ }
+ srvrelease(fs);
+ b = f->cl->current;
+Again:
+ if(b->tag == flushtag){+ flushtag = -1;
+ r->ofcall.count = 0;
+ srvacquire(fs);
+ respond(r, nil);
+ return;
+ }
+ n = pread(f->cl->fd, r->ofcall.data, r->ifcall.count, r->ifcall.offset);
+ if(n > 0){+ r->ofcall.count = n;
+ srvacquire(fs);
+ respond(r, nil);
+ return;
+ } else if(n < 0) {+ srvacquire(fs);
+ responderror(r);
+ return;
+ }
+ qlock(&b->l);
+ b->unread = 0;
+ rsleep(&b->rz);
+ qunlock(&b->l);
+ goto Again;
+ case Qtitle:
+ if(f->cl->current && f->cl->current->title){+ b = f->cl->current;
+ memset(buf, 0, sizeof(buf));
+ qlock(&b->l);
+ snprint(buf, sizeof(buf), "%s", b->title);
+ qunlock(&b->l);
+String:
+ readstr(r, buf);
+ respond(r, nil);
+ return;
+ }
+ break;
+ case Qstatus:
+ if(f->cl->current && f->cl->current->status){+ b = f->cl->current;
+ memset(buf, 0, sizeof(buf));
+ qlock(&b->l);
+ snprint(buf, sizeof(buf), "%s",b->status);
+ qunlock(&b->l);
+ goto String;
+ }
+ break;
+ case Qaside:
+ if(f->cl->current && f->cl->current->aside){+ b = f->cl->current;
+ memset(buf, 0, sizeof(buf));
+ qlock(&b->l);
+ snprint(buf, sizeof(buf), "%s", b->aside);
+ qunlock(&b->l);
+ goto String;
+ }
+ break;
+ case Qnotify:
+ if(f->cl->current && f->cl->current->notify){+ b = f->cl->current;
+ memset(buf, 0, sizeof(buf));
+ qlock(&b->l);
+ for(np = b->notify; np; np = np->next)
+ n = snprint(buf + n, sizeof(buf), "!%s\n", np->data);
+ qunlock(&b->l);
+ goto String;
+ }
+ break;
+ case Qtabs:
+ qlock(&root->l);
+ memset(buf, 0, sizeof(buf));
+ for(b = root->next; b; b = b->next){+ n += snprint(buf + n, sizeof(buf) - n, "%N\n", b);
+ }
+ qunlock(&root->l);
+ goto String;
+ }
+ if(!f->cl->current)
+ respond(r, "no buffer selected");
+ else
+ respond(r, "no data available");
+}
+
+void
+clwrite(Req *r)
+{+ Buffer *b;
+ Clfid *f;
+ Cmd cmd;
+ char path[1024];
+
+ f = r->fid->aux;
+ memset(&cmd, 0, sizeof(Cmd));
+ switch(f->level){+ case Qctl:
+ convS2C(&cmd, r->ifcall.data, r->ifcall.count);
+ switch(cmd.type){+ case BufferCmd:
+ b = bufferSearch(root, cmd.buffer);
+ if(!b){+ respond(r, "No buffers available");
+ return;
+ }
+ qlock(&b->l);
+ if(f->cl && f->cl->fd > 0){+ b->tag = flushtag = 1;
+ rwakeup(&b->rz);
+ close(f->cl->fd);
+ }
+ f->cl->current = b;
+ b->tag = r->tag;
+ qunlock(&b->l);
+ memset(path, 0, sizeof(path));
+ snprint(path, sizeof(path), "%s/%s/%s", logdir, root->name, cmd.buffer);
+ f->cl->fd = open(path, OREAD);
+ r->fid->aux = f;
+ goto Out;
+ case MarkdownCmd:
+ f->cl->showmarkdown = !f->cl->showmarkdown;
+ goto Out;
+ default:
+ sendp(root->cmds, &cmd);
+ sendp(root->cmds, nil);
+ goto Out;
+ }
+ case Qinput:
+ r->ofcall.count = 0;
+ if(f->cl->current == nil){+ respond(r, "No buffer selected");
+ return;
+ }
+ cmd.type = InputCmd;
+ strcpy(cmd.buffer, f->cl->current->name);
+ cmd.data = strdup(r->ifcall.data);
+ cmd.data[r->ifcall.count] = 0;
+ sendp(root->cmds, &cmd);
+ sendp(root->cmds, nil);
+ }
+Out:
+ respond(r, nil);
+ return;
+}
+
+void
+clflush(Req *r)
+{+ Buffer *b;
+ flushtag = r->tag;
+ if(b = bufferSearchTag(root, flushtag)){+ qlock(&b->l);
+ rwakeup(&b->rz);
+ qunlock(&b->l);
+ }
+ respond(r, nil);
+}
+
+void
+cldestroyfid(Fid *fid)
+{+ Clfid *f;
+
+ if(f = fid->aux){+ // TODO: Uncomment once we use this in aux/listen
+ //fid->aux = nil;
+ //if(f->cl)
+ // freeclient(f->cl);
+ }
+ free(f);
+}
+
+void
+clstart(Srv *s)
+{+ // TODO: Set up note handler
+ root = emalloc(sizeof(*root));
+ USED(root);
+ root = (Buffer*)s->aux;
+ flushtag = -1;
+ fs = s;
+ time0 = time(0);
+}
+
+void
+clend(Srv*)
+{+ exits(nil);
+}
--- /dev/null
+++ b/fs/cmd.c
@@ -1,0 +1,31 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#include "svc.h"
+
+int
+Cconv(Fmt *fp)
+{+ char s[CmdSize];
+ Cmd *c;
+
+ c = va_arg(fp->args, Cmd*);
+ switch(c->type){+ case ServiceCmd:
+ if(strlen(c->data) > 0)
+ snprint(s, sizeof(s), "%s %s\n%s", c->svccmd, c->buffer, c->data);
+ else
+ snprint(s, sizeof(s), "%s %s", c->svccmd, c->buffer);
+ break;
+ case InputCmd:
+ snprint(s, sizeof(s), "input %s\n%s", c->buffer, c->data);
+ break;
+ case FlushCmd:
+ snprint(s, sizeof(s), "flush");
+ break;
+ }
+ return fmtstrcpy(fp, s);
+}
--- /dev/null
+++ b/fs/convS2C.c
@@ -1,0 +1,175 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "svc.h"
+
+// TODO: Read runes instead of ascii
+
+struct state
+{+ Cmd cmd;
+ char *base;
+ int size;
+ int (*fn)(struct state *s);
+};
+
+static int parse_cmd(struct state *s);
+static int parse_from(struct state *s);
+static int parse_data(struct state *s);
+
+uint
+convS2C(Cmd *cmd, char *c, uint n)
+{+ struct state s;
+ int t;
+
+ s.fn = (*parse_cmd);
+ s.cmd.type = ErrorCmd;
+ s.cmd.data = "";
+ s.base = c;
+ s.size = n;
+ for(;;){+ t = s.fn(&s);
+ if(t < 0)
+ return t;
+ if(t == 0)
+ break;
+ }
+
+ cmd->type = s.cmd.type;
+ cmd->data = s.cmd.data;
+ strcpy(cmd->buffer, s.cmd.buffer);
+ strcpy(cmd->svccmd, s.cmd.svccmd);
+ return sizeof(cmd);
+}
+
+static int
+parse_cmd(struct state *s)
+{+ int n = 0;
+ for(;;){+ if(n > s->size)
+ return -1;
+ switch(s->base[n]){+ case '\0':
+ case '\r':
+ case '\n':
+ // Check we aren't at "quit"
+ s->base[n] = '\0';
+ s->cmd.type = CloneCmd;
+ if(strncmp("quit", s->base, n) == 0)+ s->cmd.type = QuitCmd;
+ else
+ s->cmd.data = strdup(s->base);
+ return 0;
+ case ' ':
+ case '\t':
+ // The overwhelming majority will be feed activity
+ if(strncmp(s->base, "feed", 4) == 0)
+ s->cmd.type = FeedCmd;
+ else if(strncmp(s->base, "asi", 3) == 0)
+ s->cmd.type = SideCmd;
+ else if(strncmp(s->base, "nav", 3) == 0)
+ s->cmd.type = NavCmd;
+ else if(strncmp(s->base, "titl", 4) == 0)
+ s->cmd.type = TitleCmd;
+ else if(strncmp(s->base, "ima", 3) == 0)
+ s->cmd.type = ImageCmd;
+ else if(strncmp(s->base, "del", 3) == 0)
+ s->cmd.type = DeleteCmd;
+ else if(strncmp(s->base, "rem", 3) == 0)
+ s->cmd.type = RemoveCmd;
+ else if(strncmp(s->base, "noti", 4) == 0)
+ s->cmd.type = NotifyCmd;
+ else if(strncmp(s->base, "err", 3) == 0)
+ s->cmd.type = ErrorCmd;
+ else if(strncmp(s->base, "stat", 4) == 0)
+ s->cmd.type = StatusCmd;
+ else if(strncmp(s->base, "crea", 4) == 0)
+ s->cmd.type = CreateCmd;
+ else if(strncmp(s->base, "buff", 4) == 0)
+ s->cmd.type = BufferCmd;
+ else if(strncmp(s->base, "mark", 4) == 0)
+ s->cmd.type = MarkdownCmd;
+ else {+ s->cmd.type = ServiceCmd;
+ snprint(s->cmd.svccmd, n+1, s->base);
+ }
+ s->size -= n;
+ n++;
+ s->base += n;
+ s->fn = (*parse_from);
+ return 1;
+ default:
+ n++;
+ }
+ }
+}
+
+static int
+parse_from(struct state *s)
+{+ int n = 0;
+ for(;;){+ if(n > s->size || n > MaxBuflen)
+ return -1;
+ switch(s->base[n]){+ // leading spaces/tabs, ignore
+ case ' ':
+ case '\t':
+ // NOTE: This moves our pointer forward so n++ isn't necessary
+ s->size--;
+ s->base++;
+ break;
+ case 0:
+ strncpy(s->cmd.buffer, s->base, n);
+ return 0;
+ case '\n':
+ case '\r':
+ s->base[n] = '\0';
+ strncpy(s->cmd.buffer, s->base, n+1);
+ // return if we have no data to parse
+ if (n >= s->size)
+ return 0;
+ s->size -=n;
+ n++;
+ s->base += n;
+ s->fn = (*parse_data);
+ return 1;
+ default:
+ n++;
+ }
+ }
+}
+
+static int
+parse_data(struct state *s)
+{+ int n = 0;
+ char c;
+
+ for(;;) {+ if(n >= s->size || n >= MaxBuflen)
+ return -1;
+ c = s->base[n];
+ switch(c){+ // Useful control chars
+ case '\n':
+ case '\t':
+ case '\r':
+ break;
+ default:
+ // Anything under a space is an unhandled control char
+ if(c < ' '){+ if(n > 0 ){ + s->cmd.data = strdup(s->base);
+ s->cmd.data[n] = 0;
+ }
+ return 0;
+ }
+ }
+ n++;
+ }
+}
--- /dev/null
+++ b/fs/fs.c
@@ -1,0 +1,87 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#include "svc.h"
+
+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;
+}
+
+Srv svcfs =
+{+ .start=svcstart,
+ .attach=svcattach,
+ .stat=svcstat,
+ .walk1=svcwalk1,
+ .clone=svcclone,
+ .open=svcopen,
+ .read=svcread,
+ .write=svcwrite,
+ .flush=svcflush,
+ .destroyfid=svcdestroyfid,
+ .end=svcend,
+};
+
+void
+usage(void)
+{+ fprint(2, "usage: %s [-Dd] [-m mtpt] [-s service] [-l logdir]\n", argv0);
+ exits("usage");+}
+
+void
+threadmain(int argc, char *argv[])
+{+ char *svcfile;
+ fmtinstall('N', Tconv);+ fmtinstall('!', Nconv);+ fmtinstall('C', Cconv);+ user = getuser();
+ mtpt = "/mnt/svc";
+ logdir = "/tmp/svc";
+ svcfile = nil;
+
+ ARGBEGIN {+ case 'D':
+ chatty9p++;
+ break;
+ case 'm':
+ mtpt = EARGF(usage());
+ break;
+ case 'l':
+ logdir = EARGF(usage());
+ break;
+ case 'd':
+ debug++;
+ break;
+ case 's':
+ svcfile = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ argv0 = "alt/fs";
+
+ create(logdir, OREAD, DMDIR | 0755);
+ threadpostmountsrv(&svcfs, svcfile, mtpt, MCREATE);
+ exits(nil);
+}
--- /dev/null
+++ b/fs/mkfile
@@ -1,0 +1,20 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin/svc
+
+TARG=fs
+
+HFILES=svc.h
+
+OFILES=\
+ fs.$O\
+ buffer.$O\
+ client.$O\
+ cmd.$O\
+ convS2C.$O\
+ service.$O\
+ notification.$O\
+ tabs.$O\
+
+</sys/src/cmd/mkone
+
--- /dev/null
+++ b/fs/notification.c
@@ -1,0 +1,16 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#include "svc.h"
+
+int
+Nconv(Fmt *fp)
+{+ Notify *n;
+
+ n = va_arg(fp->args, Notify*);
+ return fmtstrcpy(fp, n->data);
+}
--- /dev/null
+++ b/fs/service.c
@@ -1,0 +1,518 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#include "svc.h"
+
+#define SERVICEID(c) ((int)(((Service*)(c)) - service))
+
+enum {+ Qsroot,
+ Qclone,
+ Qlist,
+ Qservices,
+ Qctl,
+ Qmax,
+};
+
+static char *svctab[] = {+ "/",
+ "clone",
+ "list",
+ nil,
+ "ctl",
+ nil,
+};
+
+Srv clfs =
+{+ .start=clstart,
+ .attach=clattach,
+ .stat=clstat,
+ .walk1=clwalk1,
+ .clone=clclone,
+ .open=clopen,
+ .read=clread,
+ .write=clwrite,
+ .flush=clflush,
+ .destroyfid=cldestroyfid,
+ .end=clend,
+};
+
+typedef struct Svcfid Svcfid;
+typedef struct Service Service;
+
+struct Svcfid
+{ int level;+ Service *svc;
+};
+
+struct Service
+{+ Ref;
+
+ Channel *cmds;
+ Channel *input;
+ Buffer *base;
+ char *name;
+ int isInitialized;
+ int childpid;
+};
+
+static Srv *fs;
+Service service[64];
+int nservice;
+Lock svclock;
+
+static Service*
+newservice(void)
+{+ Service *svc;
+ int i;
+
+ lock(&svclock);
+ for(i = 0; i < nservice; i++)
+ if(service[i].ref == 0)
+ break;
+ if(i >= nelem(service))
+ return nil;
+ if(i == nservice)
+ nservice++;
+ svc = &service[i];
+ svc->ref++;
+
+ // NOTE: If you're sending more commands than this before they are processed, up this number
+ // But also it might be time to question your design, because commands really should not be taking long
+ svc->cmds = chancreate(sizeof(Cmd*), 8);
+ svc->base = bufferCreate(svc->cmds, svc->input);
+ svc->isInitialized = 0;
+ unlock(&svclock);
+ return svc;
+}
+
+static void
+rmservice(Service *s)
+{+ int i, found = 0;
+
+ lock(&svclock);
+ for(i = 0; i < nservice; i++){+ if(service[i].name == s->name)
+ found++;
+ /* Shift back */
+ if(found && i < nservice)
+ service[i] = service[i+1];
+ }
+ if(found)
+ nservice--;
+ unlock(&svclock);
+}
+
+static void
+freeservice(Service *s)
+{+ if(s == nil)
+ return;
+ chanfree(s->cmds);
+ memset(s, 0, sizeof(*s));
+}
+
+static void*
+wfaux(Svcfid *f)
+{+ if(f->level < Qservices)
+ return nil;
+ return f->svc;
+}
+
+static void
+svcmkqid(Qid *q, int level, void *)
+{+ q->type = 0;
+ q->vers = 0;
+ switch(level){+ case Qsroot:
+ case Qservices:
+ q->type = QTDIR;
+ default:
+ ;
+ }
+}
+
+static void
+svcmkdir(Dir *d, int level, void *aux)
+{+ char buf[1024];
+
+ memset(d, 0, sizeof(*d));
+ svcmkqid(&d->qid, level, aux);
+ d->mode = 0444;
+ d->atime = d->mtime = time(0);
+ d->uid = estrdup(user);
+ d->gid = estrdup(user);
+ d->muid = estrdup(user);
+ if(d->qid.type & QTDIR)
+ d->mode |= DMDIR | 0111;
+ switch(level){+ case Qservices:
+ memset(buf, 0, sizeof(buf));
+ snprint(buf, sizeof(buf), "%d", SERVICEID(aux));
+ d->name = estrdup(buf);
+ break;
+ case Qctl:
+ case Qclone:
+ case Qlist:
+ d->mode = 0666;
+ /* Fallthrough */
+ default:
+ d->name = estrdup(svctab[level]);
+ }
+}
+
+void
+svcattach(Req *r)
+{+ Svcfid *f;
+
+ // No anames
+ if(r->ifcall.aname && r->ifcall.aname[0]){+ respond(r, "invalid attach specifier");
+ return;
+ }
+ f = mallocz(sizeof(*f), 1);
+ f->level = Qsroot;
+ svcmkqid(&r->fid->qid, f->level, wfaux(f));
+ r->ofcall.qid = r->fid->qid;
+ r->fid->aux = f;
+ respond(r, nil);
+}
+
+void
+svcstat(Req *r)
+{+ Svcfid *f;
+
+ f = r->fid->aux;
+ svcmkdir(&r->d, f->level, wfaux(f));
+ respond(r, nil);
+}
+
+char*
+svcwalk1(Fid *fid, char *name, Qid *qid)
+{+ Svcfid *f;
+ int i, j;
+
+ if(!(fid->qid.type&QTDIR))
+ return "walk in non-directory";
+
+ f = fid->aux;
+ if(strcmp(name, "..")==0){+ switch(f->level){+ case Qsroot:
+ case Qlist:
+ break;
+ case Qservices:
+ f->level = Qsroot;
+ break;
+ default:
+ f->level = Qservices;
+ }
+ } else {+ for(i = f->level+1; i < nelem(svctab); i++){+ if(svctab[i])
+ if(strcmp(name, svctab[i]) == 0)
+ goto Out;
+ if(i == Qservices){+ j = atoi(name);
+ if(j >= 0 && j < nservice){+ f->svc = &service[j];
+ incref(f->svc);
+ goto Out;
+ }
+ }
+ }
+Out:
+ if(i >= nelem(svctab))
+ return "directory entry not found";
+ f->level = i;
+ }
+ svcmkqid(qid, f->level, wfaux(f));
+ fid->qid = *qid;
+ return nil;
+}
+
+char *
+svcclone(Fid *oldfid, Fid *newfid)
+{+ Svcfid *f, *o;
+ o = oldfid->aux;
+ if(o == nil)
+ return "bad fid";
+ f = mallocz(sizeof(*f), 1);
+ memmove(f, o, sizeof(*f));
+ if(f->svc)
+ incref(f->svc);
+ newfid->aux = f;
+ return nil;
+}
+
+void
+svcopen(Req *r)
+{+ Svcfid *f;
+ Service *svc;
+
+ f = r->fid->aux;
+ if(f->level == Qclone){+ if((svc = newservice()) == nil){+ respond(r, "no more services");
+ return;
+ }
+ f->level = Qctl;
+ f->svc = svc;
+ svcmkqid(&r->fid->qid, f->level, wfaux(f));
+ r->ofcall.qid = r->fid->qid;
+ }
+ respond(r, nil);
+}
+
+static int
+rootgen(int i, Dir *d, void *)
+{+ i += Qsroot+1;
+ if(i < Qservices){+ svcmkdir(d, i, 0);
+ return 0;
+ }
+ i -= Qservices;
+ if(i < nservice){+ svcmkdir(d, Qservices, &service[i]);
+ return 0;
+ }
+ return -1;
+}
+
+static int
+servicegen(int i, Dir *d, void *aux)
+{+ i += Qservices+1;
+ if(i >= Qmax)
+ return -1;
+ svcmkdir(d, i, aux);
+ return 0;
+}
+
+enum {+ CmdRead,
+ InputRead,
+};
+
+
+void
+svcread(Req *r)
+{+
+ Cmd *cmd;
+ char buf[CmdSize];
+ Svcfid *f;
+ int i, n;
+
+ f = r->fid->aux;
+ memset(buf, 0, CmdSize);
+
+ switch(f->level){+ case Qsroot:
+ dirread9p(r, rootgen, nil);
+ respond(r, nil);
+ return;
+ case Qservices:
+ dirread9p(r, servicegen, nil);
+ respond(r, nil);
+ return;
+ case Qlist:
+ n = 0;
+ for(i = 0; i < nservice; i++)
+ n += snprint(buf+n, sizeof(buf) - n, "%s\n", service[i].name);
+ readstr(r, buf);
+ respond(r, nil);
+ return;
+ case Qctl:
+ /* Wait for any data/command from the client */
+ srvrelease(fs);
+ cmd = recvp(f->svc->cmds);
+ if(cmd && cmd->type != FlushCmd){+ n = snprint(r->ofcall.data, r->ifcall.count, "%C", cmd);
+ r->ofcall.count = n;
+ }
+ srvacquire(fs);
+ respond(r, nil);
+ return;
+
+ }
+ respond(r, "not implemented");
+}
+
+
+void
+svcwrite(Req *r)
+{+ int n;
+ Svcfid *f;
+ Cmd cmd;
+ Buffer *b;
+ Dir *d;
+ char *p, *s;
+ char path[1024];
+
+ f = r->fid->aux;
+
+ if(f->level == Qctl){+ n = r->ofcall.count = r->ifcall.count;
+ p = mallocz(n, 1);
+ memmove(p, r->ifcall.data, n);
+ n = convS2C(&cmd, p, n);
+ if(n <= 0){+ respond(r, "malformed command");
+ goto Out;
+ }
+ switch(cmd.type){+ case CloneCmd:
+ if(!f->svc->isInitialized){+ f->svc->name = estrdup(cmd.data);
+ strcpy(f->svc->base->name, cmd.data);
+ memset(path, 0, sizeof(path));
+ snprint(path, sizeof(path), "%s/%s", logdir, cmd.data);
+ close(create(path, OREAD, DMDIR | 0755));
+ clfs.aux = f->svc->base;
+ f->svc->childpid = threadpostsrv(&clfs, strdup(cmd.data));
+ if(f->svc->childpid > 0){+ s = emalloc(10+strlen(cmd.data));
+ strcpy(s, "/srv/");
+ strcat(s, cmd.data);
+ d = dirstat(s);
+ d->mode = 0666;
+ if(dirwstat(s, d) < 0){+ rmservice(f->svc);
+ respond(r, "unable to set mode");
+ goto Out;
+ }
+ free(s);
+ f->svc->isInitialized++;
+ r->fid->aux = f;
+ respond(r, nil);
+ } else {+ rmservice(f->svc);
+ respond(r, "Unable to post to srv");
+ }
+ goto Out;
+ }
+ /* Ignore, something weird going on */
+ respond(r, nil);
+ goto Out;
+ case CreateCmd:
+ respond(r, bufferPush(f->svc->base, cmd.buffer));
+ goto Out;
+ case NotifyCmd:
+ respond(r, "not implemented yet");
+ goto Out;
+ case DeleteCmd:
+ respond(r, bufferDrop(f->svc->base, cmd.buffer));
+ goto Out;
+ case ErrorCmd:
+ respond(r, "not implemented yet");
+ goto Out;
+ }
+ if(!cmd.data){+ respond(r, "expected data");
+ goto Out;
+ }
+ if(b = bufferSearch(f->svc->base, cmd.buffer)) {+ qlock(&b->l);
+ switch(cmd.type){+ case FeedCmd:
+
+ d = dirfstat(b->fd);
+ pwrite(b->fd, cmd.data, strlen(cmd.data), d->length);
+ free(d);
+ if(rwakeupall(&b->rz) == 0)
+ b->unread++;
+ break;
+ case StatusCmd:
+ strcpy(b->status, cmd.data);
+ break;
+ case TitleCmd:
+ strcpy(b->title, cmd.data);
+ break;
+ case SideCmd:
+ strcpy(b->aside, cmd.data);
+ break;
+ }
+ qunlock(&b->l);
+ respond(r, nil);
+ } else
+ respond(r, "buffer not found");
+ Out:
+ free(p);
+ return;
+ }
+ respond(r, "not implemented");
+}
+
+void
+svcflush(Req *r)
+{+ Cmd cmd;
+ int i;
+
+ for(i = 0; i < nservice; i++)
+ if(service[i].ref > 0)
+ if((bufferSearchTag(service[i].base, r->tag))){+ memset(&cmd, 0, sizeof(Cmd));
+ cmd.type = FlushCmd;
+ send(service[i].cmds, &cmd);
+ break;
+ }
+ respond(r, nil);
+}
+
+void
+svcdestroyfid(Fid *fid)
+{+ Svcfid *f;
+
+ if(f = fid->aux){+ if(f->svc && f->svc->childpid)
+ postnote(PNPROC, f->svc->childpid, "done");
+ // TODO: Uncomment this after we are good to go, this is our keepalive roughly
+ //fid->aux = nil;
+ //if(f->svc)
+ // freeservice(f->svc);
+ }
+ free(f);
+}
+
+void
+svcstart(Srv* s)
+{+ fs = s;
+ if(mtpt != nil)
+ unmount(nil, mtpt);
+}
+
+void
+svcend(Srv*)
+{+ int i;
+
+ if(mtpt != nil)
+ unmount(nil, mtpt);
+ for(i = 0; i < nservice; i++)
+ if(service[i].ref){+ postnote(PNPROC, service[i].childpid, "done");
+ }
+ postnote(PNPROC, getpid(), "done");
+ threadexitsall(nil);
+}
--- /dev/null
+++ b/fs/svc.h
@@ -1,0 +1,109 @@
+typedef struct Buffer Buffer;
+typedef struct Notify Notify;
+typedef struct Cmd Cmd;
+
+enum
+{+ CloneCmd,
+ CreateCmd,
+ DeleteCmd,
+ RemoveCmd,
+ NotifyCmd,
+ ErrorCmd,
+ StatusCmd,
+ SideCmd,
+ NavCmd,
+ TitleCmd,
+ ImageCmd,
+ FeedCmd,
+ QuitCmd,
+ ServiceCmd,
+ InputCmd,
+ FlushCmd,
+ BufferCmd,
+ OpenCmd,
+ MarkdownCmd,
+
+ // TODO: Move data to the stack
+ MaxBuflen = 128,
+ MaxDatalen = 1024,
+ CmdSize = MaxBuflen * 2 + 1 + MaxDatalen,
+};
+
+struct Buffer
+{+ QLock l;
+ char name[MaxBuflen];
+ char title[512];
+ char status[512];
+ char *aside;
+ int fd; // feed
+ int tag; // feed
+ int unread;
+ Channel *cmds;
+ Channel *input;
+ Notify *notify;
+ Buffer *next;
+ Rendez rz;
+};
+
+struct Notify
+{+ char *data;
+ Notify *next;
+};
+
+struct Cmd
+{+ // Potentially big
+ int type;
+ char buffer[MaxBuflen];
+ char svccmd[MaxBuflen];
+ char *data;
+};
+
+Buffer *bufferCreate(Channel*, Channel*);
+Buffer *bufferSearch(Buffer*, char*);
+Buffer *bufferSearchTag(Buffer*, ulong);
+char *bufferDrop(Buffer*, char*);
+char *bufferPush(Buffer*, char*);
+void bufferDestroy(Buffer*);
+
+int Tconv(Fmt*);
+int Nconv(Fmt*);
+int Cconv(Fmt*);
+
+void* emalloc(int);
+char* estrdup(char*);
+
+char *mtpt;
+char *srvpt;
+char *user;
+char *logdir;
+int debug;
+
+uint convS2C(Cmd*, char*, uint);
+
+void clattach(Req*);
+void clstat(Req*);
+char *clwalk1(Fid*, char*, Qid*);
+char *clclone(Fid*, Fid*);
+void clopen(Req*);
+void clread(Req*);
+void clwrite(Req*);
+void clflush(Req*);
+void cldestroyfid(Fid*);
+void clstart(Srv*);
+void clend(Srv*);
+
+void svcattach(Req*);
+void svcstat(Req*);
+char *svcwalk1(Fid*, char*, Qid*);
+char *svcclone(Fid*, Fid*);
+void svcopen(Req*);
+void svcread(Req*);
+void svcwrite(Req*);
+void svcflush(Req*);
+void svcdestroyfid(Fid*);
+void svcstart(Srv*);
+void svcend(Srv*);
--- /dev/null
+++ b/fs/tabs.c
@@ -1,0 +1,21 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#include "svc.h"
+
+int
+Tconv(Fmt *fp)
+{+ char s[1024];
+ Buffer *b;
+
+ b = va_arg(fp->args, Buffer*);
+ if(b->notify)
+ snprint(s, sizeof(s), "![%d] %s", b->unread, b->name);
+ else
+ snprint(s, sizeof(s), "[%d] %s", b->unread, b->name);
+ return fmtstrcpy(fp, s);
+}
--
⑨