hlfw.ca

registry

Download patch

ref: bbf775bb70004f56a036c4989c9d7eadefc57abf
parent: 5adb5ae62a01b7af1d0190acd33d387c3e33b46c
parent: bcaa13d315ee22b89478cfda520d70c69d4715e5
author: halfwit <michaelmisch1985@gmail.com>
date: Sun Oct 22 13:35:01 PDT 2023

Merge pull request #12 from halfwit/rework

Move around and set up for mk

--- /dev/null
+++ b/aux/mkfile
@@ -1,0 +1,17 @@
+</$objtype/mkfile
+
+TARG=\
+	svcfs\
+
+HFILES=\
+	../service.h\
+
+BIN=/$objtype/bin/aux
+LIB=../lib.$O.a
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${TARG:%.$O=%.c}\
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/aux/svcfs.c
@@ -1,0 +1,966 @@
+#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,
+	Qauth,
+	Qpersist,
+	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;
+	Entry *link;
+};
+
+char *qinfo[Qmax] = {
+	[Qroot]		"services",
+	[Qsvc]		".",
+	[Qaddr]		"address",
+	[Qstatus]	"status",
+	[Quptime]	"uptime",
+	[Qauth]		"authdom",
+	[Qpersist]	"persist",
+	[Qdesc]		"description",
+};
+
+char *status[Smax] = {
+	[Sok] 	= "ok",
+	[Sdown]	= "down",
+	[Spersist] = "persist",
+	[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";
+	name = rhdr.name;
+	perm = rhdr.perm;
+	if(f->svc != nil)
+		return "permission denied";
+	/* Allow creation of persist file */
+	if(strcmp(name, "persist") == 0){
+		f->svc->svc->status = Spersist;
+		f->qtype = Qpersist;
+		thdr.qid = mkqid(f->svc, f->qtype);
+		thdr.iounit = messagesize - IOHDRSZ;
+		writeservices();
+		return 0;
+	}
+	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 "Service 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++){
+			if(i + Qsvc + 1 == Qpersist)
+				continue;
+			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 Qauth:
+		sprint(data, "%s\n", f->svc->svc->authdom);
+		goto Readstr;
+	case Quptime:
+		sprint(data, "%lld\n", f->svc->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 > MAXADDR)
+			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 Qauth:
+		if(n > MAXAUTH)
+			return "address too big!";
+		if(data[n-1] == '\n')
+			n--;
+		memmove(f->svc->svc->authdom, data, n);
+		f->svc->svc->authdom[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:
+	case Qpersist:
+	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 || f->qtype != Qpersist)
+		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";
+	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 + MAXAUTH + 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->authdom, MAXAUTH);
+			p += MAXAUTH;
+			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 + MAXAUTH;
+	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->authdom, scrub(ep+NAMELEN+MAXADDR, MAXAUTH), MAXAUTH);
+		memmove(svc->svc->description, scrub(ep+NAMELEN+MAXADDR+MAXAUTH, 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->authdom, "9front", MAXAUTH);
+	memmove(svc->address, "none", MAXADDR);
+	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(svc->svc->status == Spersist)
+		return 2;
+	if(strcmp(svc->svc->address, "none") == 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 0;
+	return 1;
+}
+
+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 or is persist, do not overwrite */
+					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);
+}
--- a/drop.c
+++ b/drop.c
@@ -1,6 +1,6 @@
 #include <u.h>
 #include <libc.h>
-#include "libservice/service.h"
+#include "service.h"
 
 static void
 usage(void)
--- a/libservice/service.h
+++ /dev/null
@@ -1,36 +1,0 @@
-#include <bio.h>
-#include <ndb.h>
-
-typedef struct Service Service;
-
-enum {
-	NAMELEN = 28,
-	NSVCS = 256,
-	MAXDESC = 256,
-	MAXADDR = 128,
-	MAXAUTH = 128,
-	RS = 0x1e,
-};
-
-enum {
-	Sok,
-	Sdown,
-	Sreg,
-	Spersist,
-	Smax,
-};
-
-struct Service {
-	char name[NAMELEN];
-	char description[MAXDESC];
-	char address[MAXADDR];
-	char authdom[MAXAUTH];
-	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);
-void svcfree(Service *);
--- a/monitor.c
+++ b/monitor.c
@@ -1,6 +1,6 @@
 #include <u.h>
 #include <libc.h>
