tnew - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit d957951b75df08a9bb0293e3e13ff87759afbb92
 (DIR) parent ad017cfbf5530cfc3ae2fafd723cdade2a4405f6
 (HTM) Author: rsc <devnull@localhost>
       Date:   Fri, 11 Feb 2005 19:41:16 +0000
       
       new
       
       Diffstat:
         A src/cmd/ndb/mkfile                  |      13 +++++++++++++
         A src/cmd/ndb/ndbipquery.c            |      54 +++++++++++++++++++++++++++++++
         A src/cmd/ndb/ndbmkdb.c               |     203 +++++++++++++++++++++++++++++++
         A src/cmd/ndb/ndbmkhash.c             |     155 +++++++++++++++++++++++++++++++
         A src/cmd/ndb/ndbmkhosts.c            |     233 +++++++++++++++++++++++++++++++
         A src/cmd/ndb/ndbquery.c              |      81 ++++++++++++++++++++++++++++++
         A src/libndb/csgetval.c               |     107 +++++++++++++++++++++++++++++++
         A src/libndb/csipinfo.c               |      68 +++++++++++++++++++++++++++++++
         A src/libndb/dnsquery.c               |     156 +++++++++++++++++++++++++++++++
         A src/libndb/ipattr.c                 |      46 +++++++++++++++++++++++++++++++
         A src/libndb/mkfile                   |      32 +++++++++++++++++++++++++++++++
         A src/libndb/ndbaux.c                 |      94 +++++++++++++++++++++++++++++++
         A src/libndb/ndbcache.c               |     144 +++++++++++++++++++++++++++++++
         A src/libndb/ndbcat.c                 |      18 ++++++++++++++++++
         A src/libndb/ndbconcatenate.c         |      18 ++++++++++++++++++
         A src/libndb/ndbdiscard.c             |      29 +++++++++++++++++++++++++++++
         A src/libndb/ndbfree.c                |      65 +++++++++++++++++++++++++++++++
         A src/libndb/ndbgetipaddr.c           |      47 +++++++++++++++++++++++++++++++
         A src/libndb/ndbgetval.c              |      75 +++++++++++++++++++++++++++++++
         A src/libndb/ndbhash.c                |     247 +++++++++++++++++++++++++++++++
         A src/libndb/ndbhf.h                  |      27 +++++++++++++++++++++++++++
         A src/libndb/ndbipinfo.c              |     242 +++++++++++++++++++++++++++++++
         A src/libndb/ndblookval.c             |      44 +++++++++++++++++++++++++++++++
         A src/libndb/ndbopen.c                |     174 +++++++++++++++++++++++++++++++
         A src/libndb/ndbparse.c               |      57 +++++++++++++++++++++++++++++++
         A src/libndb/ndbreorder.c             |      53 ++++++++++++++++++++++++++++++
         A src/libndb/ndbsubstitute.c          |      39 +++++++++++++++++++++++++++++++
       
       27 files changed, 2521 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/src/cmd/ndb/mkfile b/src/cmd/ndb/mkfile
       t@@ -0,0 +1,13 @@
       +<$PLAN9/src/mkhdr
       +
       +TARG=\
       +        ndbmkdb\
       +        ndbquery\
       +        ndbmkhash\
       +        ndbmkhosts\
       +        ndbipquery\
       +
       +LIB=$PLAN9/lib/libndb.a
       +
       +<$PLAN9/src/mkmany
       +
 (DIR) diff --git a/src/cmd/ndb/ndbipquery.c b/src/cmd/ndb/ndbipquery.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/ndb/ndbmkdb.c b/src/cmd/ndb/ndbmkdb.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/ndb/ndbmkhash.c b/src/cmd/ndb/ndbmkhash.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/ndb/ndbmkhosts.c b/src/cmd/ndb/ndbmkhosts.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/ndb/ndbquery.c b/src/cmd/ndb/ndbquery.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libndb/csgetval.c b/src/libndb/csgetval.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/csipinfo.c b/src/libndb/csipinfo.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/dnsquery.c b/src/libndb/dnsquery.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ipattr.c b/src/libndb/ipattr.c
       t@@ -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";
       +}
 (DIR) diff --git a/src/libndb/mkfile b/src/libndb/mkfile
       t@@ -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
 (DIR) diff --git a/src/libndb/ndbaux.c b/src/libndb/ndbaux.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ndbcache.c b/src/libndb/ndbcache.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ndbcat.c b/src/libndb/ndbcat.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ndbconcatenate.c b/src/libndb/ndbconcatenate.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ndbdiscard.c b/src/libndb/ndbdiscard.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ndbfree.c b/src/libndb/ndbfree.c
       t@@ -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;        
       +}
 (DIR) diff --git a/src/libndb/ndbgetipaddr.c b/src/libndb/ndbgetipaddr.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ndbgetval.c b/src/libndb/ndbgetval.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ndbhash.c b/src/libndb/ndbhash.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ndbhf.h b/src/libndb/ndbhf.h
       t@@ -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);
 (DIR) diff --git a/src/libndb/ndbipinfo.c b/src/libndb/ndbipinfo.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ndblookval.c b/src/libndb/ndblookval.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ndbopen.c b/src/libndb/ndbopen.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ndbparse.c b/src/libndb/ndbparse.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ndbreorder.c b/src/libndb/ndbreorder.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libndb/ndbsubstitute.c b/src/libndb/ndbsubstitute.c
       t@@ -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;
       +}