new
diff --git a/src/libndb/ndbopen.c b/src/libndb/ndbopen.c
new file mode 100644
index 0000000..d504702
--- /dev/null
+++ b/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;
+}