Commit Diff


commit - ad017cfbf5530cfc3ae2fafd723cdade2a4405f6
commit + d957951b75df08a9bb0293e3e13ff87759afbb92
blob - /dev/null
blob + 9bbcfd319aa8c43487c22878124b4bb720ac1d82 (mode 644)
--- /dev/null
+++ src/cmd/ndb/mkfile
@@ -0,0 +1,13 @@
+<$PLAN9/src/mkhdr
+
+TARG=\
+	ndbmkdb\
+	ndbquery\
+	ndbmkhash\
+	ndbmkhosts\
+	ndbipquery\
+
+LIB=$PLAN9/lib/libndb.a
+
+<$PLAN9/src/mkmany
+
blob - /dev/null
blob + c2859dcab5a8e596e7ea245b23ac17eb16eea883 (mode 644)
--- /dev/null
+++ src/cmd/ndb/ndbipquery.c
@@ -0,0 +1,54 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+
+/*
+ *  search the database for matches
+ */
+
+void
+usage(void)
+{
+	fprint(2, "usage: ipquery attr value rattribute\n");
+	exits("usage");
+}
+
+void
+search(Ndb *db, char *attr, char *val, char **rattr, int nrattr)
+{
+	Ndbtuple *t;
+
+	t = ndbipinfo(db, attr, val, rattr, nrattr);
+	for(; t; t = t->entry)
+		print("%s=%s ", t->attr, t->val);
+	print("\n");
+	ndbfree(t);
+}
+
+void
+main(int argc, char **argv)
+{
+	Ndb *db;
+	char *dbfile = 0;
+
+	ARGBEGIN{
+	case 'f':
+		dbfile = ARGF();
+		break;
+	}ARGEND;
+
+	if(argc < 3)
+		usage();
+
+	db = ndbopen(dbfile);
+	if(db == 0){
+		fprint(2, "no db files\n");
+		exits("no db");
+	}
+	search(db, argv[0], argv[1], argv+2, argc-2);
+	ndbclose(db);
+
+	exits(0);
+}
blob - /dev/null
blob + e19685a63b99e3d96464190c290559409598791e (mode 644)
--- /dev/null
+++ src/cmd/ndb/ndbmkdb.c
@@ -0,0 +1,203 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+
+Biobuf in;
+Biobuf out;
+
+enum
+{
+	Empty,
+	Sys,
+	Dk,
+	Ip,
+	Domain,
+};
+
+int
+iscomment(char *name)
+{
+	return *name == '#';
+}
+
+/*
+ *  is this a fully specified datakit name?
+ */
+int
+isdk(char *name)
+{
+	int slash;
+
+	slash = 0;
+	for(; *name; name++){
+		if(isalnum(*name))
+			continue;
+		if(*name == '/'){
+			slash = 1;
+			continue;
+		}
+		return 0;
+	}
+	return slash;
+}
+
+/*
+ *  Is this an internet domain name?
+ */
+int
+isdomain(char *name)
+{
+	int dot = 0;
+	int alpha = 0;
+
+	for(; *name; name++){
+		if(isalpha(*name) || *name == '-'){
+			alpha = 1;
+			continue;
+		}
+		if(*name == '.'){
+			dot = 1;
+			continue;
+		}
+		if(isdigit(*name))
+			continue;
+		return 0;
+	}
+	return dot && alpha;
+}
+
+/*
+ *  is this an ip address?
+ */
+int
+isip(char *name)
+{
+	int dot = 0;
+
+	for(; *name; name++){
+		if(*name == '.'){
+			dot = 1;
+			continue;
+		}
+		if(isdigit(*name))
+			continue;
+		return 0;
+	}
+	return dot;
+}
+
+char tup[64][64];
+int ttype[64];
+int ntup;
+
+void
+tprint(void)
+{
+	int i, tab;
+	char *p;
+
+	tab = 0;
+	for(i = 0; i < ntup; i++){
+		if(ttype[i] == Sys){
+			Bprint(&out, "sys = %s\n", tup[i]);
+			tab = 1;
+			ttype[i] = Empty;
+			break;
+		}
+	}
+	for(i = 0; i < ntup; i++){
+		if(ttype[i] == Empty)
+			continue;
+		if(tab)
+			Bprint(&out, "\t");
+		tab = 1;
+
+		switch(ttype[i]){
+		case Domain:
+			Bprint(&out, "dom=%s\n", tup[i]);
+			break;
+		case Ip:
+			Bprint(&out, "ip=%s\n", tup[i]);
+			break;
+		case Dk:
+			p = strrchr(tup[i], '/');
+			if(p){
+				p++;
+				if((*p == 'C' || *p == 'R')
+				&& strncmp(tup[i], "nj/astro/", p-tup[i]) == 0)
+					Bprint(&out, "flavor=console ");
+			}
+			Bprint(&out, "dk=%s\n", tup[i]);
+			break;
+		case Sys:
+			Bprint(&out, "sys=%s\n", tup[i]);
+			break;
+		}
+	}
+}
+
+#define NFIELDS 64
+
+/*
+ *  make a database file from a merged uucp/inet database
+ */
+void
+main(void)
+{
+	int n, i, j;
+	char *l;
+	char *fields[NFIELDS];
+	int ftype[NFIELDS];
+	int same, match;
+
+	Binit(&in, 0, OREAD);
+	Binit(&out, 1, OWRITE);
+	ntup = 0;
+	while(l = Brdline(&in, '\n')){
+		l[Blinelen(&in)-1] = 0;
+		n = getfields(l, fields, NFIELDS, 1, " \t");
+		same = 0;
+		for(i = 0; i < n; i++){
+			if(iscomment(fields[i])){
+				n = i;
+				break;
+			}
+			if(isdomain(fields[i])){
+				ftype[i] = Domain;
+				for(j = 0; j < ntup; j++)
+					if(ttype[j] == Domain && strcmp(fields[i], tup[j]) == 0){
+						same = 1;
+						ftype[i] = Empty;
+						break;
+					}
+			} else if(isip(fields[i]))
+				ftype[i] = Ip;
+			else if(isdk(fields[i]))
+				ftype[i] = Dk;
+			else
+				ftype[i] = Sys;
+		}
+		if(!same && ntup){
+			tprint();
+			ntup = 0;
+		}
+		for(i = 0; i < n; i++){
+			match = 0;
+			for(j = 0; j < ntup; j++){
+				if(ftype[i] == ttype[j] && strcmp(fields[i], tup[j]) == 0){
+					match = 1;
+					break;
+				}
+			}
+			if(!match){
+				ttype[ntup] = ftype[i];
+				strcpy(tup[ntup], fields[i]);
+				ntup++;
+			}
+		}
+	}
+	if(ntup)
+		tprint();
+	exits(0);
+}
blob - /dev/null
blob + 507bd7c7809b9dae8f87b92697855ff84637dd07 (mode 644)
--- /dev/null
+++ src/cmd/ndb/ndbmkhash.c
@@ -0,0 +1,155 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+
+/*
+ *  make the hash table completely in memory and then write as a file
+ */
+
+uchar *ht;
+ulong hlen;
+Ndb *db;
+ulong nextchain;
+
+char*
+syserr(void)
+{
+	static char buf[ERRMAX];
+
+	errstr(buf, sizeof buf);
+	return buf;
+}
+
+void
+enter(char *val, ulong dboff)
+{
+	ulong h;
+	uchar *last;
+	ulong ptr;
+
+	h = ndbhash(val, hlen);
+	h *= NDBPLEN;
+	last = &ht[h];
+	ptr = NDBGETP(last);
+	if(ptr == NDBNAP){
+		NDBPUTP(dboff, last);
+		return;
+	}
+
+	if(ptr & NDBCHAIN){
+		/* walk the chain to the last entry */
+		for(;;){
+			ptr &= ~NDBCHAIN;
+			last = &ht[ptr+NDBPLEN];
+			ptr = NDBGETP(last);
+			if(ptr == NDBNAP){
+				NDBPUTP(dboff, last);
+				return;
+			}
+			if(!(ptr & NDBCHAIN)){
+				NDBPUTP(nextchain|NDBCHAIN, last);
+				break;
+			}
+		}
+	} else
+		NDBPUTP(nextchain|NDBCHAIN, last);
+
+	/* add a chained entry */
+	NDBPUTP(ptr, &ht[nextchain]);
+	NDBPUTP(dboff, &ht[nextchain + NDBPLEN]);
+	nextchain += 2*NDBPLEN;
+}
+
+uchar nbuf[16*1024];
+
+void
+main(int argc, char **argv)
+{
+	Ndbtuple *t, *nt;
+	int n;
+	Dir *d;	
+	uchar buf[8];
+	char file[128];
+	int fd;
+	ulong off;
+	uchar *p;
+
+	if(argc != 3){
+		fprint(2, "mkhash: usage file attribute\n");
+		exits("usage");
+	}
+	db = ndbopen(argv[1]);
+	if(db == 0){
+		fprint(2, "mkhash: can't open %s\n", argv[1]);
+		exits(syserr());
+	}
+
+	/* try a bigger than normal buffer */
+	Binits(&db->b, Bfildes(&db->b), OREAD, nbuf, sizeof(nbuf));
+
+	/* count entries to calculate hash size */
+	n = 0;
+
+	while(nt = ndbparse(db)){
+		for(t = nt; t; t = t->entry){
+			if(strcmp(t->attr, argv[2]) == 0)
+				n++;
+		}
+		ndbfree(nt);
+	}
+
+	/* allocate an array large enough for worst case */
+	hlen = 2*n+1;
+	n = hlen*NDBPLEN + hlen*2*NDBPLEN;
+	ht = mallocz(n, 1);
+	if(ht == 0){
+		fprint(2, "mkhash: not enough memory\n");
+		exits(syserr());
+	}
+	for(p = ht; p < &ht[n]; p += NDBPLEN)
+		NDBPUTP(NDBNAP, p);
+	nextchain = hlen*NDBPLEN;
+
+	/* create the in core hash table */
+	Bseek(&db->b, 0, 0);
+	off = 0;
+	while(nt = ndbparse(db)){
+		for(t = nt; t; t = t->entry){
+			if(strcmp(t->attr, argv[2]) == 0)
+				enter(t->val, off);
+		}
+		ndbfree(nt);
+		off = Boffset(&db->b);
+	}
+
+	/* create the hash file */
+	snprint(file, sizeof(file), "%s.%s", argv[1], argv[2]);
+	fd = create(file, ORDWR, 0664);
+	if(fd < 0){
+		fprint(2, "mkhash: can't create %s\n", file);
+		exits(syserr());
+	}
+	NDBPUTUL(db->mtime, buf);
+	NDBPUTUL(hlen, buf+NDBULLEN);
+	if(write(fd, buf, NDBHLEN) != NDBHLEN){
+		fprint(2, "mkhash: writing %s\n", file);
+		exits(syserr());
+	}
+	if(write(fd, ht, nextchain) != nextchain){
+		fprint(2, "mkhash: writing %s\n", file);
+		exits(syserr());
+	}
+	close(fd);
+
+	/* make sure file didn't change while we were making the hash */
+	d = dirstat(argv[1]);
+	if(d == nil || d->qid.path != db->qid.path
+	   || d->qid.vers != db->qid.vers){
+		fprint(2, "mkhash: %s changed underfoot\n", argv[1]);
+		remove(file);
+		exits("changed");
+	}
+
+	exits(0);
+}
blob - /dev/null
blob + 46f9f978f814f2caa04b147edcdf431e21dd9a88 (mode 644)
--- /dev/null
+++ src/cmd/ndb/ndbmkhosts.c
@@ -0,0 +1,233 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+
+typedef struct x
+{
+	Ndbtuple *t;
+	Ndbtuple *it;
+	Ndbtuple *nt;
+} X;
+
+X x[4096];
+int nx;
+char *domname = "research.att.com";
+int domnamlen;
+
+char*
+upper(char *x)
+{
+	char *p;
+	int c;
+
+	for(p = x; c = *p; p++)
+		*p = toupper(c);
+	return x;
+}
+
+void
+printArecord(int fd, X *p)
+{
+	Ndbtuple *nt;
+	char *c;
+	char *dom = 0;
+	char *curdom = 0;
+	int i, cdlen = 0;
+	int mxweight = 0;
+
+	if(p->nt) {
+		return;
+	}
+	for(nt=p->t; nt; nt = nt->entry) {
+		/* we are only going to handle things in the specified domain */
+		c = strchr(nt->val, '.');
+		if (c==0 || strcmp(++c, domname)!=0)
+			continue;
+		i = c - nt->val - 1;
+		if(strcmp(nt->attr, "dom") == 0) {
+			curdom = nt->val;
+			cdlen = i;
+			if (dom == 0) {
+				dom = curdom;
+				fprint(fd, "%-.*s%.*s	IN A	%s\n", i, nt->val, 15-i, "               ", p->it->val);
+			} else
+				fprint(fd, "%-.*s%.*s	IN CNAME	%s.\n", i, nt->val, 15-i, "               ", dom);
+		} else if(strcmp(nt->attr, "mx") == 0) {
+			if (curdom != 0)
+				fprint(fd, "%-.*s%.*s	MX	%d	%s.\n", cdlen, curdom, 15-cdlen, "               ", mxweight++, nt->val);
+		}
+	}
+}
+
+void
+printentry(int fd, X *p)
+{
+	Ndbtuple *nt;
+
+	if(p->nt)
+		return;
+	fprint(fd, "%s	", p->it->val);
+	for(nt = p->t; nt; nt = nt->entry)
+		if(strcmp(nt->attr, "dom") == 0)
+			fprint(fd, " %s", nt->val);
+	for(nt = p->t; nt; nt = nt->entry)
+		if(strcmp(nt->attr, "sys") == 0)
+			fprint(fd, " %s", nt->val);
+	fprint(fd, "\n");
+}
+
+void
+printsys(int fd, X *p)
+{
+	Ndbtuple *nt;
+
+	for(nt = p->t; nt; nt = nt->entry)
+		if(strcmp(nt->attr, "dom") == 0)
+			fprint(fd, "%s\n", nt->val);
+}
+
+void
+printtxt(int fd, X *p)
+{
+	int i;
+	Ndbtuple *nt;
+
+	if(p->nt){
+		for(;;){
+			i = strlen(p->it->val);
+			if(strcmp(p->it->val+i-2, ".0") == 0)
+				p->it->val[i-2] = 0;
+			else
+				break;
+		}
+		fprint(fd, "\nNET : %s : %s\n", p->it->val, upper(p->nt->val));
+		return;
+	}
+	fprint(fd, "HOST : %s :", p->it->val);
+	i = 0;
+	for(nt = p->t; nt; nt = nt->entry)
+		if(strcmp(nt->attr, "dom") == 0){
+			if(i++ == 0)
+				fprint(fd, " %s", upper(nt->val));
+			else
+				fprint(fd, ", %s", upper(nt->val));
+		}
+	fprint(fd, "\n");
+}
+
+void
+parse(char *file)
+{
+	int i;
+	Ndb *db;
+	Ndbtuple *t, *nt, *tt, *ipnett;
+	char *p;
+
+	db = ndbopen(file);
+	if(db == 0)
+		exits("no database");
+	while(t = ndbparse(db)){
+		for(nt = t; nt; nt = nt->entry){
+			if(strcmp(nt->attr, "ip") == 0)
+				break;
+			if(strcmp(nt->attr, "flavor") == 0
+			&& strcmp(nt->val, "console") == 0)
+				return;
+		}
+		if(nt == 0){
+			ndbfree(t);
+			continue;
+		}
+
+		/* dump anything not on our nets */
+		ipnett = 0;
+		for(tt = t; tt; tt = tt->entry){
+			if(strcmp(tt->attr, "ipnet") == 0){
+				ipnett = tt;
+				break;
+			}
+			if(strcmp(tt->attr, "dom") == 0){
+				i = strlen(tt->val);
+				p = tt->val+i-domnamlen;
+				if(p >= tt->val && strcmp(p, domname) == 0)
+					break;
+			}
+		}
+		if(tt == 0){
+			ndbfree(t);
+			continue;
+		}
+
+		for(; nt; nt = nt->entry){
+			if(strcmp(nt->attr, "ip") != 0)
+				continue;
+			x[nx].it = nt;
+			x[nx].nt = ipnett;
+			x[nx++].t = t;
+		}
+	}
+}
+
+void
+main(int argc, char *argv[])
+{
+	int i, fd;
+	char fn[128];
+
+	if (argc>1)
+		domname = argv[1];
+	domnamlen = strlen(domname);
+	if(argc > 2){
+		for(i = 2; i < argc; i++)
+			parse(argv[i]);
+	} else {
+		parse(unsharp("#9/ndb/local"));
+		parse(unsharp("#9/ndb/friends"));
+	}
+	
+//	sprint(fn, "/lib/ndb/hosts.%-.21s", domname);
+//	fd = create(fn, OWRITE, 0664);
+//	if(fd < 0){
+//		fprint(2, "can't create %s: %r\n", fn);
+//		exits("boom");
+//	}
+//	for(i = 0; i < nx; i++)
+//		printentry(fd, &x[i]);
+//	close(fd);
+//
+
+	sprint(fn, "/lib/ndb/db.%-.24s", domname);
+	fd = create(fn, OWRITE, 0664);
+	if(fd < 0){
+		fprint(2, "can't create %s: %r\n", fn);
+		exits("boom");
+	}
+	fprint(fd, "; This file is generated automatically, do not edit!\n");
+	for(i = 0; i < nx; i++)
+		printArecord(fd, &x[i]);
+	close(fd);
+
+	sprint(fn, "/lib/ndb/equiv.%-.21s", domname);
+	fd = create(fn, OWRITE, 0664);
+	if(fd < 0){
+		fprint(2, "can't create %s: %r\n", fn);
+		exits("boom");
+	}
+	for(i = 0; i < nx; i++)
+		printsys(fd, &x[i]);
+	close(fd);
+
+	sprint(fn, "/lib/ndb/txt.%-.23s", domname);
+	fd = create(fn, OWRITE, 0664);
+	if(fd < 0){
+		fprint(2, "can't create %s: %r\n", fn);
+		exits("boom");
+	}
+	for(i = 0; i < nx; i++)
+		printtxt(fd, &x[i]);
+	close(fd);
+
+	exits(0);
+}
blob - /dev/null
blob + 65bc04720bd103d653764dfa648aff0e1808d0b3 (mode 644)
--- /dev/null
+++ src/cmd/ndb/ndbquery.c
@@ -0,0 +1,81 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+
+/*
+ *  search the database for matches
+ */
+void
+usage(void)
+{
+	fprint(2, "usage: query attr value [returned attribute]\n");
+	exits("usage");
+}
+
+void
+search(Ndb *db, char *attr, char *val, char *rattr)
+{
+	Ndbs s;
+	Ndbtuple *t;
+	Ndbtuple *nt;
+	char *p;
+
+	if(rattr){
+		p = ndbgetvalue(db, &s, attr, val, rattr, nil);
+		if(p){
+			print("%s\n", p);
+			free(p);
+		}
+		return;
+	}
+
+	t = ndbsearch(db, &s, attr, val);
+	while(t){
+		for(nt = t; nt; nt = nt->entry)
+			print("%s=%s ", nt->attr, nt->val);
+		print("\n");
+		ndbfree(t);
+		t = ndbsnext(&s, attr, val);
+	}
+}
+
+void
+main(int argc, char **argv)
+{
+	char *rattr = 0;
+	Ndb *db;
+	char *dbfile = 0;
+	int reps = 1;
+
+	ARGBEGIN{
+	case 'f':
+		dbfile = ARGF();
+		break;
+	}ARGEND;
+
+	switch(argc){
+	case 4:
+		reps = atoi(argv[3]);
+		/* fall through */
+	case 3:
+		rattr = argv[2];
+		break;
+	case 2:
+		rattr = 0;
+		break;
+	default:
+		usage();
+	}
+	
+	db = ndbopen(dbfile);
+	if(db == 0){
+		fprint(2, "no db files\n");
+		exits("no db");
+	}
+	while(reps--)
+		search(db, argv[0], argv[1], rattr);
+	ndbclose(db);
+
+	exits(0);
+}
blob - /dev/null
blob + 22705adecbbaf2d112081b8297999e562969b40b (mode 644)
--- /dev/null
+++ src/libndb/csgetval.c
@@ -0,0 +1,107 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ndbhf.h>
+
+/*
+ *  search for a tuple that has the given 'attr=val' and also 'rattr=x'.
+ *  copy 'x' into 'buf' and return the whole tuple.
+ *
+ *  return 0 if not found.
+ */
+char*
+csgetvalue(char *netroot, char *attr, char *val, char *rattr, Ndbtuple **pp)
+{
+	Ndbtuple *t, *first, *last;
+	int n, linefound;
+	char line[1024];
+	int fd;
+	int oops = 0;
+	char *rv;
+
+	if(pp)
+		*pp = nil;
+	rv = nil;
+
+	if(netroot)
+		snprint(line, sizeof(line), "%s/cs", netroot);
+	else
+		strcpy(line, "/net/cs");
+	fd = open(line, ORDWR);
+	if(fd < 0)
+		return 0;
+	seek(fd, 0, 0);
+	snprint(line, sizeof(line), "!%s=%s %s=*", attr, val, rattr);
+	if(write(fd, line, strlen(line)) < 0){
+		close(fd);
+		return 0;
+	}
+	seek(fd, 0, 0);
+
+	first = last = 0;
+	linefound = 0;
+	for(;;){
+		n = read(fd, line, sizeof(line)-2);
+		if(n <= 0)
+			break;
+		line[n] = '\n';
+		line[n+1] = 0;
+
+		t = _ndbparseline(line);
+		if(t == 0)
+			continue;
+		if(first)
+			last->entry = t;
+		else
+			first = t;
+		last = t;
+
+		while(last->entry)
+			last = last->entry;
+
+		for(; t; t = t->entry){
+			if(linefound == 0){
+				if(strcmp(rattr, t->attr) == 0){
+					linefound = 1;
+					rv = strdup(t->val);
+				}
+			}
+		}
+	}
+	close(fd);
+
+	if(oops){
+		werrstr("buffer too short");
+		ndbfree(first);
+		return nil;
+	}
+
+	if(pp){
+		setmalloctag(first, getcallerpc(&netroot));
+		*pp = first;
+	} else
+		ndbfree(first);
+
+	return rv;
+}
+
+Ndbtuple*
+csgetval(char *netroot, char *attr, char *val, char *rattr, char *buf)
+{
+	Ndbtuple *t;
+	char *p;
+
+	p = csgetvalue(netroot, attr, val, rattr, &t);
+	if(p == nil){
+		if(buf != nil)
+			*buf = 0;
+	} else {
+		if(buf != nil){
+			strncpy(buf, p, Ndbvlen-1);
+			buf[Ndbvlen-1] = 0;
+		}
+		free(p);
+	}
+	return t;
+}
blob - /dev/null
blob + 76f3d8e0950f8ef77da2517fb47f02e56acfc8f3 (mode 644)
--- /dev/null
+++ src/libndb/csipinfo.c
@@ -0,0 +1,68 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ndbhf.h>
+
+/*
+ *  look up the ip attributes 'list' for an entry that has the
+ *  given 'attr=val' and a 'ip=' tuples.
+ *
+ *  return nil if not found.
+ */
+Ndbtuple*
+csipinfo(char *netroot, char *attr, char *val, char **list, int n)
+{
+	Ndbtuple *t, *first, *last;
+	int i;
+	char line[1024];
+	int fd;
+	char *p, *e;
+
+	if(netroot)
+		snprint(line, sizeof(line), "%s/cs", netroot);
+	else
+		strcpy(line, "/net/cs");
+	fd = open(line, ORDWR);
+	if(fd < 0)
+		return 0;
+	seek(fd, 0, 0);
+	e = line + sizeof(line);
+	p = seprint(line, e, "!ipinfo %s=%s", attr, val);
+	for(i = 0; i < n; i++){
+		if(*list == nil)
+			break;
+		p = seprint(p, e, " %s", *list++);
+	}
+	
+	if(write(fd, line, strlen(line)) < 0){
+		close(fd);
+		return 0;
+	}
+	seek(fd, 0, 0);
+
+	first = last = 0;
+	for(;;){
+		n = read(fd, line, sizeof(line)-2);
+		if(n <= 0)
+			break;
+		line[n] = '\n';
+		line[n+1] = 0;
+
+		t = _ndbparseline(line);
+		if(t == 0)
+			continue;
+		if(first)
+			last->entry = t;
+		else
+			first = t;
+		last = t;
+
+		while(last->entry)
+			last = last->entry;
+	}
+	close(fd);
+
+	setmalloctag(first, getcallerpc(&netroot));
+	return first;
+}
blob - /dev/null
blob + 4cf8735ad3ae38e94c85028d2cbcbf04dd138e84 (mode 644)
--- /dev/null
+++ src/libndb/dnsquery.c
@@ -0,0 +1,156 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ndbhf.h>
+
+static void nstrcpy(char*, char*, int);
+static void mkptrname(char*, char*, int);
+static Ndbtuple *doquery(int, char *dn, char *type);
+
+/*
+ *  search for a tuple that has the given 'attr=val' and also 'rattr=x'.
+ *  copy 'x' into 'buf' and return the whole tuple.
+ *
+ *  return 0 if not found.
+ */
+Ndbtuple*
+dnsquery(char *net, char *val, char *type)
+{
+	char rip[128];
+	char *p;
+	Ndbtuple *t;
+	int fd;
+
+	/* if the address is V4 or V6 null address, give up early vwhoi*/
+	if(strcmp(val, "::") == 0 || strcmp(val, "0.0.0.0") == 0)
+		return nil;
+
+	if(net == nil)
+		net = "/net";
+	snprint(rip, sizeof(rip), "%s/dns", net);
+	fd = open(rip, ORDWR);
+	if(fd < 0){
+		if(strcmp(net, "/net") == 0)
+			snprint(rip, sizeof(rip), "/srv/dns");
+		else {
+			snprint(rip, sizeof(rip), "/srv/dns%s", net);
+			p = strrchr(rip, '/');
+			*p = '_';
+		}
+		fd = open(rip, ORDWR);
+		if(fd < 0)
+			return nil;
+		if(mount(fd, -1, net, MBEFORE, "") < 0){
+			close(fd);
+			return nil;
+		}
+		/* fd is now closed */
+		snprint(rip, sizeof(rip), "%s/dns", net);
+		fd = open(rip, ORDWR);
+		if(fd < 0)
+			return nil;
+	}
+
+	/* zero out the error string */
+	werrstr("");
+
+	/* if this is a reverse lookup, first lookup the domain name */
+	if(strcmp(type, "ptr") == 0){
+		mkptrname(val, rip, sizeof rip);
+		t = doquery(fd, rip, "ptr");
+	} else
+		t = doquery(fd, val, type);
+
+	close(fd);
+	return t;
+}
+
+/*
+ *  convert address into a reverse lookup address
+ */
+static void
+mkptrname(char *ip, char *rip, int rlen)
+{
+	char buf[128];
+	char *p, *np;
+	int len;
+
+	if(strstr(ip, "in-addr.arpa") || strstr(ip, "IN-ADDR.ARPA")){
+		nstrcpy(rip, ip, rlen);
+		return;
+	}
+
+	nstrcpy(buf, ip, sizeof buf);
+	for(p = buf; *p; p++)
+		;
+	*p = '.';
+	np = rip;
+	len = 0;
+	while(p >= buf){
+		len++;
+		p--;
+		if(*p == '.'){
+			memmove(np, p+1, len);
+			np += len;
+			len = 0;
+		}
+	}
+	memmove(np, p+1, len);
+	np += len;
+	strcpy(np, "in-addr.arpa");
+}
+
+static void
+nstrcpy(char *to, char *from, int len)
+{
+	strncpy(to, from, len);
+	to[len-1] = 0;
+}
+
+static Ndbtuple*
+doquery(int fd, char *dn, char *type)
+{
+	char buf[1024];
+	int n;
+	Ndbtuple *t, *first, *last;
+
+	seek(fd, 0, 0);
+	snprint(buf, sizeof(buf), "!%s %s", dn, type);
+	if(write(fd, buf, strlen(buf)) < 0)
+		return nil;
+		
+	seek(fd, 0, 0);
+
+	first = last = nil;
+	
+	for(;;){
+		n = read(fd, buf, sizeof(buf)-2);
+		if(n <= 0)
+			break;
+		if(buf[n-1] != '\n')
+			buf[n++] = '\n';	/* ndbparsline needs a trailing new line */
+		buf[n] = 0;
+
+		/* check for the error condition */
+		if(buf[0] == '!'){
+			werrstr("%s", buf+1);
+			return nil;
+		}
+
+		t = _ndbparseline(buf);
+		if(t != nil){
+			if(first)
+				last->entry = t;
+			else
+				first = t;
+			last = t;
+
+			while(last->entry)
+				last = last->entry;
+		}
+	}
+
+	setmalloctag(first, getcallerpc(&fd));
+	return first;
+}
blob - /dev/null
blob + d23d5ee09e5048016ad664cb4abbcce2665197d5 (mode 644)
--- /dev/null
+++ src/libndb/ipattr.c
@@ -0,0 +1,46 @@
+#include <u.h>
+#include <ctype.h>
+
+/*
+ *  return ndb attribute type of an ip name
+ */
+char*
+ipattr(char *name)
+{
+	char *p, c;
+	int dot = 0;
+	int alpha = 0;
+	int colon = 0;
+	int hex = 0;
+
+	for(p = name; *p; p++){
+		c = *p;
+		if(isdigit(c))
+			continue;
+		if(isxdigit(c))
+			hex = 1;
+		else if(isalpha(c) || c == '-')
+			alpha = 1;
+		else if(c == '.')
+			dot = 1;
+		else if(c == ':')
+			colon = 1;
+		else
+			return "sys";
+	}
+
+	if(alpha){
+		if(dot)
+			return "dom";
+		else
+			return "sys";
+	}
+
+	if(colon)
+		return "ip";	/* ip v6 */
+
+	if(dot && !hex)
+		return "ip";
+	else
+		return "sys";
+}
blob - /dev/null
blob + a49cd5e01e43fdfbaa7eacd2143905f8d9c9cbc5 (mode 644)
--- /dev/null
+++ src/libndb/mkfile
@@ -0,0 +1,32 @@
+<$PLAN9/src/mkhdr
+
+LIB=libndb.a
+OFILES=\
+#	csgetval.$O\
+#	csipinfo.$O\
+#	dnsquery.$O\
+	ipattr.$O\
+	ndbaux.$O\
+	ndbcache.$O\
+	ndbcat.$O\
+	ndbconcatenate.$O\
+	ndbdiscard.$O\
+	ndbfree.$O\
+	ndbgetipaddr.$O\
+	ndbgetval.$O\
+	ndbhash.$O\
+	ndbipinfo.$O\
+	ndblookval.$O\
+	ndbopen.$O\
+	ndbparse.$O\
+	ndbreorder.$O\
+	ndbsubstitute.$O\
+
+HFILES=\
+	$PLAN9/include/ndb.h\
+	ndbhf.h
+
+<$PLAN9/src/mksyslib
+
+$O.out: testipinfo.$O
+	$LD $prereq
blob - /dev/null
blob + 94246fc6a97f1da8ba6c0752ce6afa1fea0b664a (mode 644)
--- /dev/null
+++ src/libndb/ndbaux.c
@@ -0,0 +1,94 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ndb.h>
+#include "ndbhf.h"
+
+
+/*
+ *  parse a single tuple
+ */
+char*
+_ndbparsetuple(char *cp, Ndbtuple **tp)
+{
+	char *p;
+	int len;
+	Ndbtuple *t;
+
+	/* a '#' starts a comment lasting till new line */
+	EATWHITE(cp);
+	if(*cp == '#' || *cp == '\n')
+		return 0;
+
+	t = ndbnew(nil, nil);
+	setmalloctag(t, getcallerpc(&cp));
+	*tp = t;
+
+	/* parse attribute */
+	p = cp;
+	while(*cp != '=' && !ISWHITE(*cp) && *cp != '\n')
+		cp++;
+	len = cp - p;
+	if(len >= Ndbalen)
+		len = Ndbalen-1;
+	strncpy(t->attr, p, len);
+
+	/* parse value */
+	EATWHITE(cp);
+	if(*cp == '='){
+		cp++;
+		if(*cp == '"'){
+			p = ++cp;
+			while(*cp != '\n' && *cp != '"')
+				cp++;
+			len = cp - p;
+			if(*cp == '"')
+				cp++;
+		} else if(*cp == '#'){
+			len = 0;
+		} else {
+			p = cp;
+			while(!ISWHITE(*cp) && *cp != '\n')
+				cp++;
+			len = cp - p;
+		}
+		ndbsetval(t, p, len);
+	}
+
+	return cp;
+}
+
+/*
+ *  parse all tuples in a line.  we assume that the 
+ *  line ends in a '\n'.
+ *
+ *  the tuples are linked as a list using ->entry and
+ *  as a ring using ->line.
+ */
+Ndbtuple*
+_ndbparseline(char *cp)
+{
+	Ndbtuple *t;
+	Ndbtuple *first, *last;
+
+	first = last = 0;
+	while(*cp != '#' && *cp != '\n'){
+		t = 0;
+		cp = _ndbparsetuple(cp, &t);
+		if(cp == 0)
+			break;
+		if(first){
+			last->line = t;
+			last->entry = t;
+		} else
+			first = t;
+		last = t;
+		t->line = 0;
+		t->entry = 0;
+	}
+	if(first)
+		last->line = first;
+	setmalloctag(first, getcallerpc(&cp));
+	return first;
+}
blob - /dev/null
blob + 701d63ebc87c3bed690a5e117d7f261ca00365a2 (mode 644)
--- /dev/null
+++ src/libndb/ndbcache.c
@@ -0,0 +1,144 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+
+struct Ndbcache
+{
+	Ndbcache	*next;
+	char		*attr;
+	char		*val;
+	Ndbs		s;
+	Ndbtuple	*t;	
+};
+
+enum
+{
+	Maxcached=	128,
+};
+
+static void
+ndbcachefree(Ndbcache *c)
+{
+	free(c->val);
+	free(c->attr);
+	if(c->t)
+		ndbfree(c->t);
+	free(c);
+}
+
+static Ndbtuple*
+ndbcopy(Ndb *db, Ndbtuple *from_t, Ndbs *from_s, Ndbs *to_s)
+{
+	Ndbtuple *first, *to_t, *last, *line;
+	int newline;
+
+	*to_s = *from_s;
+	to_s->t = nil;
+	to_s->db = db;
+
+	newline = 1;
+	last = nil;
+	first = nil;
+	line = nil;
+	for(; from_t != nil; from_t = from_t->entry){
+		to_t = ndbnew(from_t->attr, from_t->val);
+
+		/* have s point to matching tuple */
+		if(from_s->t == from_t)
+			to_s->t = to_t;
+
+		if(newline)
+			line = to_t;
+		else
+			last->line = to_t;
+
+		if(last != nil)
+			last->entry = to_t;
+		else {
+			first = to_t;
+			line = to_t;
+		}
+		to_t->entry = nil;
+		to_t->line = line;
+		last = to_t;
+		newline = from_t->line != from_t->entry;
+	}
+	return first;
+}
+
+/*
+ *  if found, move to front
+ */
+int
+_ndbcachesearch(Ndb *db, Ndbs *s, char *attr, char *val, Ndbtuple **t)
+{
+	Ndbcache *c, **l;
+
+	*t = nil;
+	c = nil;
+	for(l = &db->cache; *l != nil; l = &(*l)->next){
+		c = *l;
+		if(strcmp(c->attr, attr) == 0 && strcmp(c->val, val) == 0)
+			break;
+	}
+	if(*l == nil)
+		return -1;
+
+	/* move to front */
+	*l = c->next;
+	c->next = db->cache;
+	db->cache = c;
+
+	*t = ndbcopy(db, c->t, &c->s, s);
+	return 0;
+}
+
+Ndbtuple*
+_ndbcacheadd(Ndb *db, Ndbs *s, char *attr, char *val, Ndbtuple *t)
+{
+	Ndbcache *c, **l;
+
+	c = mallocz(sizeof *c, 1);
+	if(c == nil)
+		return nil;
+	c->attr = strdup(attr);
+	if(c->attr == nil)
+		goto err;
+	c->val = strdup(val);
+	if(c->val == nil)
+		goto err;
+	c->t = ndbcopy(db, t, s, &c->s);
+	if(c->t == nil && t != nil)
+		goto err;
+
+	/* add to front */
+	c->next = db->cache;
+	db->cache = c;
+
+	/* trim list */
+	if(db->ncache < Maxcached){
+		db->ncache++;
+		return t;
+	}
+	for(l = &db->cache; (*l)->next; l = &(*l)->next)
+		;
+	c = *l;
+	*l = nil;
+err:
+	ndbcachefree(c);
+	return t;
+}
+
+void
+_ndbcacheflush(Ndb *db)
+{
+	Ndbcache *c;
+
+	while(db->cache != nil){
+		c = db->cache;
+		db->cache = c->next;
+		ndbcachefree(c);
+	}
+	db->ncache = 0;
+}
blob - /dev/null
blob + 61ef5e47b117bcc646e1e266b7b523a1731b72ec (mode 644)
--- /dev/null
+++ src/libndb/ndbcat.c
@@ -0,0 +1,18 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ndb.h>
+
+Ndb*
+ndbcat(Ndb *a, Ndb *b)
+{
+	Ndb *db = a;
+
+	if(a == nil)
+		return b;
+	while(a->next != nil)
+		a = a->next;
+	a->next = b;
+	return db;
+}
blob - /dev/null
blob + 3725f181b3ae33cdedcb842ea39564dd8aa0ac2b (mode 644)
--- /dev/null
+++ src/libndb/ndbconcatenate.c
@@ -0,0 +1,18 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+
+/* concatenate two tuples */
+Ndbtuple*
+ndbconcatenate(Ndbtuple *a, Ndbtuple *b)
+{
+	Ndbtuple *t;
+
+	if(a == nil)
+		return b;
+	for(t = a; t->entry; t = t->entry)
+		;
+	t->entry = b;
+	return a;
+}
blob - /dev/null
blob + 9b2fc0c98fc02c3271eb576050048d75563221bf (mode 644)
--- /dev/null
+++ src/libndb/ndbdiscard.c
@@ -0,0 +1,29 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+
+/* remove a from t and free it */
+Ndbtuple*
+ndbdiscard(Ndbtuple *t, Ndbtuple *a)
+{
+	Ndbtuple *nt;
+
+	/* unchain a */
+	for(nt = t; nt != nil; nt = nt->entry){
+		if(nt->line == a)
+			nt->line = a->line;
+		if(nt->entry == a)
+			nt->entry = a->entry;
+	}
+
+	/* a may be start of chain */
+	if(t == a)
+		t = a->entry;
+
+	/* free a */
+	a->entry = nil;
+	ndbfree(a);
+
+	return t;
+}
blob - /dev/null
blob + 647bff03c0c17f7b5bbfcfea492c058f567eae07 (mode 644)
--- /dev/null
+++ src/libndb/ndbfree.c
@@ -0,0 +1,65 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ndb.h>
+#include "ndbhf.h"
+
+/*
+ *  free a parsed entry
+ */
+void
+ndbfree(Ndbtuple *t)
+{
+	Ndbtuple *tn;
+
+	for(; t; t = tn){
+		tn = t->entry;
+		if(t->val != t->valbuf){
+			free(t->val);
+		}
+		free(t);
+	}
+}
+
+/*
+ *  set a value in a tuple
+ */
+void
+ndbsetval(Ndbtuple *t, char *val, int n)
+{
+	if(n < Ndbvlen){
+		if(t->val != t->valbuf){
+			free(t->val);
+			t->val = t->valbuf;
+		}
+	} else {
+		if(t->val != t->valbuf)
+			t->val = realloc(t->val, n+1);
+		else
+			t->val = malloc(n+1);
+		if(t->val == nil)
+			sysfatal("ndbsetval %r");
+	}
+	strncpy(t->val, val, n);
+	t->val[n] = 0;
+}
+
+/*
+ *  allocate a tuple
+ */
+Ndbtuple*
+ndbnew(char *attr, char *val)
+{
+	Ndbtuple *t;
+
+	t = mallocz(sizeof(*t), 1);
+	if(t == nil)
+		sysfatal("ndbnew %r");
+	if(attr != nil)
+		strncpy(t->attr, attr, sizeof(t->attr)-1);
+	t->val = t->valbuf;
+	if(val != nil)
+		ndbsetval(t, val, strlen(val));
+	return t;	
+}
blob - /dev/null
blob + f076cd9c80e4648c564f579afad8c620d0a22a0e (mode 644)
--- /dev/null
+++ src/libndb/ndbgetipaddr.c
@@ -0,0 +1,47 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+
+/* return list of ip addresses for a name */
+Ndbtuple*
+ndbgetipaddr(Ndb *db, char *val)
+{
+	char *attr, *p;
+	Ndbtuple *it, *first, *last, *next;
+	Ndbs s;
+
+	/* already an IP address? */
+	attr = ipattr(val);
+	if(strcmp(attr, "ip") == 0){
+		it = ndbnew("ip", val);
+		return it;
+	}
+
+	/* look it up */
+	p = ndbgetvalue(db, &s, attr, val, "ip", &it);
+	if(p == nil)
+		return nil;
+	free(p);
+
+	/* remove the non-ip entries */
+	first = last = nil;
+	for(; it; it = next){
+		next = it->entry;
+		if(strcmp(it->attr, "ip") == 0){
+			if(first == nil)
+				first = it;
+			else
+				last->entry = it;
+			it->entry = nil;
+			it->line = first;
+			last = it;
+		} else {
+			it->entry = nil;
+			ndbfree(it);
+		}
+	}
+
+	return first;
+}
blob - /dev/null
blob + 2c66b2065b36fb9106a1a2ab41f307c8cfdd3423 (mode 644)
--- /dev/null
+++ src/libndb/ndbgetval.c
@@ -0,0 +1,75 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "ndb.h"
+
+/*
+ *  search for a tuple that has the given 'attr=val' and also 'rattr=x'.
+ *  copy 'x' into 'buf' and return the whole tuple.
+ *
+ *  return 0 if not found.
+ */
+char*
+ndbgetvalue(Ndb *db, Ndbs *s, char *attr, char *val, char *rattr, Ndbtuple **pp)
+{
+	Ndbtuple *t, *nt;
+	char *rv;
+	Ndbs temps;
+
+	if(s == nil)
+		s = &temps;
+	if(pp)
+		*pp = nil;
+	t = ndbsearch(db, s, attr, val);
+	while(t){
+		/* first look on same line (closer binding) */
+		nt = s->t;
+		for(;;){
+			if(strcmp(rattr, nt->attr) == 0){
+				rv = strdup(nt->val);
+				if(pp != nil)
+					*pp = t;
+				else
+					ndbfree(t);
+				return rv;
+			}
+			nt = nt->line;
+			if(nt == s->t)
+				break;
+		}
+		/* search whole tuple */
+		for(nt = t; nt; nt = nt->entry){
+			if(strcmp(rattr, nt->attr) == 0){
+				rv = strdup(nt->val);
+				if(pp != nil)
+					*pp = t;
+				else
+					ndbfree(t);
+				return rv;
+			}
+		}
+		ndbfree(t);
+		t = ndbsnext(s, attr, val);
+	}
+	return nil;
+}
+
+Ndbtuple*
+ndbgetval(Ndb *db, Ndbs *s, char *attr, char *val, char *rattr, char *buf)
+{
+	Ndbtuple *t;
+	char *p;
+
+	p = ndbgetvalue(db, s, attr, val, rattr, &t);
+	if(p == nil){
+		if(buf != nil)
+			*buf = 0;
+	} else {
+		if(buf != nil){
+			strncpy(buf, p, Ndbvlen-1);
+			buf[Ndbvlen-1] = 0;
+		}
+		free(p);
+	}
+	return t;
+}
blob - /dev/null
blob + a6965cdbc8f4b19a0da46e368e6317daf82b3f34 (mode 644)
--- /dev/null
+++ src/libndb/ndbhash.c
@@ -0,0 +1,247 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "ndb.h"
+#include "ndbhf.h"
+
+enum {
+	Dptr,	/* pointer to database file */
+	Cptr,	/* pointer to first chain entry */
+	Cptr1,	/* pointer to second chain entry */
+};
+
+/*
+ *  generate a hash value for an ascii string (val) given
+ *  a hash table length (hlen)
+ */
+ulong
+ndbhash(char *vp, int hlen)
+{
+	ulong hash;
+	uchar *val = (uchar*)vp;
+
+	for(hash = 0; *val; val++)
+		hash = (hash*13) + *val-'a';
+	return hash % hlen;
+}
+
+/*
+ *  read a hash file with buffering
+ */
+static uchar*
+hfread(Ndbhf *hf, long off, int len)
+{
+	if(off < hf->off || off + len > hf->off + hf->len){
+		if(seek(hf->fd, off, 0) < 0
+		|| (hf->len = read(hf->fd, hf->buf, sizeof(hf->buf))) < len){
+			hf->off = -1;
+			return 0;
+		}
+		hf->off = off;
+	}
+	return &hf->buf[off-hf->off];
+}
+
+/*
+ *  return an opened hash file if one exists for the
+ *  attribute and if it is current vis-a-vis the data
+ *  base file
+ */
+static Ndbhf*
+hfopen(Ndb *db, char *attr)
+{
+	Ndbhf *hf;
+	char buf[sizeof(hf->attr)+sizeof(db->file)+2];
+	uchar *p;
+	Dir *d;
+
+	/* try opening the data base if it's closed */
+	if(db->mtime==0 && ndbreopen(db) < 0)
+		return 0;
+
+	/* if the database has changed, throw out hash files and reopen db */
+	if((d = dirfstat(Bfildes(&db->b))) == nil || db->qid.path != d->qid.path
+	|| db->qid.vers != d->qid.vers){
+		if(ndbreopen(db) < 0){
+			free(d);
+			return 0;
+		}
+	}
+	free(d);
+
+	if(db->nohash)
+		return 0;
+
+	/* see if a hash file exists for this attribute */
+	for(hf = db->hf; hf; hf= hf->next){
+		if(strcmp(hf->attr, attr) == 0)
+			return hf;
+	}
+
+	/* create a new one */
+	hf = (Ndbhf*)malloc(sizeof(Ndbhf));
+	if(hf == 0)
+		return 0;
+	memset(hf, 0, sizeof(Ndbhf));
+
+	/* compare it to the database file */
+	strncpy(hf->attr, attr, sizeof(hf->attr)-1);
+	sprint(buf, "%s.%s", db->file, hf->attr);
+	hf->fd = open(buf, OREAD);
+	if(hf->fd >= 0){
+		hf->len = 0;
+		hf->off = 0;
+		p = hfread(hf, 0, 2*NDBULLEN);
+		if(p){
+			hf->dbmtime = NDBGETUL(p);
+			hf->hlen = NDBGETUL(p+NDBULLEN);
+			if(hf->dbmtime == db->mtime){
+				hf->next = db->hf;
+				db->hf = hf;
+				return hf;
+			}
+		}
+		close(hf->fd);
+	}
+
+	free(hf);
+	return 0;
+}
+
+/*
+ *  return the first matching entry
+ */
+Ndbtuple*
+ndbsearch(Ndb *db, Ndbs *s, char *attr, char *val)
+{
+	uchar *p;
+	Ndbtuple *t;
+	Ndbhf *hf;
+
+	hf = hfopen(db, attr);
+
+	memset(s, 0, sizeof(*s));
+	if(_ndbcachesearch(db, s, attr, val, &t) == 0){
+		/* found in cache */
+		if(t != nil)
+			return t;	/* answer from this file */
+		if(db->next == nil)
+			return nil;
+		return ndbsearch(db->next, s, attr, val);
+	}
+
+	s->db = db;
+	s->hf = hf;
+	if(s->hf){
+		s->ptr = ndbhash(val, s->hf->hlen)*NDBPLEN;
+		p = hfread(s->hf, s->ptr+NDBHLEN, NDBPLEN);
+		if(p == 0)
+			return _ndbcacheadd(db, s, attr, val, nil);
+		s->ptr = NDBGETP(p);
+		s->type = Cptr1;
+	} else if(db->length > 128*1024){
+		print("Missing or out of date hash file %s.%s.\n", db->file, attr);
+	/*	syslog(0, "ndb", "Missing or out of date hash file %s.%s.", db->file, attr); */
+
+		/* advance search to next db file */
+		s->ptr = NDBNAP;
+		_ndbcacheadd(db, s, attr, val, nil);
+		if(db->next == 0)
+			return nil;
+		return ndbsearch(db->next, s, attr, val);
+	} else {
+		s->ptr = 0;
+		s->type = Dptr;
+	}
+	t = ndbsnext(s, attr, val);
+	_ndbcacheadd(db, s, attr, val, (t != nil && s->db == db)?t:nil);
+	setmalloctag(t, getcallerpc(&db));
+	return t;
+}
+
+static Ndbtuple*
+match(Ndbtuple *t, char *attr, char *val)
+{
+	Ndbtuple *nt;
+
+	for(nt = t; nt; nt = nt->entry)
+		if(strcmp(attr, nt->attr) == 0
+		&& strcmp(val, nt->val) == 0)
+			return nt;
+	return 0;
+}
+
+/*
+ *  return the next matching entry in the hash chain
+ */
+Ndbtuple*
+ndbsnext(Ndbs *s, char *attr, char *val)
+{
+	Ndbtuple *t;
+	Ndb *db;
+	uchar *p;
+
+	db = s->db;
+	if(s->ptr == NDBNAP)
+		goto nextfile;
+
+	for(;;){
+		if(s->type == Dptr){
+			if(Bseek(&db->b, s->ptr, 0) < 0)
+				break;
+			t = ndbparse(db);
+			s->ptr = Boffset(&db->b);
+			if(t == 0)
+				break;
+			if(s->t = match(t, attr, val))
+				return t;
+			ndbfree(t);
+		} else if(s->type == Cptr){
+			if(Bseek(&db->b, s->ptr, 0) < 0)
+				break; 
+			s->ptr = s->ptr1;
+			s->type = Cptr1;
+			t = ndbparse(db);
+			if(t == 0)
+				break;
+			if(s->t = match(t, attr, val))
+				return t;
+			ndbfree(t);
+		} else if(s->type == Cptr1){
+			if(s->ptr & NDBCHAIN){	/* hash chain continuation */
+				s->ptr &= ~NDBCHAIN;
+				p = hfread(s->hf, s->ptr+NDBHLEN, 2*NDBPLEN);
+				if(p == 0)
+					break;
+				s->ptr = NDBGETP(p);
+				s->ptr1 = NDBGETP(p+NDBPLEN);
+				s->type = Cptr;
+			} else {		/* end of hash chain */
+				if(Bseek(&db->b, s->ptr, 0) < 0)
+					break; 
+				s->ptr = NDBNAP;
+				t = ndbparse(db);
+				if(t == 0)
+					break;
+				if(s->t = match(t, attr, val)){
+					setmalloctag(t, getcallerpc(&s));
+					return t;
+				}
+				ndbfree(t);
+				break;
+			}
+		}
+	}
+
+nextfile:
+
+	/* nothing left to search? */
+	s->ptr = NDBNAP;
+	if(db->next == 0)
+		return 0;
+
+	/* advance search to next db file */
+	t = ndbsearch(db->next, s, attr, val);
+	setmalloctag(t, getcallerpc(&s));
+	return t;
+}
blob - /dev/null
blob + 4505d13bda5ce55c85fc25dc9c7ba6258e0e477b (mode 644)
--- /dev/null
+++ src/libndb/ndbhf.h
@@ -0,0 +1,27 @@
+/* a hash file */
+struct Ndbhf
+{
+	Ndbhf	*next;
+
+	int	fd;
+	ulong	dbmtime;	/* mtime of data base */
+	int	hlen;		/* length (in entries) of hash table */
+	char	attr[Ndbalen];	/* attribute hashed */
+
+	uchar	buf[256];	/* hash file buffer */
+	long	off;		/* offset of first byte of buffer */
+	int	len;		/* length of valid data in buffer */
+};
+
+char*		_ndbparsetuple(char*, Ndbtuple**);
+Ndbtuple*	_ndbparseline(char*);
+
+#define ISWHITE(x) ((x) == ' ' || (x) == '\t' || (x) == '\r')
+#define EATWHITE(x) while(ISWHITE(*(x)))(x)++
+
+extern Ndbtuple *_ndbtfree;
+
+/* caches */
+void	_ndbcacheflush(Ndb *db);
+int	_ndbcachesearch(Ndb *db, Ndbs *s, char *attr, char *val, Ndbtuple **t);
+Ndbtuple* _ndbcacheadd(Ndb *db, Ndbs *s, char *attr, char *val, Ndbtuple *t);
blob - /dev/null
blob + 5cdc24b5f580b00785c6a4f210bab5bd8700fcda (mode 644)
--- /dev/null
+++ src/libndb/ndbipinfo.c
@@ -0,0 +1,242 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+
+enum
+{
+	Ffound=	1<<0,
+	Fignore=1<<1,
+	Faddr=	1<<2,
+};
+
+static Ndbtuple*	filter(Ndb *db, Ndbtuple *t, Ndbtuple *f);
+static Ndbtuple*	mkfilter(int argc, char **argv);
+static int		filtercomplete(Ndbtuple *f);
+static Ndbtuple*	toipaddr(Ndb *db, Ndbtuple *t);
+static int		prefixlen(uchar *ip);
+static Ndbtuple*	subnet(Ndb *db, uchar *net, Ndbtuple *f, int prefix);
+
+/* make a filter to be used in filter */
+static Ndbtuple*
+mkfilter(int argc, char **argv)
+{
+	Ndbtuple *t, *first, *last;
+	char *p;
+
+	last = first = nil;
+	while(argc-- > 0){
+		t = ndbnew(0, 0);
+		if(first)
+			last->entry = t;
+		else
+			first = t;
+		last = t;
+		p = *argv++;
+		if(*p == '@'){
+			t->ptr |= Faddr;
+			p++;
+		}
+		strncpy(t->attr, p, sizeof(t->attr)-1);
+	}
+	return first;
+}
+
+/* return true if every pair of filter has been used */
+static int
+filtercomplete(Ndbtuple *f)
+{
+	for(; f; f = f->entry)
+		if((f->ptr & Fignore) == 0)
+			return 0;
+	return 1;
+}
+
+/* set the attribute of all entries in a tuple */
+static Ndbtuple*
+setattr(Ndbtuple *t, char *attr)
+{
+	Ndbtuple *nt;
+
+	for(nt = t; nt; nt = nt->entry)
+		strcpy(nt->attr, attr);
+	return t;
+}
+
+/*
+ *  return only the attr/value pairs in t maching the filter, f.
+ *  others are freed.  line structure is preserved.
+ */
+static Ndbtuple*
+filter(Ndb *db, Ndbtuple *t, Ndbtuple *f)
+{
+	Ndbtuple *nt, *nf, *next;
+
+	/* filter out what we don't want */
+	for(nt = t; nt; nt = next){
+		next = nt->entry;
+
+		/* look through filter */
+		for(nf = f; nf != nil; nf = nf->entry){
+			if(!(nf->ptr&Fignore) && strcmp(nt->attr, nf->attr) == 0)
+				break;
+		}
+		if(nf == nil){
+			/* remove nt from t */
+			t = ndbdiscard(t, nt);
+		} else {
+			if(nf->ptr & Faddr)
+				t = ndbsubstitute(t, nt, setattr(ndbgetipaddr(db, nt->val), nt->attr));
+			nf->ptr |= Ffound;
+		}
+	}
+
+	/* remember filter etnries that matched */
+	for(nf = f; nf != nil; nf = nf->entry)
+		if(nf->ptr & Ffound)
+			nf->ptr = (nf->ptr & ~Ffound) | Fignore;
+
+	return t;
+}
+
+static int
+prefixlen(uchar *ip)
+{
+	int y, i;
+
+	for(y = IPaddrlen-1; y >= 0; y--)
+		for(i = 8; i > 0; i--)
+			if(ip[y] & (1<<(8-i)))
+				return y*8 + i;
+	return 0;
+}
+
+/*
+ *  look through a containing subset
+ */
+static Ndbtuple*
+subnet(Ndb *db, uchar *net, Ndbtuple *f, int prefix)
+{
+	Ndbs s;
+	Ndbtuple *t, *nt, *xt;
+	char netstr[128];
+	uchar mask[IPaddrlen];
+	int masklen;
+
+	t = nil;
+	sprint(netstr, "%I", net);
+	nt = ndbsearch(db, &s, "ip", netstr);
+	while(nt != nil){
+		xt = ndbfindattr(nt, nt, "ipnet");
+		if(xt){
+			xt = ndbfindattr(nt, nt, "ipmask");
+			if(xt)
+				parseipmask(mask, xt->val);
+			else
+				ipmove(mask, defmask(net));
+			masklen = prefixlen(mask);
+			if(masklen <= prefix)
+				t = ndbconcatenate(t, filter(db, nt, f));
+		} else
+			ndbfree(nt);
+		nt = ndbsnext(&s, "ip", netstr);
+	}
+	return t;
+}
+
+/*
+ *  fill in all the requested attributes for a system.
+ *  if the system's entry doesn't have all required,
+ *  walk through successively more inclusive networks
+ *  for inherited attributes.
+ */
+Ndbtuple*
+ndbipinfo(Ndb *db, char *attr, char *val, char **alist, int n)
+{
+	Ndbtuple *t, *nt, *f;
+	Ndbs s;
+	char *ipstr;
+	uchar net[IPaddrlen];
+	uchar ip[IPaddrlen];
+	int prefix, smallestprefix;
+	int force;
+
+	/* just in case */
+	fmtinstall('I', eipfmt);
+	fmtinstall('M', eipfmt);
+
+	/* get needed attributes */
+	f = mkfilter(n, alist);
+
+	/*
+	 *  first look for a matching entry with an ip address
+	 */
+	t = nil;
+	ipstr = ndbgetvalue(db, &s, attr, val, "ip", &nt);
+	if(ipstr == nil){
+		/* none found, make one up */
+		if(strcmp(attr, "ip") != 0)
+			return nil;
+		t = ndbnew("ip", val);
+		t->line = t;
+		t->entry = nil;
+		parseip(net, val);
+	} else {
+		/* found one */
+		while(nt != nil){
+			nt = ndbreorder(nt, s.t);
+			t = ndbconcatenate(t, nt);
+			nt = ndbsnext(&s, attr, val);
+		}
+		parseip(net, ipstr);
+		free(ipstr);
+	}
+	ipmove(ip, net);
+	t = filter(db, t, f);
+
+	/*
+	 *  now go through subnets to fill in any missing attributes
+	 */
+	if(isv4(net)){
+		prefix = 127;
+		smallestprefix = 100;
+		force = 0;
+	} else {
+		/* in v6, the last 8 bytes have no structure (we hope) */
+		prefix = 64;
+		smallestprefix = 2;
+		memset(net+8, 0, 8);
+		force = 1;
+	}
+
+	/*
+	 *  to find a containing network, keep turning off
+	 *  the lower bit and look for a network with
+	 *  that address and a shorter mask.  tedius but
+	 *  complete, we may need to find a trick to speed this up.
+	 */
+	for(; prefix >= smallestprefix; prefix--){
+		if(filtercomplete(f))
+			break;
+		if(!force && (net[prefix/8] & (1<<(7-(prefix%8)))) == 0)
+			continue;
+		force = 0;
+		net[prefix/8] &= ~(1<<(7-(prefix%8)));
+		t = ndbconcatenate(t, subnet(db, net, f, prefix));
+	}
+
+	/*
+	 *  if there's an unfulfilled ipmask, make one up
+	 */
+	nt = ndbfindattr(f, f, "ipmask");
+	if(nt && !(nt->ptr & Fignore)){
+		char x[64];
+
+		snprint(x, sizeof(x), "%M", defmask(ip));
+		t = ndbconcatenate(t, ndbnew("ipmask", x));
+	}
+
+	ndbfree(f);
+	return t;
+}
blob - /dev/null
blob + 012bc4b2b81db242b24a3fa130ed84a6e2d57b4a (mode 644)
--- /dev/null
+++ src/libndb/ndblookval.c
@@ -0,0 +1,44 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ip.h>
+#include <ndb.h>
+
+/*
+ *  Look for a pair with the given attribute.  look first on the same line,
+ *  then in the whole entry.
+ */
+Ndbtuple*
+ndbfindattr(Ndbtuple *entry, Ndbtuple *line, char *attr)
+{
+	Ndbtuple *nt;
+
+	/* first look on same line (closer binding) */
+	for(nt = line; nt;){
+		if(strcmp(attr, nt->attr) == 0)
+			return nt;
+		nt = nt->line;
+		if(nt == line)
+			break;
+	}
+
+	/* search whole tuple */
+	for(nt = entry; nt; nt = nt->entry)
+		if(strcmp(attr, nt->attr) == 0)
+			return nt;
+
+	return nil;
+}
+
+Ndbtuple*
+ndblookval(Ndbtuple *entry, Ndbtuple *line, char *attr, char *to)
+{
+	Ndbtuple *t;
+
+	t = ndbfindattr(entry, line, attr);
+	if(t != nil){
+		strncpy(to, t->val, Ndbvlen-1);
+		to[Ndbvlen-1] = 0;
+	}
+	return t;
+}
blob - /dev/null
blob + d504702ee99ebd6b6ed6f1ea3ef35c0b8891092b (mode 644)
--- /dev/null
+++ src/libndb/ndbopen.c
@@ -0,0 +1,174 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ndb.h>
+#include "ndbhf.h"
+
+static Ndb*	doopen(char*);
+static void	hffree(Ndb*);
+
+static char *deffile = "/lib/ndb/local";
+
+/*
+ *  the database entry in 'file' indicates the list of files
+ *  that makeup the database.  Open each one and search in
+ *  the same order.
+ */
+Ndb*
+ndbopen(char *file)
+{
+	Ndb *db, *first, *last;
+	Ndbs s;
+	Ndbtuple *t, *nt;
+
+	if(file == 0)
+		file = deffile;
+	db = doopen(file);
+	if(db == 0)
+		return 0;
+	first = last = db;
+	t = ndbsearch(db, &s, "database", "");
+	Bseek(&db->b, 0, 0);
+	if(t == 0)
+		return db;
+	for(nt = t; nt; nt = nt->entry){
+		if(strcmp(nt->attr, "file") != 0)
+			continue;
+		if(strcmp(nt->val, file) == 0){
+			/* default file can be reordered in the list */
+			if(first->next == 0)
+				continue;
+			if(strcmp(first->file, file) == 0){
+				db = first;
+				first = first->next;
+				last->next = db;
+				db->next = 0;
+				last = db;
+			}
+			continue;
+		}
+		db = doopen(nt->val);
+		if(db == 0)
+			continue;
+		last->next = db;
+		last = db;
+	}
+	ndbfree(t);
+	return first;
+}
+
+/*
+ *  open a single file
+ */
+static Ndb*
+doopen(char *file)
+{
+	Ndb *db;
+
+	db = (Ndb*)malloc(sizeof(Ndb));
+	if(db == 0)
+		return 0;
+	memset(db, 0, sizeof(Ndb));
+	strncpy(db->file, file, sizeof(db->file)-1);
+
+	if(ndbreopen(db) < 0){
+		free(db);
+		return 0;
+	}
+
+	return db;
+}
+
+/*
+ *  dump any cached information, forget the hash tables, and reopen a single file
+ */
+int
+ndbreopen(Ndb *db)
+{
+	int fd;
+	Dir *d;
+
+	/* forget what we know about the open files */
+	if(db->mtime){
+		_ndbcacheflush(db);
+		hffree(db);
+		close(Bfildes(&db->b));
+		Bterm(&db->b);
+		db->mtime = 0;
+	}
+
+	/* try the open again */
+	fd = open(db->file, OREAD);
+	if(fd < 0)
+		return -1;
+	d = dirfstat(fd);
+	if(d == nil){
+		close(fd);
+		return -1;
+	}
+
+	db->qid = d->qid;
+	db->mtime = d->mtime;
+	db->length = d->length;
+	Binit(&db->b, fd, OREAD);
+	free(d);
+	return 0;
+}
+
+/*
+ *  close the database files
+ */
+void
+ndbclose(Ndb *db)
+{
+	Ndb *nextdb;
+
+	for(; db; db = nextdb){
+		nextdb = db->next;
+		_ndbcacheflush(db);
+		hffree(db);
+		close(Bfildes(&db->b));
+		Bterm(&db->b);
+		free(db);
+	}
+}
+
+/*
+ *  free the hash files belonging to a db
+ */
+static void
+hffree(Ndb *db)
+{
+	Ndbhf *hf, *next;
+
+	for(hf = db->hf; hf; hf = next){
+		next = hf->next;
+		close(hf->fd);
+		free(hf);
+	}
+	db->hf = 0;
+}
+
+/*
+ *  return true if any part of the database has changed
+ */
+int
+ndbchanged(Ndb *db)
+{
+	Ndb *ndb;
+	Dir *d;
+
+	for(ndb = db; ndb != nil; ndb = ndb->next){
+		d = dirfstat(Bfildes(&db->b));
+		if(d == nil)
+			continue;
+		if(ndb->qid.path != d->qid.path
+		|| ndb->qid.vers != d->qid.vers){
+			free(d);
+			return 1;
+		}
+		free(d);
+	}
+	return 0;
+}
blob - /dev/null
blob + 86a108dce3598c71fb005032fb21228c4166c52b (mode 644)
--- /dev/null
+++ src/libndb/ndbparse.c
@@ -0,0 +1,57 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ndb.h>
+#include "ndbhf.h"
+
+/*
+ *  Parse a data base entry.  Entries may span multiple
+ *  lines.  An entry starts on a left margin.  All subsequent
+ *  lines must be indented by white space.  An entry consists
+ *  of tuples of the forms:
+ *	attribute-name
+ *	attribute-name=value
+ *	attribute-name="value with white space"
+ *
+ *  The parsing returns a 2-dimensional structure.  The first
+ *  dimension joins all tuples. All tuples on the same line
+ *  form a ring along the second dimension.
+ */
+
+/*
+ *  parse the next entry in the file
+ */
+Ndbtuple*
+ndbparse(Ndb *db)
+{
+	char *line;
+	Ndbtuple *t;
+	Ndbtuple *first, *last;
+	int len;
+
+	first = last = 0;
+	for(;;){
+		if((line = Brdline(&db->b, '\n')) == 0)
+			break;
+		len = Blinelen(&db->b);
+		if(line[len-1] != '\n')
+			break;
+		if(first && !ISWHITE(*line) && *line != '#'){
+			Bseek(&db->b, -len, 1);
+			break;
+		}
+		t = _ndbparseline(line);
+		if(t == 0)
+			continue;
+		if(first)
+			last->entry = t;
+		else
+			first = t;
+		last = t;
+		while(last->entry)
+			last = last->entry;
+	}
+	setmalloctag(first, getcallerpc(&db));
+	return first;
+}
blob - /dev/null
blob + 167d0a0acd48418b21fecfb5d774d8040c603de8 (mode 644)
--- /dev/null
+++ src/libndb/ndbreorder.c
@@ -0,0 +1,53 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+
+/*
+ *  reorder the tuple to put x's line first in the entry and x fitst in its line
+ */
+Ndbtuple*
+ndbreorder(Ndbtuple *t, Ndbtuple *x)
+{
+	Ndbtuple *nt;
+	Ndbtuple *last, *prev;
+
+	/* if x is first, we're done */
+	if(x == t)
+		return t;
+
+	/* find end of x's line */
+	for(last = x; last->line == last->entry; last = last->line)
+		;
+
+	/* rotate to make this line first */
+	if(last->line != t){
+
+		/* detach this line and everything after it from the entry */
+		for(nt = t; nt->entry != last->line; nt = nt->entry)
+			;
+		nt->entry = nil;
+	
+		/* switch */
+		for(nt = last; nt->entry != nil; nt = nt->entry)
+			;
+		nt->entry = t;
+	}
+
+	/* rotate line to make x first */
+	if(x != last->line){
+
+		/* find entry before x */
+		for(prev = last; prev->line != x; prev = prev->line);
+			;
+
+		/* detach line */
+		nt = last->entry;
+		last->entry = last->line;
+
+		/* reattach */
+		prev->entry = nt;
+	}
+
+	return x;
+}
blob - /dev/null
blob + 46f6371231b82517dd646b7cd3c4a56ecc3016a1 (mode 644)
--- /dev/null
+++ src/libndb/ndbsubstitute.c
@@ -0,0 +1,39 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+
+/* replace a in t with b, the line structure in b is lost, c'est la vie */
+Ndbtuple*
+ndbsubstitute(Ndbtuple *t, Ndbtuple *a, Ndbtuple *b)
+{
+	Ndbtuple *nt;
+
+	if(a == b)
+		return t;
+	if(b == nil)
+		return ndbdiscard(t, a);
+
+	/* all pointers to a become pointers to b */
+	for(nt = t; nt != nil; nt = nt->entry){
+		if(nt->line == a)
+			nt->line = b;
+		if(nt->entry == a)
+			nt->entry = b;
+	}
+
+	/* end of b chain points to a's successors */
+	for(nt = b; nt->entry; nt = nt->entry){
+		nt->line = nt->entry;
+	}
+	nt->line = a->line;
+	nt->entry = a->entry;
+
+	a->entry = nil;
+	ndbfree(a);
+
+	if(a == t)
+		return b;
+	else
+		return t;
+}