hlfw.ca

registry

Download patch

ref: 47a17ff536441934c0a98bd83ef081796f133bf8
parent: dcecd5ef3ee96776d833d1c457c6cd7ba5fb8a78
parent: 2bcc704bc097aec422a49e92c4d88f4b90825b60
author: halfwit <michaelmisch1985@gmail.com>
date: Tue Oct 17 15:51:32 PDT 2023

Merge pull request #3 from halfwit/rework

Rework

--- a/README.md
+++ b/README.md
@@ -1,14 +1,101 @@
-# registry
-Inferno's registry, done in a more plan9-like manner
+# Services Registry
 
+This is an interpretation of the Inferno Registry, for Plan9-like systems
+
+## Configuration
+
+Update your ipnet in /lib/ndb/local
 ```
-mk all && mk install
+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
 ```
 
-## Bugs/Gotchas
-The registry must be in the same namespace as cs to be able to translate addresses, be sure to set up your namespaces accordingly!
+This is used by `net/services` and `net/svcquery` by default.
 
+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
+```
+
+## aux/svcfs
+
+Usage: `aux/svcfs [-r] [-m mtpt] servicesfile`
+
+- `-r` starts the server in readonly mode
+
+`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 mtpt, by default using `/mnt/services`
+
+A service can be added by creating a directory. Services may be read by anyone, but can only be modified by the creator or registry owner. Write requests must come from users in the same authdom.
+
+Each service dir contains many of the following files: 
+ - addr
+ - auth
+ - status (ok/down)
+ - uptime
+ - description
+
+### Notes
+ - It may be beneficial to expose an events file that `services` can do a blocking read on, waiting for a service to be removed/added
+ - `auth` is an optional address for the auth server to use
+
+## svc/services 
+
+Usage: `svc/services [-o] [-f servicesdb] [-s svcfs]`
+
+- `-o` Alternate naming in services, `ipnet.sysname.svcname`
+- `-f` Read in services from db
+- `-s` Address of svcfs
+
+Services connects to a `svcfs`, by default checking for an entry in your local ipnet=. 
+Without `-f`, it checks for and parses `/cfg/$sysname/registry`. (`-f` and the default directroy are temporary stopgaps before services can be self-publishing)
+
+```
+## /cfg/mysystem/registry
+service=myservice
+    addr=tcp!myserver!19294
+    description='My shared service'
+```
+
+Services will populate your local /srv with an fd pointing to all records in the given `svcfs` as well as any local entries. 
+- If the status of a service changes from Ok, it will be automatically removed
+- multiple instances can be run, one per svcfs
+- on exit, all mounted services should be kept alive; so on start it should handle silently failing when an entry already exists
+
+## svc/query
+
+Usage: `svc/query [-s svcfs] query`
+- `-s` Address of svcfs. If none is given, it uses `registry=` from your ipnet
+
+Query the svcfs for any services matching query. It returns a tuple for each match
+
+```
+$ svc/query speakers
+service=speakers addr=livingroom!12345 description='Living room speakers' uptime=1239021 status=ok
+service=speakers addr=bedroom!1234 description='Bedroom speakers' uptime=123811 status=ok
+```
+
+## svc/publish 
+Usage: `svc/publsh [-s svcfs] [-a authdom] svcname addr [attr value]`
+
+Create a service entry on the given `svcfs`, by default using the `registry=` value in `/lib/ndb/local`. If a service already exists, it will attempt to update the svcfs with the attr/values given.
+
+- `attr` can be one of `description` or `auth`
+
+## svc/drop
+Usage: `svc/drop [-s svcfs] [-a authdom] svcname`
+
+This will remove the service entry from the `svcfs`. This must be ran as the user who created the service entry, or the hostowner of `svcfs`.
+
 ## Future
