hlfw.ca

registry

Download patch

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);
+}