-#include "libservice/service.h"
+#include "service.h"
 
 void monitor(char *,int, int, int);
 void publish(Service *, char *);
--- a/publish.c
+++ b/publish.c
@@ -1,6 +1,6 @@
 #include <u.h>
 #include <libc.h>
-#include "libservice/service.h"
+#include "service.h"
 
 static void
 usage(void)
--- a/query.c
+++ b/query.c
@@ -1,6 +1,6 @@
 #include <u.h>
 #include <libc.h>
-#include "libservice/service.h"
+#include "service.h"
 
 static void
 usage(void)
--- /dev/null
+++ b/service.h
@@ -1,0 +1,36 @@
+#include <bio.h>
+#include <ndb.h>
+
+typedef struct Service Service;
+
+enum {
+	NAMELEN = 28,
+	NSVCS = 256,
+	MAXDESC = 256,
+	MAXADDR = 128,
+	MAXAUTH = 128,
+	RS = 0x1e,
+};
+
+enum {
+	Sok,
+	Sdown,
+	Sreg,
+	Spersist,
+	Smax,
+};
+
+struct Service {
+	char name[NAMELEN];
+	char description[MAXDESC];
+	char address[MAXADDR];
+	char authdom[MAXAUTH];
+	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);
+void svcfree(Service *);
--- a/svcfs.c
+++ /dev/null
@@ -1,966 +1,0 @@
-#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,
-	Qauth,
-	Qpersist,
-	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;
-	Entry *link;
-};
-
-char *qinfo[Qmax] = {
-	[Qroot]		"services",
-	[Qsvc]		".",
-	[Qaddr]		"address",
-	[Qstatus]	"status",
-	[Quptime]	"uptime",
-	[Qauth]		"authdom",
-	[Qpersist]	"persist",
-	[Qdesc]		"description",
-};
-
-char *status[Smax] = {
-	[Sok] 	= "ok",
-	[Sdown]	= "down",
-	[Spersist] = "persist",
-	[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";
-	name = rhdr.name;
-	perm = rhdr.perm;
-	if(f->svc != nil)
-		return "permission denied";
-	/* Allow creation of persist file */
-	if(strcmp(name, "persist") == 0){
-		f->svc->svc->status = Spersist;
-		f->qtype = Qpersist;
-		thdr.qid = mkqid(f->svc, f->qtype);
-		thdr.iounit = messagesize - IOHDRSZ;
-		writeservices();
-		return 0;
-	}
-	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 "Service 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++){
-			if(i + Qsvc + 1 == Qpersist)
-				continue;
-			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 Qauth:
-		sprint(data, "%s\n", f->svc->svc->authdom);
-		goto Readstr;
-	case Quptime:
-		sprint(data, "%lld\n", f->svc->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 > MAXADDR)
-			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 Qauth:
-		if(n > MAXAUTH)
-			return "address too big!";
-		if(data[n-1] == '\n')
-			n--;
-		memmove(f->svc->svc->authdom, data, n);
-		f->svc->svc->authdom[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:
-	case Qpersist:
-	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 || f->qtype != Qpersist)
-		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";
-	memset(f->svc->svc->name, 0, NAMELEN);
-	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";
-	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 + MAXAUTH + 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->authdom, MAXAUTH);
-			p += MAXAUTH;
-			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 + MAXAUTH;
-	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->authdom, scrub(ep+NAMELEN+MAXADDR, MAXAUTH), MAXAUTH);
-		memmove(svc->svc->description, scrub(ep+NAMELEN+MAXADDR+MAXAUTH, 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->authdom, "9front", MAXAUTH);
-	memmove(svc->address, "none", MAXADDR);
-	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(svc->svc->status == Spersist)
-		return 2;
-	if(strcmp(svc->svc->address, "none") == 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 0;
-	return 1;
-}
-
-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 or is persist, do not overwrite */
-					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);
-}