-There is work towards having ndb/registry act in resolver mode, but it hasn't been completely ironed out.
-The basic gist would be, you run a main registry for your entire network, and add an entry into your ipnet tuple for `registry=thatip`
-Then you start the rest of your ndb/registry sessions with -s, which uses the main to resolve any query; but also allows resolution from any other arbitrary registry server you add to your ndb.
+- Integration into `cpurc`
--- a/dns.h
+++ /dev/null
@@ -1,568 +1,0 @@
-#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/drop.c
@@ -1,0 +1,50 @@
+#include <u.h>
+#include <libc.h>
+#include "libservice/service.h"
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-s svcfs] [-d authdom] svcname\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	char *svcfs, *authdom;
+    	int fd;
+
+	svcfs = nil;
+	authdom = nil;
+	ARGBEGIN{
+	case 's':
+		svcfs = EARGF(usage());
+		break;
+    	case 'a':
+        	authdom = EARGF(usage());
+		break;
+	default:
+		usage();
+		break;
+	}ARGEND
+	argv0 = "svcfs";
+	if(argc != 1)
+		usage();
+    	if(strlen(argv[0]) > NAMELEN){
+		fprint(2, "Service name too large: %r\n");
+		exits("namelen");
+	}
+	fd = svcdial(svcfs, authdom);
+	if(mount(fd, -1, "/mnt/services/", MREPL, "") < 0)
+		goto Error;
+	if(remove(smprint("/mnt/services/%s", argv[0])) < 0)
+		goto Error;
+	close(fd);
+	unmount(0, "/mnt/services");
+	exits(0);
+Error:
+	fprint(2, "Error removing service: %r\n");
+	close(fd);
+	exits("error");
+}
--- /dev/null
+++ b/libservice/mkfile
@@ -1,0 +1,17 @@
+</$objtype/mkfile
+
+LIB=libservice.a$O
+
+OFILES=\
+	svcdial.$O\
+	svcquery.$O\
+	svctimefmt.$O\
+
+HFILES=\
+	service.h
+
+</sys/src/cmd/mklib
+
+nuke:V:
+	mk clean
+	rm -f libservice.a$O
--- /dev/null
+++ b/libservice/service.h
@@ -1,0 +1,32 @@
+#include <bio.h>
+#include <ndb.h>
+
+typedef struct Service Service;
+
+enum {
+    NAMELEN = 28,
+    NSVCS = 256,
+    MAXDESC = 256,
+    MAXADDR = 128,
+	RS = 0x1e,
+};
+
+enum {
+	Sok = 1,
+	Sdown = 2,
+	Sreg =3 ,
+	Smax =4,
+};
+
+struct Service {
+	char name[NAMELEN];
+	char description[MAXDESC];
+	char address[MAXADDR];
+	uchar status;
+	vlong uptime;
+	Service *next; /* Used for queries */
+};
+
+int svctimefmt(Fmt *f);
+int svcdial(char *netroot, char *authdom);
+Service* svcquery(int fd, char *query, char **argv, int argc);
--- /dev/null
+++ b/libservice/svcdial.c
@@ -1,0 +1,51 @@
+#include <u.h>
+#include <libc.h>
+#include "service.h"
+
+/* Connect to svcfs */
+int
+svcdial(char *netroot, char *dom)
+{
+    Ndbtuple *t, *nt;
+	char *p;
+	int rv;
+
+	if(dom == nil)
+		/* look for one relative to my machine */
+		return dial(netmkaddr("$registry", nil, "16675"), nil, nil, nil);
+
+	/* look up an auth server in an authentication domain */
+	p = csgetvalue(netroot, "authdom", dom, "registry", &t);
+
+	/* if that didn't work, just try the IP domain */
+	if(p == nil)
+		p = csgetvalue(netroot, "dom", dom, "registry", &t);
+
+	/*
+	 * if that didn't work, try p9registry.$dom.  this is very helpful if
+	 * you can't edit /lib/ndb.
+	 */
+	if(p == nil) {
+		p = smprint("p9registry.%s", dom);
+		if(p == nil)
+			return -1;
+		t = ndbnew("registry", p);
+	}
+	free(p);
+
+	/*
+	 * allow multiple registry= attributes for backup auth servers,
+	 * try each one in order.
+	 */
+	rv = -1;
+	for(nt = t; nt != nil; nt = nt->entry) {
+		if(strcmp(nt->attr, "registry") == 0) {
+			rv = dial(netmkaddr(nt->val, nil, "16675"), nil, nil, nil);
+			if(rv >= 0)
+				break;
+		}
+	}
+	ndbfree(t);
+
+	return rv;
+}
--- /dev/null
+++ b/libservice/svcquery.c
@@ -1,0 +1,97 @@
+#include <u.h>
+#include <libc.h>
+#include "service.h"
+
+char *
+readFile(char *dir, char *name, int len)
+{
+	int fd, n;
+	char buf[MAXDESC+1], path[NAMELEN+25];
+
+	sprint(path, "/mnt/services/%s/%s", dir, name);
+	if((fd = open(path, OREAD)) < 0)
+		return nil;
+	n = readn(fd, buf, len);
+	if(buf[n-1] == '\n' || buf[n-1] == RS)
+		buf[n-1] = '\0';
+	buf[n] = '\0';
+	close(fd);
+	return buf;
+}
+
+Service *
+addService(Dir d)
+{
+	Service *svc;
+	char *desc, *addr, *stat, *up;
+
+	svc = malloc(sizeof *svc);
+	memmove(svc->name, d.name, NAMELEN);
+	svc->name[strlen(d.name)] = '\0';
+	desc = readFile(d.name, "description", MAXDESC);
+	memmove(svc->description, desc, strlen(desc));
+	addr = readFile(d.name, "address", MAXADDR);
+	memmove(svc->address, addr, strlen(addr));
+	svc->status = Sreg;
+	stat = readFile(d.name, "status", 12);
+	if(strncmp(stat, "ok", 2) == 0)
+		svc->status = Sok;
+	if(strncmp(stat, "down", 4) == 0)
+		svc->status = Sdown;
+	up = readFile(d.name, "uptime", 64); /* Way huge */
+	svc->uptime = strtoll(up, nil, 10);
+
+	return svc;
+}
+
+int
+filter(Dir d, char *attr, char *value)
+{
+	char path[NAMELEN+25], buf[MAXDESC];
+	int fd, length;
+
+	length = strlen(value);
+	sprint(path, "/mnt/services/%s/%s", d.name, attr);
+	if((fd = open(path, OREAD)) < 0)
+		return -1;
+	if(readn(fd, buf, length) != length){
+		close(fd);
+		return -1;
+	}
+	if(strncmp(value, buf, length) != 0){
+		close(fd);
+		return -1;
+	}
+	return 0;
+}
+
+Service *
+svcquery(int fd, char *query, char **argv, int argc)
+{
+	Service *svc, *bsvc;
+	Dir *d;
+	int dfd, i, n;
+
+	bsvc = nil;
+
+	/* Build out a tuple based on our search values */
+	if(strlen(query) == 0)
+		return nil;
+	if(mount(fd, -1, "/mnt/services", MREPL, "") < 0)
+		return nil;
+	dfd = open("/mnt/services", OREAD);
+	while((n = dirread(dfd, &d)) > 0){
+		for(i=0; i < n; i++){
+			if(argc == 2 && filter(d[i], argv[0], argv[1]) < 0)
+				continue;
+			if(strncmp(query, d[i].name, strlen(query)) == 0 || strcmp(query, ".") == 0){
+				svc = addService(d[i]);
+				svc->next = bsvc;
+				bsvc = svc;
+			}
+		}
+		free(d);	
+	}
+	unmount(0, "/mnt/services");
+	return bsvc;
+}
--- /dev/null
+++ b/libservice/svctimefmt.c
@@ -1,0 +1,18 @@
+#include <u.h>
+#include <libc.h>
+#include "service.h"
+
+int
+svctimefmt(Fmt *f)
+{
+	vlong u, d, h, m, s;
+
+	/* Untested at the moment */
+	u = va_arg(f->args, vlong);
+	d = u / 86400;
+	h = u % 3600; // Give remaining hours
+	m = h % 60;
+	s = m % 60;
+	/* Print whole integer values for each */
+	return fmtprint(f, "\'%d days, %d hours, %d minutes, %d seconds\'", (int)d, (int)h, (int)m, (int)s);
+}
--- a/mkfile
+++ b/mkfile
@@ -1,16 +1,20 @@
 # registry mkfile
 </$objtype/mkfile
 
