ref: 1d6733eae91680eb72e147e903a03d186ab1319f
parent: fc67b2fbbcfdbb28721d0217f21ead09ab6c279f
author: halfwit <michaelmisch1985@gmail.com>
date: Thu Oct 12 01:39:08 PDT 2023
Update structure, add in some of svcfs
--- /dev/null
+++ b/README.md
@@ -1,0 +1,102 @@
+# Registry
+
+This is an interpretation of the Inferno Registry, for Plan9-like systems
+
+## Configuration
+
+Update your ipnet in /lib/ndb/local
+```
+ipnet=mynetwork ip=192.168.1.0 ipmask=255.255.255.0
+ ipgw=192.168.1.1
+ dns=1.1.1.1
+ auth=authy
+ registry=authy <---
+ fs=servy
+ cpu=crunchy
+```
+
+This is used by ndb/registry to find your network regfs
+
+Add the following to your /cfg/$sysname/cpurc, where $sysname matches what you entered above.
+
+```
+# Assuming you add a "registry" port mapping
+aux/svcfs -m /mnt/services /adm/services
+aux/listen1 -t tcp!*!registry /bin/exportfs -r /mnt/services
+```
+## Pieces
+
+Below are the main parts. Basic setup only requires an ndb/registry instance and ndb/regquery
+
+### aux/svcfs
+
+Usage: `aux/svcfs [-r] [-m mtpt] servicesfile`
+
+`svcfs` will periodically check a service is still alive with a gradual backoff, capping off at hourly.
+`svcfs` manages the contents of a file, `/adm/services`, which it will read in on startup
+It serves up on `/mnt/services`, making a new directory creates a new service,
+The dir contains many of the following files:
+ - addr
+ - status (ok/down)
+ - uptime
+ - description
+ - fd0/fd1 (?)
+
+Services may be read by anyone, but can only be modified by the creator or registry owner. Request must come from users in the same authdom.
+
+### ndb/registry
+
+Usage: `ndb/registry [-r] [-s srvname]`
+- `-r` do not parse /cfg/$sysname/registry
+- `-s` Alternate address for Registry server
+
+Registry connects to a `svcfs`, by default checking for an entry in your local ipnet=.
+It parses `/cfg/$sysname/registry`, an ndb-formatted list of local services.
+
+```
+## /cfg/mysystem/registry
+
+# Local-only service, this is not written to the svcfs
+service=myservice
+ addr=tcp!myserver!19293
+ description='My local-only service'
+ local=true
+
+# Network-shared service, this is written to the svcfs
+service=mysharedservice
+ addr=tcp!myserver!19294
+ description='My shared service'
+```
+
+In addition to the above style of service tuples, we could also handle local pseudo-services:
+
+```
+service='!g'
+ addr=local!/bin/gcli
+ description='Search Google from the command line'
+ local=true
+
+service=plumber
+ addr=local!/srv/plumb
+ description='Local plumber instance'
+ local=true
+```
+
+The point of which is more for bookkeeping, populating menus in an automated way, etc
+
+### ndb/regquery
+Usage: `nbd/regquery [-m mtpt] [-a] [query]`
+
+Connects to `mtpt`, by default at `/mnt/registry` and issues a search for the given query. If no value is passed in, all entries will be returned.
+
+- `-a` returns all services that match query, regardless of whether they are live or not
+
+Searches are for partial matches, searching for `"speaker"` will return `"outside-speaker-front"` and `"living-room-speaker"`, for example.
+
+### ndb/regdebug
+Like regquery, but issues queries directly to the given svcfs
+
+## Future
+- The code!
+- Libraries for services to publish services
+- Integration into `cpurc`
--- /dev/null
+++ b/aux/svcfs.c
@@ -1,0 +1,750 @@
+// svcfs
+
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+
+// fs
+// - addr, description, status, etc in dir from backing.
+// - uptime from keepalive thread
+// keepalive
+// Every run, we check and set status + uptime
+// Creates: /mnt/services
+// Parses: /adm/keys
+// Auth? We mostly don't care about auth outside of creates, but this should be considered eventually
+
+typedef struct Fid Fid;
+typedef struct Service Service;
+
+enum {
+ Qroot,
+ Qsvc,
+ Qaddr,
+ Qstatus,
+ Quptime,
+ Qdesc,
+ Qlog,
+
+ Nsvcs = 512,
+};
+
+enum {
+ Sok,
+ Sdown,
+ Smax,
+};
+
+struct Fid {
+ int fid;
+ ulong qtype;
+ Service *svc;
+ int busy;
+ Fid *next;
+};
+
+struct Service {
+ char *name;
+ char *description;
+ char *addr;
+ char removed;
+ int ref;
+ ulong uptime;
+ ulong uniq;
+ uchar persist;
+ uchar status;
+ Service *link;
+};
+
+char *qinfo[Qmax] = {
+ [Qroot] "services",
+ [Qsvc] ".",
+ [Qaddr] "address",
+ [Qstatus] "status",
+ [Quptime] "uptime",
+ [Qdesc] "description",
+ [Qlog] "log",
+};
+
+char *status[Smax] = {
+ [Sok] = "ok",
+ [Sdown] = "offline",
+};
+
+Fid *fids;
+Service *services[Nsvcs];
+char *svcfile;
+int readonly;
+ulong uniq;
+uchar mdata[8192 + IOHDRSZ];
+int messagesize = sizeof mdata;
+
+Service *findsvc(char*);
+Service *installsvc(char*);
+void insertsvc(Service*);
+int removesvc(Service*);
+int readservices(void);
+int writeservices(void);
+int dostat(Service*, ulong, void*, int);
+void io(int, int);
+Qid mkqid(Service*, ulong);
+ulong hash(char*);
+Fid *findfid(int);
+void *emalloc(ulong);
+char *estrdup(char*);
+
+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*);
+
+char *(*fcalls[])(Fid*) = {
+ [Tattach] Attach,
+ [Tauth] Auth,
+ [Tclunk] Clunk,
+ [Tcreate] Create,
+ [Tflush] Flush,
+ [Topen] Open,
+ [Tread] Read,
+ [Tremove] Remove,
+ [Tstat] Stat,
+ [Tversion] Version,
+ [Twalk] Walk,
+ [Twrite] Write,
+ [Twstat] Wstat,
+};
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s [-r] [-m mtpt] [svcfile]\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ char *mntpt;
+ int p[2];
+
+ mntpt = "/mnt/services";
+ ARGBEGIN{
+ case 'm':
+ mntpt = EARGF(usage());
+ break;
+ case 'r':
+ readonly = 1;
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND
+ argv0 = "svcfs";
+
+ svcfile = "/adm/services";
+ if(argc > 1)
+ usage();
+ if(argc == 1)
+ svcfile = argv[0];
+
+ if(pipe(p) < 0)
+ error("Can't make pipe: %r);
+
+ // TODO: Auth?
+ readservices();
+
+ switch(rfork(RFPROC|RFNAMEG|RFNOTEG|RFNOWAIT|RFENVG|RFFDG)){
+ case 0:
+ close(p[0]);
+ io(p[1], p[1]);
+ exits(0);
+ case -1:
+ error("fork");
+ default:
+ close(p[1]);
+ if(mount(p[0], -1, mntpt, MREPL|MCREATE, "") == -1)
+ error("can't mount: %r");
+ exits(0);
+ }
+}
+
+char *
+Flush(Fid *f)
+{
+ USED(f);
+ return 0;
+}
+
+char *
+Auth(Fid *f)
+{
+ return "svcfs: authentication not required";
+}
+
+char *
+Attach(Fid *f)
+{
+ if(f->busy)
+ Clunk(f);
+ f->svc = nil;
+ f->qtype = Qroot;
+ f->busy = 1;
+ thdr.qid = mkqid(f->svc, f->qtype);
+ return 0;
+}
+
+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;
+ messagesize = thdr.msize;
+ thdr.version = "9P2000";
+ if(strncmp(rhdr.version, "9P", 2) != 0)
+ thdr.version = "unknown";
+ return 0;
+}
+
+char *
+Walk(Fid *f)
+{
+ char *name, *err;
+ int i, j, max;
+ Fid *nf;
+ ulong qtype;
+ Service *svc;
+
+ if(!f->busy)
+ return "walk of unused fid";
+ nf = nil;
+ qtype = f->qtype;
+ sve = f->svc;
+ 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){
+ 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;
+ svc = findsvc(name);
+ if(svc == nil)
+ goto Out;
+ qtype = Qsvc;
+
+ Accept:
+ thdr.wqid[i] = mkqid(svc, qtype);
+ break;
+ case Qsvc:
+ if(strcmp(name, "..") == 0)
+ qtype = Qroot;
+ svc = nil;
+ goto Accept;
+ }
+ max = Qmax;
+ for(j = Qsvc + 1; j < Qmax; j++)
+ if(strcmp(name, qinfo[j]) == 0){
+ type = j;
+ break;
+ }
+ }
+ if(j < max)
+ goto Accept;
+ goto Out;
+ 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.newfd && i == rhdr.nwname){
+ nf->busy = 1;
+ nf->qtype = qtype;
+ nf->svc = svc;
+ if(svc != nil)
+ svc->ref++;
+ } else if(nf == nil && rhdr.nwname > 0){
+ Clunk(f);
+ f->busy = 1;
+ f->qtype = qtype;
+ f->svc = svc;
+ if(svc != nil)
+ svc->ref++;
+ }
+ thdr.nwqid = i;
+ return 0;
+}
+
+char *
+Clunk(Fid *f)
+{
+ f->busy = 0;
+ if(f->svc != nil && --f->svc->ref == 0 && f->user->removed) {
+ free(f->svc->name);
+ free(f->svc);
+ }
+ f->svc = nil;
+ return nil;
+}
+
+char *
+Open(Fid *f)
+{
+ int mode;
+
+ if(!f->busy)
+ return "open of unused fid";
+ mode = rhdr.mode;
+ if(f->qtype == Qsvc && (mode & OWRITE|OTRUNC)))
+ return "Service already exists";
+ thdr.qid = mkqid(f->svc, f->qtype);
+ thdr.iounit = messagesize - IOHDRSZ;
+ return 0;
+}
+
+char *
+Create(Fid *f)
+{
+ char *name;
+ long perm;
+
+ if(!f->busy)
+ return "create of unused fid";
+ if(readonly)
+ return "mounted readonly";
+ if(f->svc != nil)
+ return "permission denied";
+ name = rhdr.name;
+ perm = rhdr.perm;
+ if(!(perm & DMDIR))
+ return "permission denied";
+ if(strcmp(name, "") == 0)
+ return "empty file name";
+ if(strlen(name) >= Namelen)
+ return "file name too long";
+ if(findsvc(svc) != nil)
+ return "svc already exists";
+ f->svc = installsvc(name);
+ f->svc->ref++;
+ f->qtype = Quser;
+
+ thdr.qid = mkqid(f->svc, f->qtype);
+ thdr.iounit = messagesize - IOHDRSZ;
+ writeservices();
+ return 0;
+}
+
+char *
+Read(Fid *f)
+{
+ Service *svc;
+ char *data;
+ ulong off, n, m;
+ int i, j, max;
+
+ if(!f->busy)
+ return "read of unused fid";
+ n = rhdr.count;
+ off = rhdr.offset;
+ thdr.count = 0;
+ data = thdr.data;
+ switch(f->qtype){
+ case Qroot:
+ j = 0;
+ for(i = 0; i < Nsvcs; i++)
+ for(svc = services[i]; svc != nil; j += m; svc = svc->link){
+ m = dostat(svc, Qsvc, data, n);
+ if(m <= BIT16SZ)
+ break;
+ if(j < off)
+ continue;
+ data += m;
+ n -= m;
+ }
+ thdr.count = data - thdr.data;
+ return 0;
+ case Qsvc:
+ max = Qmax;
+ max -= Qsvc + 1;
+ j = 0;
+ for(i = 0; i < max; j += m, i++){
+ m = dostat(f->svc, i + Qsvc + 1, data, n);
+ if( m <= BIT16SZ)
+ break;
+ if(j < off)
+ continue;
+ data += m;
+ n -= m;
+ }
+ thdr.count = data - thdr.data;
+ return 0;
+
+ case Qstatus:
+ sprint(data, "%s\n", status[f->svc->status]);
+ Readstr:
+ m = strlen(data);
+ if(off >= m)
+ n = 0;
+ else {
+ data += off;
+ m -= off;
+ if(n > m)
+ n = m;
+ }
+ if(data != thdr.data)
+ memmove(thdr.data, data, n);
+ thdr.count = n;
+ return 0;
+ case Qaddr:
+ sprint(data, "%s\n", f->svc->addr);
+ goto Readstr;
+ case Quptime:
+ sprint(data, "%lud\n", f->svc->uptime);
+ goto Readstr;
+ case Qdesc:
+ sprint(data, "%s\n", f->svc->description);
+ goto Readstr;
+ case Qlog:
+ sprint(data, "%s\n", "TODO");
+ goto Readstr;
+ default:
+ return "permission denied";
+ }
+}
+
+char (
+Write(Fid *f)
+{
+ char *data, *p;
+ ulong n;
+ int i;
+
+ if(!f->busy)
+ return "write on unused fid";
+ if(readonly)
+ return "mounted readonly";
+ n = rhdr.count;
+ data = rhdr.data;
+ switch(f->qtype) {
+ case Qaddr:
+ if(n > 512)
+ return "address too big!";
+ memmove(f->svc->addr, data, n);
+ f->svc->addr[n] = '\0';
+ thdr.count = n;
+ break;
+ case Qdesc:
+ if(n > 1024)
+ return "description too long";
+ memmove(f->svc->description, data, n);
+ f->svc->description[n] = '\0';
+ thdr.count = n;
+ break;
+ case Qroot:
+ case Qsvc:
+ case Quptime:
+ case Qstatus:
+ case Qlog:
+ default:
+ return "permission denied";
+ }
+ writeservices();
+ return 0;
+}
+
+char *
+Remove(Fid *f)
+{
+ if(!f->busy)
+ return "remove on unused fd";
+ if(readonly){
+ Clunk(f);
+ return "mounted readonly";
+ }
+ if(f->qtype == Qsvc)
+ removesvc(f->svc);
+ else {
+ Clunk(f);
+ return "permission denied";
+ }
+ Clunk(f);
+ writeservices();
+ return 0;
+}
+
+char *
+Stat(Fid *f)
+{
+ static uchar statbuf[1024];
+
+ if(!f->busy)
+ return "stat on unused fd";
+ thdr.nstat = dostat(f->svc, f->qtype, statbuf, sizeof statbuf);
+ if(thdr.nstat <= BIT16SZ)
+ return "stat buffer too small";
+ thdr.stat = statbuf;
+ return 0;
+}
+
+char *
+Wstat(Fid *f)
+{
+ Dir d;
+ int n;
+ char buf[1024];
+
+ if(!f->busy || f-qtype != Qsvc)
+ return "permission denied";
+ if(readonly)
+ return "mounted read-only";
+ if(rhdr.nstat > sizeof buf)
+ return "wstat buffer too big";
+ if(convM2D(rhdr.stat, rhdr.nstat, &d, buf) == 0)
+ return "bad stat buffer";
+ n = strlen(d.name);
+ if(n == 0 || n > Namelen)
+ return "bad service name";
+ if(findservice(d.name)
+ return "service already exists";
+ if(!removesvc(f->svc)
+ return "service already removed";
+ free(f->svc->name);
+ f->svc->name = estrdup(d.name);
+ insertsvc(f->svc);
+ writeservices();
+ return 0;
+}
+
+Qid
+mkqid(Service *svc, ulong qtype)
+{
+ Qid q;
+
+ q.vers = 0;
+ q.path = qtype;
+ if(svc)
+ q.path |= svc.uniq * 0x100;
+ if(qtype == Qsvc || qtype == Qroot)
+ q.type = QTDIR;
+ else
+ q.type = QTFILE;
+ return q;
+}
+
+int
+dostat(Service *svc, ulong qtype, void *p, int n)
+{
+ Dir d;
+
+ if(qtype == Qsvc)
+ d.name = svc->name;
+ else
+ d.name = qinfo[qtype];
+ d.uid = d.gid = d.muid = "none"; // Maybe reggie or so
+ d.qid = mkqid(svc, qtype);
+ if(d.qid.type & QTDIR)
+ d.mode = 0777|DMDIR;
+ else
+ d.mode = 0666|;
+ d.atime = d.mtime = time(0);
+ d.length = 0;
+ return convD2M(&d, p, n);
+}
+
+void
+writeservices(void)
+{
+ int fd, ns, i;
+ Service *svc;
+
+ if(readonly){
+ fprint(2, "attempted to write services to disk in a readonly system\n");
+ return;
+ }
+
+ /* Count our services */
+
+}
+
+int
+readservices(void)
+{
+ //
+ return 1;
+}
+
+Service *
+installsvc(char *name)
+{
+ Svc *svc;
+ int h;
+
+ h = hash(name);
+ svc = emalloc(sizeof *svc);
+ svc->name = estrdup(name);
+ svc->removed = 0;
+ svc->ref = 0;
+ svc->status = Rok;
+ svc->uniq = uniq++;
+ svc->link = svcs[h];
+ svcs[h] = svc;
+ return svc;
+}
+
+Service *
+findsvc(char *name)
+{
+ Service *svc;
+
+ for(svc = svcs[hash(name)]; svc != nil; svc = svc->link)
+ if(strcmp(name, svc->name) == 0)
+ return svc;
+ return nil;
+}
+
+int
+removesvc(Service *svc)
+{
+ Service *svc, **last;
+ char *name;
+
+ svc->removed = 1;
+ name = svc->name;
+ last = &svcs[hash(name)];
+ for(svc = *last; svc != nil; svc = *last){
+ if(strcmp(name, svc->name) == 0) {
+ *last = svc->link;
+ return 1;
+ }
+ last = &svc->link;
+ }
+
+ return 0;
+}
+
+void
+insertsvc(Service *svc)
+{
+ int h;
+
+ svc->removed = 0;
+ h = hash(svc->name);
+ svc->link = svcs[h];
+ svcs[h] = svc;
+}
+
+ulong
+hash(char *s)
+{
+ ulong h;
+
+ h = 0;
+ while(*s)
+ h = (h << 1) ^ *s++;
+ return h % Nsvcs;
+}
+
+Fid *
+findfid(int)
+{
+ 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->svc = nil;
+ f->next = fids;
+ fids = f;
+ return f;
+}
+
+void io(int in, int out)
+{
+ char *err;
+ int n;
+
+ while((n = read9pmsg(in, mdata, messagesize)) != 0){
+ if(n < 0)
+ error("mount read: %r");
+ if(convM2S(mdata, n, &rhdr) != n)
+ error("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)
+ error("mount write");
+ }
+}
+
+void *
+emalloc(ulong n)
+{
+ void *p;
+
+ if((p = malloc(n)) != nil){
+ memset(p, 0, n);
+ return p;
+ }
+ error("out of memory!");
+ return nil;
+}
+
+char *
+estrdup(char *s)
+{
+ char *d;
+ int n;
+
+ n = strlen(s)+1;
+ d = emalloc(n);
+ memmove(d, s, n);
+ return d;
+}
--- /dev/null
+++ b/dns.h
@@ -1,0 +1,568 @@
+#define NS2MS(ns) ((ns) / 1000000L)
+#define S2MS(s) ((s) * 1000LL)
+
+#define timems() NS2MS(nsec())
+
+typedef struct Ndbtuple Ndbtuple;
+
+enum
+{
+ /* RR types; see: http://www.iana.org/assignments/dns-parameters */
+ Ta= 1,
+ Tns= 2,
+ Tmd= 3,
+ Tmf= 4,
+ Tcname= 5,
+ Tsoa= 6,
+ Tmb= 7,
+ Tmg= 8,
+ Tmr= 9,
+ Tnull= 10,
+ Twks= 11,
+ Tptr= 12,
+ Thinfo= 13,
+ Tminfo= 14,
+ Tmx= 15,
+ Ttxt= 16,
+ Trp= 17,
+ Tafsdb= 18,
+ Tx25= 19,
+ Tisdn= 20,
+ Trt= 21,
+ Tnsap= 22,
+ Tnsapptr= 23,
+ Tsig= 24,
+ Tkey= 25,
+ Tpx= 26,
+ Tgpos= 27,
+ Taaaa= 28,
+ Tloc= 29,
+ Tnxt= 30,
+ Teid= 31,
+ Tnimloc= 32,
+ Tsrv= 33,
+ Tatma= 34,
+ Tnaptr= 35,
+ Tkx= 36,
+ Tcert= 37,
+ Ta6= 38,
+ Tdname= 39,
+ Tsink= 40,
+ Topt= 41,
+ Tapl= 42,
+ Tds= 43,
+ Tsshfp= 44,
+ Tipseckey= 45,
+ Trrsig= 46,
+ Tnsec= 47,
+ Tdnskey= 48,
+
+ Tspf= 99,
+ Tuinfo= 100,
+ Tuid= 101,
+ Tgid= 102,
+ Tunspec= 103,
+
+ /* query types (all RR types are also queries) */
+ Ttkey= 249, /* transaction key */
+ Ttsig= 250, /* transaction signature */
+ Tixfr= 251, /* incremental zone transfer */
+ Taxfr= 252, /* zone transfer */
+ Tmailb= 253, /* { Tmb, Tmg, Tmr } */
+ Tmaila= 254, /* obsolete */
+ Tall= 255, /* all records */
+
+ /* classes */
+ Csym= 0, /* internal symbols */
+ Cin= 1, /* internet */
+ Ccs, /* CSNET (obsolete) */
+ Cch, /* Chaos net */
+ Chs, /* Hesiod (?) */
+
+ /* class queries (all class types are also queries) */
+ Call= 255, /* all classes */
+
+ /* opcodes */
+ Oquery= 0<<11, /* normal query */
+ Oinverse= 1<<11, /* inverse query (retired) */
+ Ostatus= 2<<11, /* status request */
+ Onotify= 4<<11, /* notify slaves of updates */
+ Oupdate= 5<<11,
+ Omask= 0xf<<11, /* mask for opcode */
+
+ /* response codes */
+ Rok= 0,
+ Rformat= 1, /* format error */
+ Rserver= 2, /* server failure (e.g. no answer from something) */
+ Rname= 3, /* bad name */
+ Runimplimented= 4, /* unimplemented */
+ Rrefused= 5, /* we don't like you */
+ Ryxdomain= 6, /* name exists when it should not */
+ Ryxrrset= 7, /* rr set exists when it should not */
+ Rnxrrset= 8, /* rr set that should exist does not */
+ Rnotauth= 9, /* not authoritative */
+ Rnotzone= 10, /* name not in zone */
+ Rbadvers= 16, /* bad opt version */
+/* Rbadsig= 16, */ /* also tsig signature failure */
+ Rbadkey= 17, /* key not recognized */
+ Rbadtime= 18, /* signature out of time window */
+ Rbadmode= 19, /* bad tkey mode */
+ Rbadname= 20, /* duplicate key name */
+ Rbadalg= 21, /* algorithm not supported */
+ Rmask= 0x1f, /* mask for response */
+ Rtimeout= 1<<5, /* timeout sending (for internal use only) */
+
+ /* bits in flag word (other than opcode and response) */
+ Fresp= 1<<15, /* message is a response */
+ Fauth= 1<<10, /* true if an authoritative response */
+ Ftrunc= 1<<9, /* truncated message */
+ Frecurse= 1<<8, /* request recursion */
+ Fcanrec= 1<<7, /* server can recurse */
+
+ Domlen= 256, /* max domain name length (with NULL) */
+ Labellen= 64, /* max domain label length (with NULL) */
+ Strlen= 256, /* max string length (with NULL) */
+
+ /* time to live values (in seconds) */
+ Min= 60,
+ Hour= 60*Min, /* */
+ Day= 24*Hour, /* Ta, Tmx */
+ Week= 7*Day, /* Tsoa, Tns */
+ Year= 52*Week,
+ DEFTTL= Day,
+
+ /* reserved time (can't be timed out earlier) */
+ Reserved= 5*Min,
+
+ /* packet sizes */
+ Maxudp= 512, /* maximum bytes per udp message sent */
+ Maxudpin= 2048, /* maximum bytes per udp message rcv'd */
+
+ /* length of domain name hash table */
+ HTLEN= 4*1024,
+
+ Maxpath= 128, /* size of mntpt */
+ Maxlcks= 10, /* max. query-type locks per domain name */
+ Maxauth= 16, /* proto for auth */
+ Maxremote= 256, /* For registry, if you're more popular than this... */
+ Maxmdns= 255, /* Limits for mDNS messages */
+ Maxdial= 271, /* port + trns + !! + host */
+
+ RRmagic= 0xdeadbabe,
+ DNmagic= 0xa110a110,
+
+ /* parallelism: tune; was 32; allow lots */
+ Maxactive= 250,
+
+ /* tune; was 60*1000; keep it short */
+ Maxreqtm= 8*1000, /* max. ms to process a request */
+
+ Notauthoritative = 0,
+ Authoritative,
+};
+
+typedef struct Area Area;
+typedef struct Block Block;
+typedef struct Cert Cert;
+typedef struct DN DN;
+typedef struct DNSmsg DNSmsg;
+typedef struct Key Key;
+typedef struct Null Null;
+typedef struct RR RR;
+typedef struct Request Request;
+typedef struct SOA SOA;
+typedef struct Server Server;
+typedef struct Svc Svc;
+typedef struct Sig Sig;
+typedef struct Srv Srv;
+typedef struct Txt Txt;
+
+/*
+ * a structure to track a request and any slave process handling it
+ */
+struct Request
+{
+ int isslave; /* pid of slave */
+ uvlong aborttime; /* time in ms at which we give up */
+ jmp_buf mret; /* where master jumps to after starting a slave */
+ int id;
+ char *from; /* who asked us? */
+ void *aux;
+};
+
+/*
+ * a domain name
+ */
+struct DN
+{
+ DN *next; /* hash collision list */
+ ulong magic;
+ char *name; /* owner */
+ RR *rr; /* resource records off this name */
+ ulong referenced; /* time last referenced */
+ ulong ordinal;
+ ushort class; /* RR class */
+ uchar respcode; /* response code */
+ uchar mark; /* for mark and sweep */
+};
+
+/*
+ * security info
+ */
+struct Block
+{
+ int dlen;
+ uchar *data;
+};
+struct Key
+{
+ int flags;
+ int proto;
+ int alg;
+ Block;
+};
+struct Cert
+{
+ int type;
+ int tag;
+ int alg;
+ Block;
+};
+struct Sig
+{
+ Cert;
+ int labels;
+ ulong ttl;
+ ulong exp;
+ ulong incep;
+ DN *signer;
+};
+struct Null
+{
+ Block;
+};
+
+/*
+ * text strings
+ */
+struct Txt
+{
+ Txt *next;
+ char *p;
+};
+
+/*
+ * registry service
+ */
+struct Svc
+{
+ int id; /* for server tracking */
+ Svc *next;
+ char labl[Maxmdns]; /* Use mDNS limits */
+ char host[Maxmdns]; /* host ip */
+ char mtpt[Maxpath];
+ char auth[Maxauth];
+ char trns[16]; /* transport tcp/ssh/gopher/carrierpidgeon */
+ char port[8]; /* 9fs, numerical, etc - switch to #? */
+ uchar perm; /* flag: from db */
+};
+
+/*
+ * an unpacked resource record
+ */
+struct RR
+{
+ RR *next;
+ ulong magic;
+ DN *owner; /* domain that owns this resource record */
+ uintptr pc; /* for tracking memory allocation */
+ ulong ttl; /* time to live to be passed on */
+ ulong expire; /* time this entry expires locally */
+ ulong marker; /* used locally when scanning rrlists */
+ ushort type; /* RR type */
+ ushort query; /* query type is in response to */
+ uchar auth; /* flag: authoritative */
+ uchar db; /* flag: from database */
+ uchar cached; /* flag: rr in cache */
+ uchar negative; /* flag: this is a cached negative response */
+
+ union { /* discriminated by negative & type */
+ DN *negsoaowner; /* soa for cached negative response */
+ DN *host; /* hostname - soa, cname, mb, md, mf, mx, ns, srv */
+ DN *cpu; /* cpu type - hinfo */
+ DN *mb; /* mailbox - mg, minfo */
+ DN *ip; /* ip address - a, aaaa */
+ DN *rp; /* rp arg - rp */
+ uintptr arg0; /* arg[01] are compared to find dups in dn.c */
+ };
+ union { /* discriminated by negative & type */
+ int negrcode; /* response code for cached negative resp. */
+ DN *rmb; /* responsible maibox - minfo, soa, rp */
+ DN *ptr; /* pointer to domain name - ptr */
+ DN *os; /* operating system - hinfo */
+ ulong pref; /* preference value - mx */
+ ulong local; /* ns served from local database - ns */
+ ushort port; /* - srv */
+ uintptr arg1; /* arg[01] are compared to find dups in dn.c */
+ };
+ union { /* discriminated by type */
+ SOA *soa; /* soa timers - soa */
+ Srv *srv;
+ Key *key;
+ Cert *cert;
+ Sig *sig;
+ Null *null;
+ Txt *txt;
+ };
+};
+
+/*
+ * list of servers
+ */
+struct Server
+{
+ Server *next;
+ char *name;
+};
+
+/*
+ * timers for a start-of-authority record. all ulongs are in seconds.
+ */
+struct SOA
+{
+ ulong serial; /* zone serial # */
+ ulong refresh; /* zone refresh interval */
+ ulong retry; /* zone retry interval */
+ ulong expire; /* time to expiration */
+ ulong minttl; /* min. time to live for any entry */
+
+ Server *slaves; /* slave servers */
+};
+
+/*
+ * srv (service location) record (rfc2782):
+ * _service._proto.name ttl class(IN) 'SRV' priority weight port target
+ */
+struct Srv
+{
+ ushort pri;
+ ushort weight;
+};
+
+typedef struct Rrlist Rrlist;
+struct Rrlist
+{
+ int count;
+ RR *rrs;
+};
+
+/*
+ * domain messages
+ */
+struct DNSmsg
+{
+ ushort id;
+ int flags;
+ int qdcount; /* questions */
+ RR *qd;
+ int ancount; /* answers */
+ RR *an;
+ int nscount; /* name servers */
+ RR *ns;
+ int arcount; /* hints */
+ RR *ar;
+};
+
+/*
+ * definition of local area for dblookup
+ */
+struct Area
+{
+ Area *next;
+
+ int len; /* strlen(area->soarr->owner->name) */
+ RR *soarr; /* soa defining this area */
+ int neednotify;
+ int needrefresh;
+};
+
+typedef struct Cfg Cfg;
+struct Cfg {
+ int cachedb;
+ int resolver;
+ int justforw; /* flag: pure resolver, just forward queries */
+ int serve; /* flag: serve udp queries */
+ int inside;
+ int straddle;
+};
+
+/* (udp) query stats */
+typedef struct {
+ QLock;
+ ulong slavehiwat; /* procs */
+ ulong qrecvd9p; /* query counts */
+ ulong qrecvdudp;
+ ulong qsent;
+ ulong qrecvd9prpc; /* packet count */
+ ulong alarms;
+ /* reply times by count */
+ ulong under10ths[3*10+2]; /* under n*0.1 seconds, n is index */
+ ulong tmout;
+ ulong tmoutcname;
+ ulong tmoutv6;
+
+ ulong answinmem; /* answers in memory */
+ ulong negans; /* negative answers received */
+ ulong negserver; /* neg ans with Rserver set */
+ ulong negbaddeleg; /* neg ans with bad delegations */
+ ulong negbdnoans; /* ⋯ and no answers */
+ ulong negnorname; /* neg ans with no Rname set */
+ ulong negcached; /* neg ans cached */
+} Stats;
+
+Stats stats;
+
+enum
+{
+ Recurse,
+ Dontrecurse,
+ NOneg,
+ OKneg,
+};
+
+extern Cfg cfg;
+extern char *dbfile;
+extern int debug;
+extern Area *delegated;
+extern char *logfile;
+extern int maxage; /* age of oldest entry in cache (secs) */
+extern char mntpt[];
+extern int needrefresh;
+extern int norecursion;
+extern ulong now; /* time base */
+extern vlong nowns;
+extern Area *owned;
+extern int sendnotifies;
+extern ulong target;
+extern char *trace;
+extern int traceactivity;
+extern char *zonerefreshprogram;
+extern Svc *registry;
+extern int rfd[Maxremote];
+
+#pragma varargck type "R" RR*
+#pragma varargck type "Q" RR*
+#pragma varargck type "G" Svc*
+#pragma varargck type "D" Svc*
+#pragma varargck type "N" Svc*
+
+/* dn.c */
+extern char *rrtname[];
+extern char *rname[];
+extern unsigned nrname;
+extern char *opname[];
+extern Lock dnlock;
+
+void abort(); /* char*, ... */;
+void addserver(Server**, char*);
+Server* copyserverlist(Server*);
+void db2cache(int);
+void dnage(DN*);
+void dnageall(int);
+void dnagedb(void);
+void dnagenever(DN *);
+void dnauthdb(void);
+void dndump(char*);
+void dninit(void);
+DN* dnlookup(char*, int, int);
+DN* idnlookup(char*, int, int);
+DN* ipalookup(uchar*, int, int);
+void dnptr(uchar*, uchar*, char*, int, int, int);
+void dnpurge(void);
+void dnslog(char*, ...);
+void dnstats(char *file);
+void* emalloc(int);
+char* estrdup(char*);
+void freeanswers(DNSmsg *mp);
+void freeserverlist(Server*);
+int getactivity(Request*, int);
+Area* inmyarea(char*);
+void putactivity(int);
+void reg2cache(void);
+void regconnect(void);
+char* rstr2cache(char*, int);
+Svc* rstr2svc(char*);
+char* rstrdtch(char*);
+char* rstrupdt(char*);
+RR* randomize(RR*);
+void reglog(char*, ...);
+RR* rralloc(int);
+void rrattach(RR*, int);
+int rravfmt(Fmt*);
+RR* rrcat(RR**, RR*);
+RR** rrcopy(RR*, RR**);
+int rrfmt(Fmt*);
+void rrfree(RR*);
+void rrfreelist(RR*);
+RR* rrlookup(DN*, int, int);
+char* rrname(int, char*, int);
+RR* rrremneg(RR**);
+RR* rrremtype(RR**, int);
+RR* rrremowner(RR**, DN*);
+RR* rrremfilter(RR**, int (*)(RR*, void*), void*);
+int rrsupported(int);
+int rrtype(char*);
+void slave(Request*);
+int subsume(char*, char*);
+int tsame(int, int);
+void unique(RR*);
+void warning(char*, ...);
+
+
+/* dnarea.c */
+void refresh_areas(Area*);
+void freearea(Area**);
+void addarea(DN *dp, RR *rp, Ndbtuple *t);
+
+/* dblookup.c */
+int baddelegation(RR*, RR*, uchar*);
+RR* dblookup(char*, int, int, int, int);
+RR* dnsservers(int);
+RR* domainlist(int);
+int insideaddr(char *dom);
+int insidens(uchar *ip);
+int myip(uchar *ip);
+int opendatabase(void);
+int outsidensip(int, uchar *ip);
+
+/* dns.c */
+char* walkup(char*);
+RR* getdnsservers(int);
+void logreply(int, uchar*, DNSmsg*);
+void logsend(int, int, uchar*, char*, char*, int);
+
+/* dnresolve.c */
+RR* dnresolve(char*, int, int, Request*, RR**, int, int, int, int*);
+int udpport(char *);
+int mkreq(DN *dp, int type, uchar *buf, int flags, ushort reqno);
+int seerootns(void);
+void initdnsmsg(DNSmsg *mp, RR *rp, int flags, ushort reqno);
+
+/* dnserver.c */
+void dnserver(DNSmsg*, DNSmsg*, Request*, uchar *, int);
+void dnudpserver(char*);
+
+/* dnnotify.c */
+void dnnotify(DNSmsg*, DNSmsg*, Request*);
+void notifyproc(void);
+
+/* reglookup.c */
+int openregistry(void);
+
+/* convDNS2M.c */
+int convDNS2M(DNSmsg*, uchar*, int);
+
+/* convM2DNS.c */
+char* convM2DNS(uchar*, int, DNSmsg*, int*);
+
+#pragma varargck argpos dnslog 1
--- /dev/null
+++ b/lib/reglookup.c
@@ -1,0 +1,213 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+static Ndb *db;
+static QLock dblock;
+
+int
+openregistry(void)
+{
+ if(db != nil)
+ return 0;
+
+ db = ndbopen(dbfile);
+ return db!=nil ? 0: -1;
+}
+
+static void
+attach(Svc* svc, int persist)
+{
+ svc->perm = !!persist;
+
+ if(registry != nil){
+ svc->next = registry;
+ }
+
+ svc->next = registry;
+ registry = svc;
+}
+
+static char*
+detach(char *dial)
+{
+ Svc *c, *last = 0;
+ char buf[Maxdial]; /* trns is capped at 16, port 8 */
+
+ for(c = registry; c; c = c->next){
+ snprint(buf, Maxdial, "%s!%s!%s", c->trns, c->host, c->port);
+ if(strcmp(buf, dial)==0){
+ if(last == 0)
+ registry = c->next;
+ else
+ last->next = c->next;
+ free(c);
+ return 0;
+ }
+ last = c;
+ }
+
+ return "found no matching service";
+}
+
+static void
+host2svc(Svc *svc, char *dial)
+{
+ int n;
+
+ /*
+ * entry host=tcp!mything!9fs
+ * for now, tokenize but we should allow short strings
+ */
+ n = strcspn(dial, "!");
+ if(n < 1)
+ strcpy(svc->trns, "tcp");
+ else
+ strecpy(svc->trns, svc->trns+n+1, dial);
+ dial = dial + n + 1;
+
+ n = strcspn(dial, "!");
+ strecpy(svc->host, svc->host+n+1, dial);
+
+ dial = dial + n + 1;
+ if(sizeof(dial) < 1)
+ strcpy(svc->port, "9fs");
+ else if(sizeof(dial) > 8)
+ /* If this starts happening, we should bump the number */
+ strecpy(svc->port, svc->port + 8, dial);
+ else
+ strcpy(svc->port, dial);
+}
+
+static void
+dbtuple2cache(Ndbtuple *t, int persist)
+{
+ Ndbtuple *et, *nt;
+ Svc *svc;
+
+
+ for(et = t; et; et = et->entry)
+ if(strncmp(et->attr, "serv", 4)==0){
+ svc = emalloc(sizeof(*svc));
+ host2svc(svc, et->val);
+ for(nt = et->entry; nt; nt = nt->entry)
+ if(strcmp(nt->attr, "label")==0)
+ strecpy(svc->labl, svc->labl+Maxmdns, nt->val);
+ else if(strcmp(nt->attr, "auth")==0)
+ strecpy(svc->auth, svc->auth+Maxauth, nt->val);
+ else if(strcmp(nt->attr, "mtpt")==0)
+ strecpy(svc->mtpt, svc->mtpt+Maxpath, nt->val);
+ attach(svc, persist);
+ };
+}
+
+static void
+dbfile2cache(Ndb *db)
+{
+ Ndbtuple *t;
+
+ if(debug)
+ reglog("reading %s", db->file);
+ Bseek(&db->b, 0, 0);
+ while(t = ndbparse(db)){
+ dbtuple2cache(t, 1);
+ ndbfree(t);
+ }
+
+
+}
+
+Svc*
+rstr2svc(char *entry)
+{
+ Svc *svc;
+ char *args[7];
+
+ int i, n;
+
+ n = tokenize(entry, args, 7);
+
+ svc = emalloc(sizeof(*svc));
+ host2svc(svc, estrdup(args[0]));
+
+ for(i = 1; i < n - 1; i++)
+ if(strcmp(args[i], "label")==0)
+ strecpy(svc->labl, svc->labl+Maxmdns, args[++i]);
+ else if(strcmp(args[i], "auth")==0)
+ strecpy(svc->auth, svc->auth+Maxauth, args[++i]);
+ else if(strcmp(args[i], "mtpt")==0)
+ strecpy(svc->mtpt, svc->mtpt+Maxpath, args[++i]);
+
+ return svc;
+}
+
+char*
+rstr2cache(char *entry, int persist)
+{
+ Svc *svc;
+
+ svc = rstr2svc(entry);
+ attach(svc, persist);
+ return 0;
+}
+
+char*
+rstrdtch(char *svc)
+{
+ return detach(svc);
+}
+
+/* e.g. update tcp!foo!9fs label newlabel */
+char*
+rstrupdt(char *entry)
+{
+ Svc *c, *svc = 0;
+ char *args[7], buf[Maxdial];
+ int i, n;
+
+ n = tokenize(entry, args, 7);
+
+ /* Find our service */
+ for(c = registry; c; c = c->next){
+ snprint(buf, Maxdial, "%s!%s!%s", c->trns, c->host, c->port);
+ if(strcmp(buf, args[0])==0){
+ svc = c;
+ break;
+ }
+ }
+
+ if(svc == 0)
+ return "found no matching service";
+
+ for(i = 1; i < n - 1; i++)
+ if(strcmp(args[i], "label")==0)
+ strecpy(svc->labl, svc->labl+Maxmdns, args[++i]);
+ else if(strcmp(args[i], "auth")==0)
+ strecpy(svc->auth, svc->auth+Maxauth, args[++i]);
+ else if(strcmp(args[i], "mtpt")==0)
+ strecpy(svc->mtpt, svc->mtpt+Maxpath, args[++i]);
+
+ return 0;
+}
+
+void
+reg2cache(void)
+{
+ Ndb *ndb;
+
+ qlock(&dblock);
+ if(openregistry() < 0){
+ qunlock(&dblock);
+ return;
+ }
+
+ if(debug)
+ syslog(0, logfile, "building cache from db");
+
+ for(ndb = db; ndb; ndb = ndb->next)
+ dbfile2cache(ndb);
+
+ qunlock(&dblock);
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,16 @@
+# registry mkfile
+</$objtype/mkfile
+
+TARG = \
+ registry\
+ regquery\
+
+HFILES = dns.h /$objtype/lib/libndb.a
+
+BIN=/$objtype/bin/ndb
+
+</sys/src/cmd/mkmany
+
+$O.registry: registry.$O reglookup.$O
+ $LD -o $target $prereq
+
--- /dev/null
+++ b/ndb/registry.c
@@ -1,0 +1,967 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <bio.h>
+#include <ip.h>
+#include "dns.h"
+
+enum
+{
+ Maxrequest= 1024,
+ Maxreply= 8192,
+ Maxrecords= 192,
+ Maxfdata= 8192,
+
+ Qdir= 0,
+ Qreg= 1,
+};
+
+typedef struct Mfile Mfile;
+typedef struct Job Job;
+typedef struct Records Records;
+
+struct Mfile
+{
+ Mfile *next;
+
+ char *user;
+ Qid qid;
+ int fid;
+ int bare;
+
+ char reply[Maxreply];
+ ushort rr[Maxrecords]; /* offset of record */
+ ushort nrr; /* number of records */
+};
+
+/*
+ * active requests
+ */
+struct Job
+{
+ Job *next;
+ int flushed;
+ Fcall request;
+ Fcall reply;
+};
+Lock joblock;
+Job *joblist;
+
+struct {
+ Lock;
+ Mfile *inuse; /* active mfile's */
+} mfalloc;
+
+Svc *registry;
+int vers;
+int debug;
+char *dbfile = "/lib/ndb/registry";
+char *reguser;
+char mtpt[Maxpath];
+int rfd[Maxremote];
+int mfd[2];
+char *logfile = "registry";
+
+void rversion(Job*);
+void rflush(Job*);
+void rattach(Job*, Mfile*);
+char* rwalk(Job*, Mfile*);
+void ropen(Job*, Mfile*);
+void rcreate(Job*, Mfile*);
+void rread(Job*, Mfile*);
+void rwrite(Job*, Mfile*);
+void rclunk(Job*, Mfile*);
+void rremove(Job*, Mfile*);
+void rstat(Job*, Mfile*);
+void rwstat(Job*, Mfile*);
+void rauth(Job*);
+void mountinit(char*, char*);
+void setext(char*, int, char*);
+void io(void);
+
+static char* resolve(char*, ...);
+static char* addsvc(char*);
+static char* rmsvc(char*);
+static char* updatesvc(char*);
+static void refresh(void);
+static void regdump(char*);
+static void sendmsg(Job*, char*);
+
+static int scanfmt(Fmt*);
+static int srvfmt(Fmt*);
+static int dumpfmt(Fmt*);
+
+static char* query(Job*, Mfile*, char*, int);
+static char* resolvequery(Job*, Mfile*, char*, int);
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-xrd] [-f ndb-file]\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char* argv[])
+{
+ char servefile[Maxpath], ext[Maxpath];
+ Dir *dir;
+ ext[0] = 0;
+
+ setnetmtpt(mtpt, sizeof mtpt, nil);
+
+ ARGBEGIN{
+ case 'd':
+ debug = 1;
+ break;
+ case 'f':
+ dbfile = EARGF(usage());
+ break;
+ case 'x':
+ setnetmtpt(mtpt, sizeof mtpt, EARGF(usage()));
+ setext(ext, sizeof ext, mtpt);
+ break;
+ } ARGEND;
+ if(argc != 0)
+ usage();
+
+
+
+ rfork(RFREND|RFNOTEG);
+
+ fmtinstall('F', fcallfmt);
+ fmtinstall('G', srvfmt);
+ fmtinstall('N', scanfmt);
+ fmtinstall('D', dumpfmt);
+
+ reglog("starting registry on %s", mtpt);
+
+ if(openregistry())
+ sysfatal("unable to open db file");
+
+ reguser = estrdup(getuser());
+ seprint(servefile, servefile+Maxpath, "#s/registry%s", ext);
+
+ dir = dirstat(servefile);
+ if (dir)
+ sysfatal("%s exists; another registry instance is running", servefile);
+ free(dir);
+
+ mountinit(servefile, mtpt);
+ reg2cache();
+ io();
+
+ _exits(0);
+}
+
+void
+setext(char *ext, int n, char *p)
+{
+ int i, c;
+
+ n--;
+ for(i = 0; i < n; i++){
+ c = p[i];
+ if(c == 0)
+ break;
+ if(c == '/')
+ c = '_';
+ ext[i] = c;
+ }
+ ext[i] = 0;
+}
+
+void
+mountinit(char *service, char *mtpt)
+{
+ int f;
+ int p[2];
+ char buf[32];
+
+ if(pipe(p) < 0)
+ sysfatal("pipe failed: %r");
+
+ /*
+ * make a /srv/registry
+ */
+ if((f = create(service, OWRITE|ORCLOSE, 0666)) < 0)
+ sysfatal("create %s failed: %r", service);
+ snprint(buf, sizeof buf, "%d", p[1]);
+ if(write(f, buf, strlen(buf)) != strlen(buf))
+ sysfatal("write %s failed: %r", service);
+
+ /* copy namespace to avoid a deadlock */
+ switch(rfork(RFFDG|RFPROC|RFNAMEG)){
+ case 0: /* child: start main proc */
+ close(p[1]);
+ procsetname("%s", mtpt);
+ break;
+ case -1:
+ sysfatal("fork failed: %r");
+ default: /* parent: make /srv/registry, mount it, exit */
+ close(p[0]);
+
+ /*
+ * put ourselves into the file system
+ */
+ if(mount(p[1], -1, mtpt, MAFTER, "") < 0)
+ fprint(2, "registry mount failed: %r\n");
+ _exits(0);
+ }
+ mfd[0] = mfd[1] = p[0];
+}
+
+Mfile*
+newfid(int fid, int needunused)
+{
+ Mfile *mf;
+
+ lock(&mfalloc);
+ for(mf = mfalloc.inuse; mf != nil; mf = mf->next)
+ if(mf->fid == fid){
+ unlock(&mfalloc);
+ if(needunused)
+ return nil;
+ return mf;
+ }
+ mf = emalloc(sizeof(*mf));
+ mf->fid = fid;
+ mf->qid.vers = vers;
+ mf->qid.type = QTDIR;
+ mf->qid.path = 0LL;
+ mf->user = estrdup(reguser);
+ mf->next = mfalloc.inuse;
+ mfalloc.inuse = mf;
+ mf->bare = 1;
+ unlock(&mfalloc);
+ return mf;
+}
+
+void
+freefid(Mfile *mf)
+{
+ Mfile **l;
+
+ lock(&mfalloc);
+ for(l = &mfalloc.inuse; *l != nil; l = &(*l)->next)
+ if(*l == mf){
+ *l = mf->next;
+ free(mf->user);
+ memset(mf, 0, sizeof *mf); /* cause trouble */
+ free(mf);
+ unlock(&mfalloc);
+ return;
+ }
+ unlock(&mfalloc);
+ sysfatal("freeing unused fid");
+}
+
+Mfile*
+copyfid(Mfile *mf, int fid)
+{
+ Mfile *nmf;
+
+ nmf = newfid(fid, 1);
+ if(nmf == nil)
+ return nil;
+ nmf->fid = fid;
+ free(nmf->user);
+ nmf->user = estrdup(mf->user);
+ nmf->qid.type = mf->qid.type;
+ nmf->qid.path = mf->qid.path;
+ nmf->qid.vers = vers++;
+ return nmf;
+}
+
+Job*
+newjob(void)
+{
+ Job *job;
+
+ job = emalloc(sizeof *job);
+ lock(&joblock);
+ job->next = joblist;
+ joblist = job;
+ job->request.tag = -1;
+ unlock(&joblock);
+ return job;
+}
+
+void
+freejob(Job *job)
+{
+ Job **l;
+
+ lock(&joblock);
+ for(l = &joblist; *l; l = &(*l)->next)
+ if(*l == job){
+ *l = job->next;
+ memset(job, 0, sizeof *job); /* cause trouble */
+ free(job);
+ break;
+ }
+ unlock(&joblock);
+}
+
+void
+flushjob(int tag)
+{
+ Job *job;
+
+ lock(&joblock);
+ for(job = joblist; job; job = job->next)
+ if(job->request.tag == tag && job->request.type != Tflush){
+ job->flushed = 1;
+ break;
+ }
+ unlock(&joblock);
+}
+
+void
+io(void)
+{
+ long n;
+ Mfile *mf;
+ uchar mdata[IOHDRSZ + Maxfdata];
+ Job *job;
+
+ while((n = read9pmsg(mfd[0], mdata, sizeof mdata)) != 0){
+ if(n < 0){
+ syslog(1, logfile, "error reading 9P from %s: %r", mtpt);
+ break;
+ }
+
+ job = newjob();
+ if(convM2S(mdata, n, &job->request) != n){
+ reglog("format error %ux %ux %ux %ux %ux",
+ mdata[0], mdata[1], mdata[2], mdata[3], mdata[4]);
+ freejob(job);
+ break;
+ }
+ mf = newfid(job->request.fid, 0);
+ if(debug)
+ reglog("%F", &job->request);
+
+ switch(job->request.type){
+ default:
+ warning("unknown request type %d", job->request.type);
+ break;
+ case Tversion:
+ rversion(job);
+ break;
+ case Tauth:
+ rauth(job);
+ break;
+ case Tflush:
+ rflush(job);
+ break;
+ case Tattach:
+ rattach(job, mf);
+ break;
+ case Twalk:
+ rwalk(job, mf);
+ break;
+ case Topen:
+ ropen(job, mf);
+ break;
+ case Tcreate:
+ rcreate(job, mf);
+ break;
+ case Tread:
+ rread(job, mf);
+ break;
+ case Twrite:
+ rwrite(job, mf);
+ break;
+ case Tclunk:
+ rclunk(job, mf);
+ break;
+ case Tremove:
+ rremove(job, mf);
+ break;
+ case Tstat:
+ rstat(job, mf);
+ break;
+ case Twstat:
+ rwstat(job, mf);
+ break;
+ }
+
+ freejob(job);
+ }
+}
+
+void
+rversion(Job *job)
+{
+ if(job->request.msize > IOHDRSZ + Maxfdata)
+ job->reply.msize = IOHDRSZ + Maxfdata;
+ else
+ job->reply.msize = job->request.msize;
+ job->reply.version = "9P2000";
+ if(strncmp(job->request.version, "9P", 2) != 0)
+ job->reply.version = "unknown";
+ sendmsg(job, nil);
+}
+
+void
+rauth(Job *job)
+{
+ sendmsg(job, "registry: authentication not required");
+}
+
+void
+rflush(Job *job)
+{
+ flushjob(job->request.oldtag);
+ sendmsg(job, 0);
+}
+
+void
+rattach(Job *job, Mfile *mf)
+{
+ if(mf->user != nil)
+ free(mf->user);
+ mf->user = estrdup(job->request.uname);
+ mf->qid.vers = vers++;
+ mf->qid.type = QTDIR;
+ mf->qid.path = 0LL;
+ job->reply.qid = mf->qid;
+ sendmsg(job, 0);
+}
+
+char*
+rwalk(Job *job, Mfile *mf)
+{
+ int i, nelems;
+ char *err;
+ char **elems;
+ Mfile *nmf;
+ Qid qid;
+
+ err = 0;
+ nmf = nil;
+ elems = job->request.wname;
+ nelems = job->request.nwname;
+ job->reply.nwqid = 0;
+
+ if(job->request.newfid != job->request.fid){
+ /* clone fid */
+ nmf = copyfid(mf, job->request.newfid);
+ if(nmf == nil){
+ err = "clone bad newfid";
+ goto send;
+ }
+ mf = nmf;
+ }
+ /* else nmf will be nil */
+
+ qid = mf->qid;
+ if(nelems > 0){
+ /* walk fid */
+ for(i=0; i<nelems && i<MAXWELEM; i++){
+ if((qid.type & QTDIR) == 0){
+ err = "not a directory";
+ break;
+ }
+ if(strcmp(elems[i], "..") == 0 || strcmp(elems[i], ".") == 0){
+ qid.type = QTDIR;
+ qid.path = Qdir;
+ Found:
+ job->reply.wqid[i] = qid;
+ job->reply.nwqid++;
+ continue;
+ }
+ if(strcmp(elems[i], "registry") == 0){
+ qid.type = QTFILE;
+ qid.path = Qreg;
+ goto Found;
+ }
+ err = "file does not exist";
+ break;
+ }
+ }
+
+ send:
+ if(nmf != nil && (err!=nil || job->reply.nwqid<nelems))
+ freefid(nmf);
+ if(err == nil)
+ mf->qid = qid;
+ sendmsg(job, err);
+ return err;
+}
+
+void
+ropen(Job *job, Mfile *mf)
+{
+ int mode;
+ char *err;
+
+ err = 0;
+ mode = job->request.mode;
+ if(mf->qid.type & QTDIR)
+ if(mode)
+ err = "permission denied";
+ job->reply.qid = mf->qid;
+ job->reply.iounit = 0;
+ sendmsg(job, err);
+}
+
+void
+rcreate(Job *job, Mfile *mf)
+{
+ USED(mf);
+ sendmsg(job, "creation permission denied");
+}
+
+void rclunk(Job *job, Mfile *mf)
+{
+ freefid(mf);
+ sendmsg(job, 0);
+}
+
+void
+rremove(Job *job, Mfile *mf)
+{
+ USED(mf);
+ sendmsg(job, "remove permission denied");
+}
+
+void
+rread(Job *job, Mfile *mf)
+{
+ int i, n;
+ long clock;
+ ulong cnt;
+ vlong off;
+ char *err;
+ uchar buf[Maxfdata];
+ Dir dir;
+
+ n = 0;
+ err = nil;
+ off = job->request.offset;
+ cnt = job->request.count;
+ *buf = '\0';
+ job->reply.data = (char*)buf;
+ if(mf->qid.type & QTDIR){
+ clock = time(nil);
+ if(off == 0){
+ memset(&dir, 0, sizeof dir);
+ dir.name = "registry";
+ dir.qid.type = QTFILE;
+ dir.qid.vers = vers;
+ dir.qid.path = Qreg;
+ dir.mode = 0666;
+ dir.length = 0;
+ dir.uid = dir.gid = dir.muid = mf->user;
+ dir.atime = dir.mtime = clock;
+ n = convD2M(&dir, buf, sizeof buf);
+ }
+ } else if (off < 0)
+ err = "negative read offset";
+ else {
+ if(mf->bare)
+ query(job, mf, "all", 0);
+ for(i = 1; i < mf->nrr; i++)
+ if(mf->rr[i] > off)
+ break;
+ if(i <= mf->nrr){
+ if(off + cnt > mf->rr[i])
+ n = mf->rr[i] - off;
+ else
+ n = cnt;
+ assert(n >= 0);
+ job->reply.data = mf->reply + off;
+ }
+ }
+ job->reply.count = n;
+ sendmsg(job, err);
+}
+
+void
+rwrite(Job *job, Mfile *mf)
+{
+ int send, pipe2rc;
+ ulong cnt;
+ char *err, *atype;
+ char errbuf[ERRMAX];
+
+ err = nil;
+ cnt = job->request.count;
+ send = 1;
+ if(mf->qid.type & QTDIR)
+ err = "can't write directory";
+ else if (job->request.offset != 0)
+ err = "writing at non-zero offset";
+ else if (cnt >= Maxrequest)
+ err = "request too long";
+ else
+ send = 0;
+ if(send)
+ goto send;
+
+ job->request.data[cnt] = 0;
+ if(cnt > 0 && job->request.data[cnt-1] == '\n')
+ job->request.data[cnt-1] = 0;
+
+ if(strcmp(mf->user, "none") == 0 || strcmp(mf->user, reguser) != 0)
+ goto query; /* We don't want remote clients to modify our local */
+
+ /*
+ * special commands
+ */
+ send = 1;
+ if(strcmp(job->request.data, "debug")==0)
+ debug ^= 1;
+ else if(strcmp(job->request.data, "dump")==0)
+ regdump("/lib/ndb/regdump");
+ else if (strcmp(job->request.data, "refresh")==0)
+ refresh();
+ else if (strncmp(job->request.data, "add ", 4)==0)
+ err = addsvc(job->request.data + 4);
+ else if (strncmp(job->request.data, "rm ", 3)==0)
+ err = rmsvc(job->request.data + 3);
+ else if (strncmp(job->request.data, "update ", 7)==0)
+ err = updatesvc(job->request.data + 7);
+ else
+ send = 0;
+ if (send)
+ goto send;
+
+query:
+ /*
+ * kill previous reply
+ */
+ mf->nrr = 0;
+ mf->rr[0] = 0;
+ pipe2rc = 0;
+
+ atype = strchr(job->request.data, ' ');
+ if(atype == 0){
+ snprint(errbuf, sizeof errbuf, "illegal request %s", job->request.data);
+ err = errbuf;
+ goto send;
+ } else
+ *atype++ = 0;
+
+ if(strcmp(atype, "svc") == 0)
+ pipe2rc++;
+ else if(strcmp(atype, "scan") != 0){
+ snprint(errbuf, sizeof errbuf, "unknown query %s", atype);
+ err = errbuf;
+ goto send;
+ }
+
+ err = query(job, mf,job->request.data, pipe2rc);
+send:
+ mf->bare = 0;
+ job->reply.count = cnt;
+ sendmsg(job, err);
+}
+
+void
+rstat(Job *job, Mfile *mf)
+{
+ Dir dir;
+ uchar buf[IOHDRSZ+Maxfdata];
+
+ memset(&dir, 0, sizeof dir);
+ if(mf->qid.type & QTDIR){
+ dir.name = ".";
+ dir.mode = DMDIR|0555;
+ }else{
+ dir.name = "registry";
+ dir.mode = 0666;
+ }
+ dir.qid = mf->qid;
+ dir.length = 0;
+ dir.uid = dir.gid = dir.muid = mf->user;
+ dir.atime = dir.mtime = time(nil);
+ job->reply.nstat = convD2M(&dir, buf, sizeof buf);
+ job->reply.stat = buf;
+ sendmsg(job, 0);
+}
+
+void
+rwstat(Job *job, Mfile *mf)
+{
+ USED(mf);
+ sendmsg(job, "wstat permission denied");
+}
+
+static char *
+resolvequery(Job *job, Mfile *mf, char *p, int pipe2rc)
+{
+ int match, i;
+ int n;
+ Svc *c;
+
+ char cmd[256];
+ char buf[8192+1];
+
+ lock(&joblock);
+ if(!job->flushed){
+ match = n = 0;
+ mf->nrr = 0;
+
+ snprint(cmd, sizeof(cmd), "%s %s", p, ((pipe2rc)?"svc":"scan"));
+
+ for(i = 0; i < Maxremote && !match && rfd[i] > 1; i++){
+ seek(rfd[i], 0, 0);
+ write(rfd[i], cmd, sizeof cmd);
+
+ seek(rfd[i], 0, 0);
+ while(read(rfd[i], buf, sizeof(buf)-1) > 0){
+ match = 1;
+ c = rstr2svc(buf);
+ mf->rr[mf->nrr++] = n;
+ if(pipe2rc)
+ n += snprint(mf->reply+n, Maxreply-n, "%G", c);
+ else
+ n += snprint(mf->reply+n, Maxreply-n, "%N", c);
+ free(c);
+ }
+ }
+ }
+ unlock(&joblock);
+
+ return 0;
+}
+
+static char *
+query(Job *job, Mfile *mf, char *p, int pipe2rc)
+{
+ int n;
+
+ Svc *c;
+ lock(&joblock);
+ if(!job->flushed){
+ n = 0;
+ mf->nrr = 0;
+ for(c = registry; c && n < Maxreply; c = c->next)
+ if((strncmp(p, c->labl, strlen(p))==0) || (strcmp(p, "all")==0)){
+ mf->rr[mf->nrr++] = n;
+ if(pipe2rc)
+ n += snprint(mf->reply+n, Maxreply-n, "%G", c);
+ else
+ n += snprint(mf->reply+n, Maxreply-n, "%N", c);
+ }
+ mf->rr[mf->nrr] = n;
+ }
+ unlock(&joblock);
+ return nil;
+}
+
+static void
+sendmsg(Job *job, char *err)
+{
+ int n;
+ uchar mdata[IOHDRSZ+Maxfdata];
+ char ename[ERRMAX];
+
+ if(err){
+ job->reply.type = Rerror;
+ snprint(ename, sizeof ename, "registry: %s", err);
+ job->reply.ename = ename;
+ }else
+ job->reply.type = job->request.type+1;
+ job->reply.tag = job->request.tag;
+ n = convS2M(&job->reply, mdata, sizeof mdata);
+ if(n == 0){
+ warning("sendmsg convS2M of %F returns 0", &job->reply);
+ abort();
+ }
+ lock(&joblock);
+ if(job->flushed == 0)
+ if(write(mfd[1], mdata, n)!=n)
+ sysfatal("mount write");
+ unlock(&joblock);
+ if(debug)
+ reglog("%F %d", &job->reply, n);
+}
+
+static void
+regdump(char *file)
+{
+ Svc *rp;
+ int fd;
+
+ fd = create(file, OWRITE, 0666);
+ if(fd < 0)
+ return;
+ lock(&mfalloc);
+ for(rp = registry; rp; rp = rp->next)
+ fprint(fd, "%D\n\n", rp);
+ unlock(&mfalloc);
+ close(fd);
+}
+
+static void
+refresh(void)
+{
+ Svc *c;
+ char dial[Maxdial];
+
+ for(c = registry; c; c = c->next){
+ /* Don't remove the ones we've added since startup */
+ if(!c->perm)
+ continue;
+ snprint(dial, Maxdial, "%s!%s!%s", c->trns, c->host, c->port);
+ rmsvc(dial);
+ /* Reset so we don't have messy loops */
+ c = registry;
+ }
+ reg2cache();
+}
+
+static char *
+resolve(char *cmd, ...)
+{
+ int n;
+ char fullcmd[256];
+ char buf[8192+1];
+ va_list arg;
+
+ va_start(arg, cmd);
+ vseprint(fullcmd, fullcmd+sizeof(fullcmd), cmd, arg);
+ va_end(arg);
+
+ /* We only operate on our local rfd */
+ seek(rfd[0], 0, 0);
+ write(rfd[0], fullcmd, sizeof fullcmd);
+
+ seek(rfd[0], 0, 0);
+ while((n = read(rfd[0], buf, sizeof(buf)-1)) > 0){
+ buf[n++] = '\n';
+ write(1, buf, n);
+ }
+ return buf;
+}
+
+static char *
+addsvc(char *args)
+{
+ if(debug)
+ reglog("Adding entry: %s", args);
+
+ return rstr2cache(args, 0);
+}
+
+static char *
+rmsvc(char *args)
+{
+ if(debug)
+ reglog("Removing entry: %s", args);
+ return rstrdtch(args);
+}
+
+static char *
+updatesvc(char *args)
+{
+ if(debug)
+ reglog("Updating entry: %s", args);
+ return rstrupdt(args);
+}
+
+void
+warning(char *fmt, ...)
+{
+ char regerr[256];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(regerr, regerr+sizeof(regerr), fmt, arg);
+ va_end(arg);
+ syslog(1, logfile, regerr);
+}
+
+void
+reglog(char *fmt, ...)
+{
+ char regerr[256];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(regerr, regerr+sizeof(regerr), fmt, arg);
+ va_end(arg);
+ syslog(0, logfile, regerr);
+}
+
+void*
+emalloc(int size)
+{
+ void *x;
+
+ x = malloc(size);
+ if(x == nil)
+ sysfatal("out of memory");
+ memset(x, 0, size);
+ return x;
+}
+
+char*
+estrdup(char *s)
+{
+ int size;
+ char *p;
+
+ size = strlen(s);
+ p = malloc(size+1);
+ if(p == nil)
+ sysfatal("out of memory");
+ memmove(p, s, size);
+ p[size] = 0;
+ return p;
+}
+
+static int
+srvfmt(Fmt *f)
+{
+ Svc *r;
+ char mf[Maxpath+1], auth[7];
+
+ r = va_arg(f->args, Svc*);
+ mf[0] = 0;
+ auth[0] = 0;
+
+ if(strcmp(r->mtpt, "")!= 0)
+ snprint(mf, sizeof(r->mtpt)+1, " %s", r->mtpt);
+
+ if(strcmp(r->auth, "none")==0)
+ snprint(auth, 4, "srv");
+ else
+ snprint(auth, 7, "srvtls");
+
+ return fmtprint(f, "%s!%s!%s\n",
+ r->trns, r->host, r->port);
+}
+
+static int
+scanfmt(Fmt *f)
+{
+ Svc *r;
+ char mf[Maxpath+6]; /* pad for our tuple attrs */
+
+ mf[0] = 0;
+ r = va_arg(f->args, Svc*);
+ if(strcmp(r->mtpt, "")!=0)
+ snprint(mf, sizeof(r->mtpt)+6, " mtpt=%s", r->mtpt);
+ return fmtprint(f, "service=%s!%s!%s label='%s' auth=%s%s\n",
+ r->trns, r->host, r->port, r->labl, r->auth, mf);
+}
+
+static int
+dumpfmt(Fmt *f)
+{
+ Svc *r;
+ char mf[Maxpath+7]; /* pad for our tuple attrs */
+
+ r = va_arg(f->args, Svc*);
+ if(r->mtpt != 0)
+ snprint(mf, sizeof(r->mtpt) + 7, "\n\tmtpt=%s", r->mtpt);
+ return fmtprint(f, "service=%s!%s!%s\n\tlabel=%s\n\tauth=%s%s",
+ r->trns, r->host, r->port, r->labl, r->auth, mf);
+}
+
--- /dev/null
+++ b/ndb/regquery.c
@@ -1,0 +1,67 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ndb.h>
+#include "dns.h"
+#include "ip.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: regquery [-s] [-f registry] query\n");
+ exits("usage");
+}
+
+static void
+queryregistry(int fd, char *line, int n)
+{
+ char buf[8192+1];
+
+ seek(fd, 0, 0);
+ write(fd, line, n);
+
+ seek(fd, 0, 0);
+ while((n = read(fd, buf, sizeof(buf)-1)) > 0)
+ write(1, buf, n);
+}
+
+static void
+query(int fd, char *q, int pipe2rc)
+{
+ char arg[260];
+
+ if(strlen(q) > 255)
+ sysfatal("query too long");
+
+ sprint(arg, "%s %s", q, (pipe2rc) ? "svc":"scan");
+ queryregistry(fd, arg, sizeof(arg));
+}
+
+void
+main(int argc, char *argv[])
+{
+ int fd, pipe2rc = 0;
+ char *rst = "/net/registry";
+
+ ARGBEGIN {
+ case 's':
+ pipe2rc++;
+ break;
+ case 'f':
+ rst = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if(argc != 1)
+ usage();
+
+ fd = open(rst, ORDWR);
+ if(fd < 0)
+ sysfatal("can't open %s: %r", rst);
+
+ query(fd, argv[0], pipe2rc);
+ exits(0);
+}