-TARG =	\
-	registry\
-	regquery\
+TARG=\
+	drop\
+	publish\
+	query\
+	svcfs
 
-HFILES = dns.h /$objtype/lib/libndb.a
+LIB=libservice/libservice.a$O
 
-BIN=/$objtype/bin/ndb
+HFILES=libservice/service.h
 
-</sys/src/cmd/mkmany
+BIN=/$objtype/bin/svc
 
-$O.registry: registry.$O reglookup.$O
-	$LD -o $target $prereq
+$LIB:
+	cd libservice
+	mk
 
+</sys/src/cmd/mkmany
--- /dev/null
+++ b/publish.c
@@ -1,0 +1,90 @@
+#include <u.h>
+#include <libc.h>
+#include "libservice/service.h"
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-s svcfs] [-d authdom] svcname addr [attr value]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	char *svcfs, *authdom, ap[NAMELEN];
+    	int i, fd, sfd;
+
+	svcfs = nil;
+	authdom = nil;
+	ARGBEGIN{
+	case 's':
+		svcfs = EARGF(usage());
+		break;
+    	case 'a':
+        	authdom = EARGF(usage());
+		break;
+	default:
+		usage();
+		break;
+	}ARGEND
+	argv0 = "svcfs";
+
+	if(argc < 2)
+		usage();
+
+	if(strlen(argv[0]) > NAMELEN){
+		fprint(2, "Service name too large\n");
+		exits("namelen");
+	}
+	if(strlen(argv[0]) > MAXADDR){
+		fprint(2, "Address too long\n");
+		exits("address");
+	}
+	if((sfd = svcdial(svcfs, authdom)) < 0){
+		fprint(2, "Error dialing svcfs: %r\n");
+		exits("error");
+	}
+	if(mount(sfd, -1, "/mnt/services", MREPL|MCREATE, "/") < 0){
+		fprint(2, "Error mounting svcfs: %r\n");
+        	exits("error");
+	}
+	/* If create fails, try to continue to update values */
+	sprint(ap, "/mnt/services/%s", argv[0]);
+	fd = create(ap, OWRITE, DMDIR|0777);
+	if(fd < 0)
+		goto Error;
+	sprint(ap, "/mnt/services/%s/address", argv[0]);
+	if((fd = open(ap, OWRITE)) < 0)
+		goto Error;
+	if(write(fd, argv[1], strlen(argv[1])) < 0)
+		goto Error;
+	/* Description, authdom */
+	if(argc == 2){
+		unmount("", "/mnt/services");
+		exits(0);
+	}
+	/* Janky */
+	for(i = 2; i < argc; i++){
+		if(strcmp("description", argv[i]) == 0){
+			sprint(ap, "/mnt/services/%s/description", argv[0]);
+			if((fd = open(ap, OWRITE|OTRUNC)) < 0)
+				goto Error;
+			if(write(fd, argv[i+1], strlen(argv[i+1])) < 0)
+				goto Error;
+		} /*else if(strcmp(argv[i], "authdom") == 0){
+			sprint(ap, "/mnt/services/%s/authdom", argv[0]);
+			if((fd = open(ap, OWRITE|OTRUNC)) < 0)
+				goto Error;
+			if(write(fd, argv[i+1], MAXADDR) <= 0)
+				goto Error;
+			}*/
+		i++;
+	}
+	unmount("", "/mnt/services");
+	exits(0);
+Error:
+	fprint(2, "Error publishing service: %r\n");
+	close(sfd);
+	exits("error");
+}
--- /dev/null
+++ b/query.c
@@ -1,0 +1,73 @@
+#include <u.h>
+#include <libc.h>
+#include "libservice/service.h"
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-s svcfs] [-d authdom] query [attr value...]\n", argv0);
+	exits("usage");
+}
+
+void
+search(int fd, char *query, char **argv, int argc)
+{
+	Service *s, *svcs;
+
+	svcs = svcquery(fd, query, argv, argc);
+	for(s = svcs; s; s = s->next){
+		print("service=%s address=%s\n", s->name, s->address);
+		switch(s->status){
+		case Sok:
+			print("\tstatus=ok\n");
+			break;
+		case Sdown:
+			print("\tstatus=down\n");
+			break;
+		case Sreg:
+			print("\tstatus=registered\n");
+			break;
+		}
+		print("\tdescription=\'%s\'\n", s->description);
+		print("\tuptime=%T\n", s->uptime);
+		if(s->next != nil)
+			print("\n");
+	}
+	for(s = svcs; s;){
+		svcs = s->next;
+		free(s);
+		s = svcs;
+	}
+}
+
+void
+main(int argc, char *argv[])
+{
+	char *svcfs;
+	char *authdom;
+	int fd;
+
+	svcfs = nil;
+	authdom = nil;
+	ARGBEGIN{
+	case 's':
+		svcfs = EARGF(usage());
+		break;
+	case 'a':
+		authdom = EARGF(usage());
+		break;
+	default:
+		usage();
+		break;
+	}ARGEND
+	argv0 = "svcfs";
+
+	if(argc == 0)
+		usage();
+	fmtinstall('T', svctimefmt);
+	if((fd = svcdial(svcfs, authdom)) < 0)
+		exits("error");
+	search(fd, argv[0], argv+1, argc-1);
+	close(fd);
+	exits(0);
+}
--- a/registry.c
+++ /dev/null
@@ -1,995 +1,0 @@
-#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	resolver;
-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 'r':
-		resolver = 1;
-		break;
-	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(! resolver && 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);
-
-	if(resolver)
-		regconnect();
-	else
-		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;
-	}
-
-	if(resolver){
-		resolvequery(job, mf, job->request.data, pipe2rc);
-		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;
-
-	if(resolver){
-		resolve("dump");
-		return;
-	}
-
-	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];
-
-	if(resolver){
-		resolve("refresh");
-		return;
-	}
-
-	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);
-	if(resolver)
-		return resolve("add", args);
-	return rstr2cache(args, 0);
-}
-
-static char *
-rmsvc(char *args)
-{
-	if(debug)
-		reglog("Removing entry: %s", args);
-	if(resolver)
-		return resolve("rm", args);
-	return rstrdtch(args);
-}
-
-static char *
-updatesvc(char *args)
-{
-	if(debug)
-		reglog("Updating entry: %s", args);
-	if(resolver)
-		return resolve("update", 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);
-}
-
--- a/reglookup.c
+++ /dev/null
@@ -1,251 +1,0 @@
-#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
-regconnect(void)
-{
-	Ndb *adb;
-	Ndbtuple *t;
-	char *host, *list[1];
-	int rfdn, fd;
-
-	rfdn = 0;
-	adb = ndbopen(0);
-
-	/* Start with us */
-	host = getenv("sysname");
-	list[0] = "registry";
-	t = ndbipinfo(adb, "sys", host, list, 1);
-	if(t->val != nil){
-		fd = dial(t->val, 0, 0, 0);
-		if(fd >= 0)
-			rfd[rfdn++] = fd;
-	}
-	ndbfree(t);
-
-	/* Here we want to find our other registry= tuples in the ndb
-         * I don't claim to know the best way to do this. registry= entries 
-	 * in an ipnet= is the solution i see best fitting, but for now 
-         * this will be marked as TODO and left alone 
-	 * the goal will be, however, to iterate through upstream resolvers
-         * which act as lists for services one may add, like the 9ants grid
-         * registry, as a good example; and when a local resolve misses, it
-         * bubbles up to the next resolver, etc, until we exhaust the list 
-         * or find a match
-         */
-
-	if(rfdn < 1)
-		sysfatal("unable to dial any registry server");
-	rfd[rfdn] = 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);
-}
--- a/regquery.c
+++ /dev/null
@@ -1,67 +1,0 @@
-#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);
-}
--- /dev/null
+++ b/services
@@ -1,0 +1,57 @@
+#!/bin/rc
+
+$argv0 = $0
+
+fn usage {
+    echo 'Usage:' $argv0 '[-o] [-f servicesdb] [-s svcfs]' >[1=2]
+    exits 'usage'
+}
+
+dbfile=()
+svcfs=()
+order=1
+while(~ $1 -*){
+	switch($1){
+	case -o; order=2
+	case *
+		~ $#* 1 && usage
+		switch($1){
+		case -f; dbfile=$2
+		case -s; svcfs=$2
+		case *; usage
+		}
+		shift
+	}
+	shift
+}
+
+! ~ $#* 0 && usage
+
+if(~ $svcfs "")
+	svcfs=`{ndb/ipquery sys $sysname registry | sed 's/registry=//'}
+if(~ $svcfs ""){
+	echo 'Unable to find Registry'
+	exits 'registry'
+}
+# Ours, we check later for the one associated with svcfs
+# With -s, parse the addr ipnet if exists and use that instead
+ipnet=`{ndb/ipquery sys $sysname ipnet | sed 's/ipnet=//'}
+
+fn mount {
+	# Mount up svcfs in our namespace
+	srv -c -m $svcfs^'!16675' services /mnt/services
+
+}
+
+fn publish {
+	# Try /cfg/sysname/services if not set
+	# Initial read + post everything
+	#  - if we posted the service, service.local
+}
+
+fn serve {
+
+}
+
+# Walk the dir, parse addr file + build /srv/service.sysname.ipnet
+# With -o, /srv/ipnet.sysname.service
--- /dev/null
+++ b/svcfs.c
@@ -1,0 +1,932 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <fcall.h>
+#include "libservice/service.h"
+
+typedef struct Fid Fid;
+typedef struct Entry Entry;
+
+enum {
+	Qroot,
+	Qsvc,
+	Qaddr,
+	Qstatus,
+	Quptime,
+	Qdesc,
+	Qmax,
+};
+
+struct Fid {
+	int	fid;
+	ulong	qtype;
+	Entry *svc;
+	int	busy;
+	Fid	*next;
+};
+
+struct Entry {
+	Service *svc;
+	char	removed;
+	int	ref;
+	vlong	uptime;
+	ulong	uniq;
+	uchar	persist;
+	Entry *link;
+};
+
+char *qinfo[Qmax] = {
+	[Qroot]		"services",
+	[Qsvc]		".",
+	[Qaddr]		"address",
+	[Qstatus]	"status",
+	[Quptime]	"uptime",
+	[Qdesc]		"description",
+};
+
+char *status[Smax] = {
+	[Sok] 	= "ok",
+	[Sdown]	= "down",
+	[Sreg] = "registered",
+};
+
+Fid *fids;
+Entry *services[NSVCS];
+char	*svcfile;
+int		readonly;
+ulong	uniq;
+Fcall   rhdr, thdr;
+uchar	mdata[8192 + IOHDRSZ];
+int		messagesize = sizeof mdata;
+
+Entry *findsvc(char*);
+Entry *installsvc(char*);
+void	insertsvc(Entry*);
+int	removesvc(Entry*);
+int	readservices(void);
+void	writeservices(void);
+void	error(char*);
+int	dostat(Entry*, ulong, void*, int);
+void	watch(void);
+void	io(int, int);
+Qid	mkqid(Entry*, 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], pid;
+
+	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");
+
+	readservices();
+	if((pid = rfork(RFPROC|RFNOTEG|RFMEM)) == 0) {
+		watch();
+		exits(0);
+	}
+
+	switch(rfork(RFPROC|RFNAMEG|RFNOTEG|RFNOWAIT|RFENVG|RFFDG|RFMEM)){
+	case 0:
+		close(p[0]);
+		io(p[1], p[1]);
+		postnote(PNPROC, 1, "shutdown");
+		postnote(PNPROC, pid, "shutdown");
+		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)
+{
+	USED(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;
+	Entry *e;
+
+	if(!f->busy)
+		return "walk of unused fid";
+	nf = nil;
+	qtype = f->qtype;
+	e = 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;
+				e = findsvc(name);
+				if(e == nil)
+					goto Out;
+				qtype = Qsvc;
+			Accept:
+				thdr.wqid[i] = mkqid(e, qtype);
+				break;
+			case Qsvc:
+				if(strcmp(name, "..") == 0) {
+					qtype = Qroot;
+					e = nil;
+					goto Accept;
+				}
+				max = Qmax;
+				for(j = Qsvc + 1; j < Qmax; j++)
+					if(strcmp(name, qinfo[j]) == 0){
+						qtype = 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.newfid && i == rhdr.nwname){
+		nf->busy = 1;
+		nf->qtype = qtype;
+		nf->svc = e;
+		if(e != nil)
+			e->ref++;
+	} else if(nf == nil && rhdr.nwname > 0){
+		Clunk(f);
+		f->busy = 1;
+		f->qtype = qtype;
+		f->svc = e;
+		if(e != nil)
+			e->ref++;
+	}
+	thdr.nwqid = i;
+	return 0;		
+}
+
+char *
+Clunk(Fid *f)
+{
+	f->busy = 0;
+	if(f->svc != nil && --f->svc->ref == 0 && f->svc->removed) {
+		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(name) != nil)
+		return "svc already exists";
+	f->svc = installsvc(name);
+	f->svc->ref++;
+	f->qtype = Qsvc;
+
+	thdr.qid = mkqid(f->svc, f->qtype);
+	thdr.iounit = messagesize - IOHDRSZ;
+	writeservices();
+	return 0;
+}
+
+char *
+Read(Fid *f)
+{
+	Entry *e;
+	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(e = services[i]; e != nil; j += m, e = e->link){
+				m = dostat(e, 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->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->svc->address);
+		goto Readstr;
+	case Quptime:
+		sprint(data, "%lld\n", f->svc->uptime);
+		goto Readstr;
+	case Qdesc:
+		sprint(data, "%s\n", f->svc->svc->description);
+		goto Readstr;
+	default:
+		return "permission denied";
+	}	
+}
+
+char *
+Write(Fid *f)
+{
+	char *data;
+	int n;
+
+	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 > NAMELEN)
+			return "address too big!";
+		if(data[n-1] = '\n')
+			n--;
+		memmove(f->svc->svc->address, data, n);
+		f->svc->svc->address[n] = '\0';
+		thdr.count = n;
+		break;
+	case Qdesc:
+		if(n > MAXDESC)
+			return "description too long";
+		if(data[n-1] = '\n')
+			n--;
+		memmove(f->svc->svc->description, data, n);
+		f->svc->svc->description[n] = '\0';
+		thdr.count = n;
+		break;
+	case Qroot:
+	case Qsvc:
+	case Quptime:
+	case Qstatus:
+	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(findsvc(d.name))
+		return "service already exists";
+	if(!removesvc(f->svc))
+		return "service already removed";
+	free(f->svc->svc->name);
+	memmove(f->svc->svc->name, d.name, NAMELEN);
+	insertsvc(f->svc);
+	writeservices();
+	return 0;
+}
+
+Qid
+mkqid(Entry *e, ulong qtype)
+{
+	Qid q;
+
+	q.vers = 0;
+	q.path = qtype;
+	if(e)
+		q.path |= e->uniq * 0x100;
+	if(qtype == Qsvc || qtype == Qroot)
+		q.type = QTDIR;
+	else
+		q.type = QTFILE;
+	return q;
+}
+
+int
+dostat(Entry *e, ulong qtype, void *p, int n)
+{
+	Dir d;
+
+	if(qtype == Qsvc)
+		d.name = e->svc->name;
+	else
+		d.name = qinfo[qtype];
+	d.uid = d.gid = d.muid = "none"; // Maybe reggie or so
+	d.qid = mkqid(e, 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 entrylen;
+	int fd, ns, i;
+	Entry *e;
+	uchar *p, *buf;
+	ns = 0;
+
+	if(readonly){
+		fprint(2, "attempted to write services to disk in a readonly system\n");
+		return;
+	}
+	
+	entrylen = NAMELEN + MAXADDR + MAXDESC;
+	/* Count our services */
+	for(i = 0; i < NSVCS; i++)
+		for(e = services[i]; e != nil; e = e->link)
+			ns++;
+
+	/* Make a buffer large enough to hold each line */
+	buf = emalloc(ns * entrylen);
+	memset(buf, RS, entrylen);
+	p = buf;
+	for(i = 0; i < NSVCS; i++)
+		for(e = services[i]; e !=nil; e = e->link){
+			strncpy((char *)p, e->svc->name, NAMELEN);
+			p += NAMELEN;
+			strncpy((char *)p, e->svc->address, MAXADDR);
+			p += MAXADDR;
+			strncpy((char *)p, e->svc->description, MAXDESC);
+			p += MAXDESC;
+		}
+	fd = create(svcfile, OWRITE, 0660);
+	if(fd < 0){
+		fprint(2, "svcfs: can't write %s: %r\n", svcfile);
+		free(buf);
+		return;
+	}
+	if(write(fd, buf, p - buf) != (p - buf))
+		fprint(2, "svcfs: can't write %s: %r\n", svcfile);
+	close(fd);
+	free(buf);
+}
+
+int
+svcok(char *svc, int nu)
+{
+	int i, n, rv;
+	Rune r;
+	char buf[NAMELEN+1];
+
+	memset(buf, 0, sizeof buf);
+	memmove(buf, svc, NAMELEN);
+
+	if(buf[NAMELEN-1] != 0){
+		fprint(2, "svcfs: %d: no termination\n", nu);
+		return -1;
+	}
+
+	rv = 0;
+	for(i = 0; buf[i]; i += n){
+		n = chartorune(&r, buf+i);
+		if(r == Runeerror){
+			rv = -1;
+		} else if(r == RS) { /* Scrub our spacer out */
+			buf[i] = 0;
+		} else if(isascii(r) && iscntrl(r) || r == ' ' || r == '/')
+			rv = -1;
+	}
+
+	if(i == 0){
+		fprint(2, "svcfs: %d: nil name\n", nu);
+		return -1;
+	}
+	if(rv == -1)
+		fprint(2, "svcfs: %d: bad syntax\n", nu);
+	return rv;
+}
+
+char *
+scrub(uchar *ep, int len)
+{
+	int i, n;
+	Rune r;
+	char *buf;
+
+	buf = emalloc(len);
+	memset(buf, 0, sizeof buf);
+	memmove(buf, ep, len);
+
+	for(i = 0; buf[i]; i += n){
+		n = chartorune(&r, buf+i);
+		if(r == Runeerror)
+			return "error";
+		if(r == RS)
+			buf[i] = 0;
+	}
+	if(i == 0){
+		return "empty";
+	}
+	return buf;
+}
+
+int
+readservices(void)
+{
+	int fd, i, n, ns, entrylen;
+	uchar *buf, *ep;
+	Entry *svc;
+	Dir *d;
+
+	/* Read our file into buf */
+	fd = open(svcfile, OREAD);
+	if(fd < 0){
+		fprint(2, "svcfs: can't read %s: %r\n", svcfile);
+		return 0;
+	}
+	d = dirfstat(fd);
+	if(d == nil){
+		close(fd);
+		return 0;
+	}
+	buf = emalloc(d->length);
+	n = readn(fd, buf, d->length);
+	close(fd);
+	free(d);
+	if(n != d->length){
+		free(buf);
+		return 0;
+	}
+	ep = buf;
+	entrylen = NAMELEN + MAXDESC + MAXADDR;
+	n = n / entrylen;
+	ns = 0;
+	for(i = 0; i < n; ep += entrylen, i++){
+		svc = findsvc((char *)ep);
+		if(svc == nil)
+			svc = installsvc((char *)ep);
+		memmove(svc->svc->address, scrub(ep + NAMELEN, MAXADDR), MAXADDR);
+		memmove(svc->svc->description, scrub(ep + NAMELEN + MAXADDR, MAXDESC), MAXDESC);
+		ns++;
+	}
+	free(buf);
+
+	print("%d services read in\n", ns);
+	return 1;
+}
+
+Entry *
+installsvc(char *name)
+{
+	Entry *e;
+	Service *svc;
+	int h;
+
+	h = hash(name);
+	e = emalloc(sizeof *e);
+	svc = emalloc(sizeof *svc);
+	memmove(svc->name, name, NAMELEN);
+	memmove(svc->description, "No description provided", MAXDESC);
+	memmove(svc->address, "none", 4);
+	svc->status = Sreg;
+	e->removed = 0;
+	e->ref = 0;
+	e->uniq = uniq++;
+	e->svc = svc;
+	e->link = services[h];
+	services[h] = e;
+	return e;
+}
+
+Entry *
+findsvc(char *name)
+{
+	Entry *e;
+
+	for(e = services[hash(name)]; e != nil; e = e->link)
+		if(strcmp(name, e->svc->name) == 0)
+			return e;
+	return nil;
+}
+
+int
+removesvc(Entry *e)
+{
+	Entry *s, **last;
+	char *name;
+
+	e->removed = 1;
+	name = e->svc->name;
+	last = &services[hash(name)];
+	for(s = *last; s != nil; s = *last){
+		if(strcmp(name, s->svc->name) == 0) {
+			*last = s->link;
+			return 1;
+		}
+		last = &s->link;
+	}
+
+	return 0;
+}
+
+void
+insertsvc(Entry *e)
+{
+	int h;
+
+	e->removed = 0;
+	h = hash(e->svc->name);
+	e->link = services[h];
+	services[h] = e;
+}
+
+ulong
+hash(char *s)
+{
+	ulong h;
+
+	h = 0;
+	while(*s)
+		h = (h << 1) ^ *s++;
+	return h % NSVCS;
+}
+
+Fid *
+findfid(int fid)
+{
+	Fid *f, *ff;
+
+
+	ff = nil;
+	for(f = fids; f; f = f->next)
+		if(f->fid == fid)
+			return f;
+		else if(!ff && !f->busy)
+			ff = f;
+	if(ff != nil){
+		ff->fid = fid;
+		return ff;
+	}
+
+	f = emalloc(sizeof *f);
+	f->fid = fid;
+	f->busy = 0;
+	f->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");
+	}
+}
+
+int
+alive(Entry *svc)
+{
+	int fd;
+
+	if(strncmp(svc->svc->address, "none", 4) == 0)
+		return 2;
+	fd = dial(svc->svc->address, nil, nil, nil);
+	if(fd < 0){
+		if(svc->svc->status == Sreg)
+			return 2;
+		return -1;
+	}
+	close(fd);
+	if(svc->svc->status == Sok)
+		return 1;
+	return 0;
+}
+
+void
+watch(void)
+{
+	/* Status, uptime */
+	Entry *svc;
+	int i;
+	int seconds;
+	vlong start;
+
+	seconds = 30;
+	for(;;) {
+		start = nsec();
+		for(i = 0; i < seconds; i++)
+			sleep(1000);
+		for(i = 0; i < NSVCS; i++)
+			for(svc = services[i]; svc !=nil; svc = svc->link)
+				switch(alive(svc)){
+				case -1: 
+					/* Offline */
+					svc->svc->status = Sdown;
+					break;
+				case 0:
+					/* Coming online */
+					svc->svc->status = Sok;
+					svc->svc->uptime = 0;
+					break;
+				case 1:
+					svc->svc->status = Sok;
+					svc->svc->uptime += ((nsec() - start) / 1000000000LL);
+					break;
+				default:
+					/* Still in setup */
+					break;
+				}
+	}
+}
+
+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;
+}
+
+void
+error(char *s)
+{
+	fprint(2, "svcfs: %s\n", s);
+	exits(s);
+}
+