diff options
Diffstat (limited to 'appl/cmd/ndb')
| -rw-r--r-- | appl/cmd/ndb/cs.b | 676 | ||||
| -rw-r--r-- | appl/cmd/ndb/csquery.b | 97 | ||||
| -rw-r--r-- | appl/cmd/ndb/dns.b | 1860 | ||||
| -rw-r--r-- | appl/cmd/ndb/dnsquery.b | 177 | ||||
| -rw-r--r-- | appl/cmd/ndb/mkfile | 28 | ||||
| -rw-r--r-- | appl/cmd/ndb/mkhash.b | 119 | ||||
| -rw-r--r-- | appl/cmd/ndb/query.b | 135 | ||||
| -rw-r--r-- | appl/cmd/ndb/registry.b | 671 | ||||
| -rw-r--r-- | appl/cmd/ndb/regquery.b | 104 |
9 files changed, 3867 insertions, 0 deletions
diff --git a/appl/cmd/ndb/cs.b b/appl/cmd/ndb/cs.b new file mode 100644 index 00000000..1506e9ba --- /dev/null +++ b/appl/cmd/ndb/cs.b @@ -0,0 +1,676 @@ +implement Cs; + +# +# Connection server translates net!machine!service into +# /net/tcp/clone 135.104.9.53!564 +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "srv.m"; + srv: Srv; + +include "bufio.m"; +include "attrdb.m"; + attrdb: Attrdb; + Attr, Db, Dbentry, Tuples: import attrdb; + +include "ip.m"; + ip: IP; +include "ipattr.m"; + ipattr: IPattr; + +include "arg.m"; + +Cs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +# signature of dial-on-demand module +CSdial: module +{ + init: fn(nil: ref Draw->Context): string; + connect: fn(): string; +}; + +Reply: adt +{ + fid: int; + pid: int; + addrs: list of string; + err: string; +}; + +Cached: adt +{ + expire: int; + query: string; + addrs: list of string; +}; + +Ncache: con 16; +cache:= array[Ncache] of ref Cached; +nextcache := 0; + +rlist: list of ref Reply; + +ndbfile := "/lib/ndb/local"; +ndb: ref Db; +mntpt := "/net"; +myname: string; + +stderr: ref Sys->FD; + +verbose := 0; +dialmod: CSdial; + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + attrdb = load Attrdb Attrdb->PATH; + if(attrdb == nil) + cantload(Attrdb->PATH); + attrdb->init(); + ip = load IP IP->PATH; + if(ip == nil) + cantload(IP->PATH); + ip->init(); + ipattr = load IPattr IPattr->PATH; + if(ipattr == nil) + cantload(IPattr->PATH); + ipattr->init(attrdb, ip); + + svcname := "#scs"; + arg := load Arg Arg->PATH; + if (arg == nil) + cantload(Arg->PATH); + arg->init(args); + arg->setusage("cs [-v] [-x mntpt] [-f database] [-d dialmod]"); + while((c := arg->opt()) != 0) + case c { + 'v' or 'D' => + verbose++; + 'd' => # undocumented hack to replace svc/cs/cs + f := arg->arg(); + if(f != nil){ + dialmod = load CSdial f; + if(dialmod == nil) + cantload(f); + } + 'f' => + ndbfile = arg->earg(); + 'x' => + mntpt = arg->earg(); + svcname = "#scs"+svcpt(mntpt); + * => + arg->usage(); + } + + if(arg->argv() != nil) + arg->usage(); + arg = nil; + + srv = load Srv Srv->PATH; # hosted Inferno only + if(srv != nil) + srv->init(); + + sys->remove(svcname+"/cs"); + sys->unmount(svcname, mntpt); + publish(svcname); + if(sys->bind(svcname, mntpt, Sys->MBEFORE) < 0) + error(sys->sprint("can't bind #s on %s: %r", mntpt)); + file := sys->file2chan(mntpt, "cs"); + if(file == nil) + error(sys->sprint("can't make %s/cs: %r", mntpt)); + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + refresh(); + if(dialmod != nil){ + e := dialmod->init(ctxt); + if(e != nil) + error(sys->sprint("can't initialise dial-on-demand: %s", e)); + } + spawn cs(file); +} + +svcpt(s: string): string +{ + for(i:=0; i<len s; i++) + if(s[i] == '/') + s[i] = '_'; + return s; +} + +publish(dir: string) +{ + d := Sys->nulldir; + d.mode = 8r777; + if(sys->wstat(dir, d) < 0) + sys->fprint(sys->fildes(2), "cs: can't publish %s: %r\n", dir); +} + +cantload(m: string) +{ + error(sys->sprint("cannot load %s: %r", m)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "cs: %s\n", s); + raise "fail:error"; +} + +refresh() +{ + myname = sysname(); + if(ndb == nil){ + ndb2 := Db.open(ndbfile); + if(ndb2 == nil){ + err := sys->sprint("%r"); + ndb2 = Db.open("/lib/ndb/inferno"); # try to get service map at least + if(ndb2 == nil) + sys->fprint(sys->fildes(2), "cs: warning: can't open %s: %s\n", ndbfile, err); # continue without it + } + ndb = Db.open(mntpt+"/ndb"); + if(ndb != nil) + ndb = ndb.append(ndb2); + else + ndb = ndb2; + }else + ndb.reopen(); +} + +sysname(): string +{ + t := rf("/dev/sysname"); + if(t != nil) + return t; + t = rf("#e/sysname"); + if(t == nil){ + s := rf(mntpt+"/ndb"); + if(s != nil){ + db := Db.sopen(t); + if(db != nil){ + (e, nil) := db.find(nil, "sys"); + if(e != nil) + t = e.findfirst("sys"); + } + } + } + if(t != nil){ + fd := sys->open("/dev/sysname", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "%s", t); + } + return t; +} + +rf(name: string): string +{ + fd := sys->open(name, Sys->OREAD); + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0) + return nil; + return string buf[0:n]; +} + +cs(file: ref Sys->FileIO) +{ + pidc := chan of int; + donec := chan of ref Reply; + for (;;) { + alt { + (nil, buf, fid, wc) := <-file.write => + cleanfid(fid); # each write cancels previous requests + if(dialmod != nil){ + e := dialmod->connect(); + if(e != nil){ + if(len e > 5 && e[0:5]=="fail:") + e = e[5:]; + if(e == "") + e = "unknown error"; + wc <-= (0, "cs: dial on demand: "+e); + break; + } + } + if(wc != nil){ + nbytes := len buf; + query := string buf; + if(query == "refresh"){ + refresh(); + wc <-= (nbytes, nil); + break; + } + now := time(); + r := ref Reply; + r.fid = fid; + spawn request(r, query, nbytes, now, wc, pidc, donec); + r.pid = <-pidc; + rlist = r :: rlist; + } + + (off, nbytes, fid, rc) := <-file.read => + if(rc != nil){ + r := findfid(fid); + if(r != nil) + reply(r, off, nbytes, rc); + else + rc <-= (nil, "unknown request"); + } else + ; # cleanfid(fid); # compensate for csendq in file2chan + + r := <-donec => + r.pid = 0; + } + } +} + +findfid(fid: int): ref Reply +{ + for(rl := rlist; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid == fid) + return r; + } + return nil; +} + +cleanfid(fid: int) +{ + rl := rlist; + rlist = nil; + for(; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid != fid) + rlist = r :: rlist; + else + killgrp(r.pid); + } +} + +killgrp(pid: int) +{ + if(pid != 0){ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "killgrp") < 0) + sys->fprint(stderr, "cs: can't killgrp %d: %r\n", pid); + } +} + +request(r: ref Reply, query: string, nbytes: int, now: int, wc: chan of (int, string), pidc: chan of int, donec: chan of ref Reply) +{ + pidc <-= sys->pctl(Sys->NEWPGRP, nil); + if(query != nil && query[0] == '!'){ + # general query + (r.addrs, r.err) = genquery(query[1:]); + }else{ + (r.addrs, r.err) = xlate(query, now); + if(r.addrs == nil && r.err == nil) + r.err = "cs: can't translate address"; + } + if(r.err != nil){ + if(verbose) + sys->fprint(stderr, "cs: %s: %s\n", query, r.err); + wc <-= (0, r.err); + } else + wc <-= (nbytes, nil); + donec <-= r; +} + +reply(r: ref Reply, off: int, nbytes: int, rc: chan of (array of byte, string)) +{ + if(r.err != nil){ + rc <-= (nil, r.err); + return; + } + addr: string = nil; + if(r.addrs != nil){ + addr = hd r.addrs; + r.addrs = tl r.addrs; + } + off = 0; # this version ignores offset + rc <-= reads(addr, off, nbytes); +} + +# +# return the file2chan reply for a read of the given string +# +reads(str: string, off, nbytes: int): (array of byte, string) +{ + bstr := array of byte str; + slen := len bstr; + if(off < 0 || off >= slen) + return (nil, nil); + if(off + nbytes > slen) + nbytes = slen - off; + if(nbytes <= 0) + return (nil, nil); + return (bstr[off:off+nbytes], nil); +} + +lookcache(query: string, now: int): ref Cached +{ + for(i:=0; i<len cache; i++){ + c := cache[i]; + if(c != nil && c.query == query && now < c.expire){ + if(verbose) + sys->print("cache: %s -> %s\n", query, hd c.addrs); + return c; + } + } + return nil; +} + +putcache(query: string, addrs: list of string, now: int) +{ + ce := ref Cached; + ce.expire = now+120; + ce.query = query; + ce.addrs = addrs; + cache[nextcache] = ce; + nextcache = (nextcache+1)%Ncache; +} + +xlate(address: string, now: int): (list of string, string) +{ + n: int; + l, rl, results: list of string; + repl, netw, mach, service: string; + + ce := lookcache(address, now); + if(ce != nil && ce.addrs != nil) + return (ce.addrs, nil); + + (n, l) = sys->tokenize(address, "!\n"); + if(n < 2) + return (nil, "bad format request"); + + netw = hd l; + if(netw == "net") + netw = "tcp"; # TO DO: better (needs lib/ndb) + if(!isnetwork(netw)) + return (nil, "network unavailable "+netw); + l = tl l; + + if(!isipnet(netw)) { + repl = mntpt + "/" + netw + "/clone "; + for(;;){ + repl += hd l; + if((l = tl l) == nil) + break; + repl += "!"; + } + return (repl :: nil, nil); # no need to cache + } + + if(n != 3) + return (nil, "bad format request"); + mach = hd l; + service = hd tl l; + + if(!isnumeric(service)) { + s := xlatesvc(netw, service); + if(s == nil){ + if(srv != nil) + s = srv->ipn2p(netw, service); + if(s == nil) + return (nil, "cs: can't translate service"); + } + service = s; + } + + attr := ipattr->dbattr(mach); + if(mach == "*") + l = "" :: nil; + else if(attr != "ip") { + # Symbolic server == "$SVC" + if(mach[0] == '$' && len mach > 1 && ndb != nil){ + (s, nil) := ipattr->findnetattr(ndb, "sys", myname, mach[1:]); + if(s == nil){ + names := dblook("infernosite", "", mach[1:]); + if(names == nil) + return (nil, "cs: can't translate "+mach); + s = hd names; + } + mach = s; + attr = ipattr->dbattr(mach); + } + if(attr == "sys"){ + results = dblook("sys", mach, "ip"); + if(results != nil) + attr = "ip"; + } + if(attr != "ip"){ + err: string; + (results, err) = querydns(mach, "ip"); + if(err != nil) + return (nil, err); + }else if(results == nil) + results = mach :: nil; + l = results; + if(l == nil){ + if(srv != nil) + l = srv->iph2a(mach); + if(l == nil) + return (nil, "cs: unknown host"); + } + } else + l = mach :: nil; + + while(l != nil) { + s := hd l; + l = tl l; + if(s != "") + s[len s] = '!'; + s += service; + + repl = mntpt+"/"+netw+"/clone "+s; + if(verbose) + sys->fprint(stderr, "cs: %s!%s!%s -> %s\n", netw, mach, service, repl); + + rl = repl :: rl; + } + rl = reverse(rl); + putcache(address, rl, now); + return (rl, nil); +} + +querydns(name: string, rtype: string): (list of string, string) +{ + fd := sys->open(mntpt+"/dns", Sys->ORDWR); + if(fd == nil) + return (nil, nil); + if(sys->fprint(fd, "%s %s", name, rtype) < 0) + return (nil, sys->sprint("%r")); + rl: list of string; + buf := array[256] of byte; + sys->seek(fd, big 0, 0); + while((n := sys->read(fd, buf, len buf)) > 0){ + # name rtype value + (nf, fld) := sys->tokenize(string buf[0:n], " \t"); + if(nf != 3){ + sys->fprint(stderr, "cs: odd result from dns: %s\n", string buf[0:n]); + continue; + } + rl = hd tl tl fld :: rl; + } + return (reverse(rl), nil); +} + +dblook(attr: string, val: string, rattr: string): list of string +{ + rl: list of string; + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + (e, ptr) = ndb.findbyattr(ptr, attr, val, rattr); + if(e == nil) + break; + for(l := e.findbyattr(attr, val, rattr); l != nil; l = tl l){ + (nil, al) := hd l; + for(; al != nil; al = tl al) + if(!inlist((hd al).val, rl)) + rl = (hd al).val :: rl; + } + } + return reverse(rl); +} + +inlist(s: string, l: list of string): int +{ + for(; l != nil; l = tl l) + if(hd l == s) + return 1; + return 0; +} + +reverse(l: list of string): list of string +{ + t: list of string; + for(; l != nil; l = tl l) + t = hd l :: t; + return t; +} + +isnumeric(a: string): int +{ + i, c: int; + + for(i = 0; i < len a; i++) { + c = a[i]; + if(c < '0' || c > '9') + return 0; + } + return 1; +} + +nets: list of string; + +isnetwork(s: string) : int +{ + if(find(s, nets)) + return 1; + (ok, nil) := sys->stat(mntpt+"/"+s+"/clone"); + if(ok >= 0) { + nets = s :: nets; + return 1; + } + return 0; +} + +find(e: string, l: list of string) : int +{ + for(; l != nil; l = tl l) + if (e == hd l) + return 1; + return 0; +} + +isipnet(s: string) : int +{ + return s == "net" || s == "tcp" || s == "udp" || s == "il"; +} + +xlatesvc(proto: string, s: string): string +{ + if(ndb == nil || s == nil || isnumeric(s)) + return s; + (e, nil) := ndb.findbyattr(nil, proto, s, "port"); + if(e == nil) + return nil; + matches := e.findbyattr(proto, s, "port"); + if(matches == nil) + return nil; + (ts, al) := hd matches; + restricted := ""; + if(ts.hasattr("restricted")) + restricted = "!r"; + if(verbose > 1) + sys->print("%s=%q port=%s%s\n", proto, s, (hd al).val, restricted); + return (hd al).val+restricted; +} + +time(): int +{ + timefd := sys->open("/dev/time", Sys->OREAD); + if(timefd == nil) + return 0; + buf := array[128] of byte; + sys->seek(timefd, big 0, 0); + n := sys->read(timefd, buf, len buf); + if(n < 0) + return 0; + return int ((big string buf[0:n]) / big 1000000); +} + +# +# general query: attr1=val1 attr2=val2 ... finds matching tuple(s) +# where attr1 is the key and val1 can't be * +# +genquery(query: string): (list of string, string) +{ + (tups, err) := attrdb->parseline(query, 0); + if(err != nil) + return (nil, "bad query: "+err); + if(tups == nil) + return (nil, "bad query"); + pairs := tups.pairs; + a0 := (hd pairs).attr; + if(a0 == "ipinfo") + return (nil, "ipinfo not yet supported"); + v0 := (hd pairs).val; + + # if((a0 == "dom" || a0 == "ip") && v0 != nil){ + # query dns ... + # } + + ptr: ref Attrdb->Dbptr; + e: ref Dbentry; + for(;;){ + (e, ptr) = ndb.findpair(ptr, a0, v0); + if(e == nil) + break; + for(l := e.lines; l != nil; l = tl l) + if(qmatch(hd l, tl pairs)){ + ls: list of string; + for(l = e.lines; l != nil; l = tl l) + ls = tuptext(hd l) :: ls; + return (reverse(ls), nil); + } + } + return (nil, "no match"); +} + +# +# see if set of tuples t contains every non-* attr/val pair +# +qmatch(t: ref Tuples, av: list of ref Attr): int +{ +Match: + for(; av != nil; av = tl av){ + a := hd av; + for(pl := t.pairs; pl != nil; pl = tl pl) + if((hd pl).attr == a.attr && + (a.val == "*" || a.val == (hd pl).val)) + continue Match; + return 0; + } + return 1; +} + +tuptext(t: ref Tuples): string +{ + s: string; + for(pl := t.pairs; pl != nil; pl = tl pl){ + p := hd pl; + if(s != nil) + s[len s] = ' '; + s += sys->sprint("%s=%q", p.attr, p.val); + } + return s; +} diff --git a/appl/cmd/ndb/csquery.b b/appl/cmd/ndb/csquery.b new file mode 100644 index 00000000..61690617 --- /dev/null +++ b/appl/cmd/ndb/csquery.b @@ -0,0 +1,97 @@ +implement Csquery; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Csquery: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: csquery [-x /net] [-s server] [address ...]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + cantload(Bufio->PATH); + + net := "/net"; + server: string; + arg := load Arg Arg->PATH; + if(arg == nil) + cantload(Arg->PATH); + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'x' => + net = arg->arg(); + if(net == nil) + usage(); + 's' => + server = arg->arg(); + if(server == nil) + usage(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + if(server == nil) + server = net+"/cs"; + if(args != nil){ + for(; args != nil; args = tl args) + csquery(server, hd args); + }else{ + f := bufio->fopen(sys->fildes(0), Sys->OREAD); + if(f == nil) + exit; + for(;;){ + sys->print("> "); + s := f.gets('\n'); + if(s == nil) + break; + csquery(server, s[0:len s-1]); + } + } +} + +cantload(s: string) +{ + sys->fprint(sys->fildes(2), "csquery: can't load %s: %r\n", s); + raise "fail:load"; +} + +csquery(server: string, addr: string) +{ + cs := sys->open(server, Sys->ORDWR); + if(cs == nil){ + sys->fprint(sys->fildes(2), "csquery: can't open %s: %r\n", server); + raise "fail:open"; + } + stdout := sys->fildes(1); + b := array of byte addr; + if(sys->write(cs, b, len b) > 0){ + sys->seek(cs, big 0, Sys->SEEKSTART); + buf := array[256] of byte; + while((n := sys->read(cs, buf, len buf)) > 0) + sys->print("%s\n", string buf[0:n]); + if(n == 0) + return; + } + sys->print("%s: %r\n", addr); +} diff --git a/appl/cmd/ndb/dns.b b/appl/cmd/ndb/dns.b new file mode 100644 index 00000000..8aa1fc0a --- /dev/null +++ b/appl/cmd/ndb/dns.b @@ -0,0 +1,1860 @@ +implement DNS; + +# +# domain name service +# +# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. +# +# RFCs: 1034, 1035, 2181, 2308 +# +# TO DO: +# server side: +# database; inmyzone; ptr generation; separate zone transfer +# currently doesn't implement loony rules on case +# limit work +# check data +# Call +# ipv6 +# + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; + +include "draw.m"; + +include "bufio.m"; + +include "srv.m"; + srv: Srv; + +include "ip.m"; + ip: IP; + IPaddrlen, IPaddr, IPv4off, OUdphdrlen: import ip; + +include "arg.m"; + +include "attrdb.m"; + attrdb: Attrdb; + Db, Dbentry, Tuples: import attrdb; + +include "ipattr.m"; + ipattr: IPattr; + dbattr: import ipattr; + +DNS: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Reply: adt +{ + fid: int; + pid: int; + query: string; + attr: string; + addrs: list of string; + err: string; +}; + +rlist: list of ref Reply; + +dnsfile := "/lib/ndb/local"; +myname: string; +mntpt := "/net"; +DNSport: con 53; +debug := 0; +referdns := 0; +usehost := 1; +now: int; + +servers: list of string; + +# domain name from dns/db +domain: string; +dnsdomains: list of string; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + arg := load Arg Arg->PATH; + if(arg == nil) + cantload(Arg->PATH); + arg->init(args); + arg->setusage("dns [-Drh] [-f dnsfile] [-x mntpt]"); + svcname := "#sdns"; + while((c := arg->opt()) != 0) + case c { + 'D' => + debug = 1; + 'f' => + dnsfile = arg->earg(); + 'h' => + usehost = 0; + 'r' => + referdns = 1; + 'x' => + mntpt = arg->earg(); + svcname = "#sdns"+svcpt(mntpt); + * => + arg->usage(); + } + args = arg->argv(); + if(args != nil) + arg->usage(); + arg = nil; + + if(usehost){ + srv = load Srv Srv->PATH; # hosted Inferno only + if(srv != nil) + srv->init(); + } + ip = load IP IP->PATH; + if(ip == nil) + cantload(IP->PATH); + ip->init(); + attrdb = load Attrdb Attrdb->PATH; + if(attrdb == nil) + cantload(Attrdb->PATH); + attrdb->init(); + ipattr = load IPattr IPattr->PATH; + if(ipattr == nil) + cantload(IPattr->PATH); + ipattr->init(attrdb, ip); + + sys->pctl(Sys->NEWPGRP | Sys->FORKFD, nil); + myname = sysname(); + stderr = sys->fildes(2); + readservers(); + now = time(); + sys->remove(svcname+"/dns"); + sys->unmount(svcname, mntpt); + publish(svcname); + if(sys->bind(svcname, mntpt, Sys->MBEFORE) < 0) + error(sys->sprint("can't bind #s on %s: %r", mntpt)); + file := sys->file2chan(mntpt, "dns"); + if(file == nil) + error(sys->sprint("can't make %s/dns: %r", mntpt)); + sync := chan of int; + spawn dnscache(sync); + <-sync; + spawn dns(file); +} + +publish(dir: string) +{ + d := Sys->nulldir; + d.mode = 8r777; + if(sys->wstat(dir, d) < 0) + sys->fprint(sys->fildes(2), "cs: can't publish %s: %r\n", dir); +} + +svcpt(s: string): string +{ + for(i:=0; i<len s; i++) + if(s[i] == '/') + s[i] = '_'; + return s; +} + +cantload(s: string) +{ + error(sys->sprint("can't load %s: %r", s)); +} + +error(s: string) +{ + sys->fprint(stderr, "dns: %s\n", s); + raise "fail:error"; +} + +dns(file: ref Sys->FileIO) +{ + pidc := chan of int; + donec := chan of ref Reply; + for(;;){ + alt { + (nil, buf, fid, wc) := <-file.write => + now = time(); + cleanfid(fid); # each write cancels previous requests + if(wc != nil){ + r := ref Reply; + r.fid = fid; + spawn request(r, buf, wc, pidc, donec); + r.pid = <-pidc; + rlist = r :: rlist; + } + + (off, nbytes, fid, rc) := <-file.read => + now = time(); + if(rc != nil){ + r := findfid(fid); + if(r != nil) + reply(r, off, nbytes, rc); + else + rc <-= (nil, "unknown request"); + } + + r := <-donec => + now = time(); + r.pid = 0; + if(r.err != nil) + cleanfid(r.fid); + } + } +} + +findfid(fid: int): ref Reply +{ + for(rl := rlist; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid == fid) + return r; + } + return nil; +} + +cleanfid(fid: int) +{ + rl := rlist; + rlist = nil; + for(; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid != fid) + rlist = r :: rlist; + else + killgrp(r.pid); + } +} + +killgrp(pid: int) +{ + if(pid != 0){ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "killgrp") < 0) + sys->fprint(stderr, "dns: can't killgrp %d: %r\n", pid); + } +} + +request(r: ref Reply, data: array of byte, wc: chan of (int, string), pidc: chan of int, donec: chan of ref Reply) +{ + pidc <-= sys->pctl(Sys->NEWPGRP, nil); + query := string data; + for(i := 0; i < len query; i++) + if(query[i] == ' ') + break; + r.query = query[0:i]; + for(; i < len query && query[i] == ' '; i++) + ; + r.attr = query[i:]; + attr := rrtype(r.attr); + if(attr < 0) + r.err = "unknown type"; + else + (r.addrs, r.err) = dnslookup(r.query, attr); + if(r.addrs == nil && r.err == nil) + r.err = "not found"; + if(r.err != nil){ + if(debug) + sys->fprint(stderr, "dns: %s: %s\n", query, r.err); + wc <-= (0, "dns: "+r.err); + } else + wc <-= (len data, nil); + donec <-= r; +} + +reply(r: ref Reply, off: int, nbytes: int, rc: chan of (array of byte, string)) +{ + if(r.err != nil || r.addrs == nil){ + rc <-= (nil, r.err); + return; + } + addr: string; + if(r.addrs != nil){ + addr = hd r.addrs; + r.addrs = tl r.addrs; + } + off = 0; # this version ignores offsets +# rc <-= reads(r.query+" "+r.attr+" "+addr, off, nbytes); + rc <-= reads(addr, off, nbytes); +} + +# +# return the file2chan reply for a read of the given string +# +reads(str: string, off, nbytes: int): (array of byte, string) +{ + bstr := array of byte str; + slen := len bstr; + if(off < 0 || off >= slen) + return (nil, nil); + if(off + nbytes > slen) + nbytes = slen - off; + if(nbytes <= 0) + return (nil, nil); + return (bstr[off:off+nbytes], nil); +} + +sysname(): string +{ + t := rf("/dev/sysname"); + if(t != nil) + return t; + t = rf("#e/sysname"); + if(t == nil){ + s := rf(mntpt+"/ndb"); + if(s != nil){ + db := Db.sopen(t); + if(db != nil){ + (e, nil) := db.find(nil, "sys"); + if(e != nil) + t = e.findfirst("sys"); + } + } + } + if(t != nil){ + fd := sys->open("/dev/sysname", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "%s", t); + } + return t; +} + +rf(name: string): string +{ + fd := sys->open(name, Sys->OREAD); + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0) + return nil; + return string buf[0:n]; +} + +samefile(d1, d2: Sys->Dir): int +{ + # ``it was black ... it was white! it was dark ... it was light! ah yes, i remember it well...'' + return d1.dev==d2.dev && d1.dtype==d2.dtype && + d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers && + d1.mtime==d2.mtime; +} + +# +# database +# dnsdomain= suffix to add to unqualified unrooted names +# dns= dns server to try +# dom= domain name +# ip= IP address +# ns= name server +# soa= +# soa=delegated +# infernosite= set of site-wide parameters +# + +# +# basic Domain Name Service resolver +# + +laststat := 0; # time last stat'd (to reduce churn) +dnsdb: ref Db; + +readservers(): list of string +{ + if(laststat != 0 && now < laststat+2*60) + return servers; + laststat = now; + if(dnsdb == nil){ + db := Db.open(dnsfile); + if(db == nil){ + sys->fprint(stderr, "dns: can't open %s: %r\n", dnsfile); + return nil; + } + dyndb := Db.open(mntpt+"/ndb"); + if(dyndb != nil) + dnsdb = dyndb.append(db); + else + dnsdb = db; + }else{ + if(!dnsdb.changed()) + return servers; + dnsdb.reopen(); + } + if((l := dblooknet("sys", myname, "dnsdomain")) == nil) + l = dblook("infernosite", "", "dnsdomain"); + dnsdomains = "" :: l; + if((l = dblooknet("sys", myname, "dns")) == nil) + l = dblook("infernosite", "", "dns"); + servers = l; +# zones := dblook("soa", "", "dom"); +#printlist("zones", zones); + if(debug) + printlist("dnsdomains", dnsdomains); + if(debug) + printlist("servers", servers); + return servers; +} + +printlist(w: string, l: list of string) +{ + sys->print("%s:", w); + for(; l != nil; l = tl l) + sys->print(" %q", hd l); + sys->print("\n"); +} + +dblookns(dom: string): list of ref RR +{ + domns := dblook("dom", dom, "ns"); + hosts: list of ref RR; + for(; domns != nil; domns = tl domns){ + s := hd domns; + if(debug) + sys->print("dns db: dom=%s ns=%s\n", dom, s); + ipl: list of ref RR = nil; + addrs := dblook("dom", s, "ip"); + for(; addrs != nil; addrs = tl addrs){ + a := parseip(hd addrs); + if(a != nil){ + ipl = ref RR.A(s, Ta, Cin, now+60, 0, a) :: ipl; + if(debug) + sys->print("dom=%s ip=%s\n", s, hd addrs); + } + } + if(ipl != nil){ + # only use ones for which we've got addresses + cachec <-= (ipl, 0); + hosts = ref RR.Host(dom, Tns, Cin, now+60, 0, s) :: hosts; + } + } + if(hosts == nil){ + if(debug) + sys->print("dns: no ns for dom=%s in db\n", dom); + return nil; + } + cachec <-= (hosts, 0); + cachec <-= Sync; + return hosts; +} + +defaultresolvers(): list of ref NS +{ + resolvers := readservers(); + al: list of ref RR; + for(; resolvers != nil; resolvers = tl resolvers){ + nm := hd resolvers; + a := parseip(nm); + if(a == nil){ + # try looking it up as a domain name with an ip address + for(addrs := dblook("dom", nm, "ip"); addrs != nil; addrs = tl addrs){ + a = parseip(hd addrs); + if(a != nil) + al = ref RR.A("defaultns", Ta, Cin, now+60, 0, a) :: al; + } + }else + al = ref RR.A("defaultns", Ta, Cin, now+60, 0, a) :: al; + } + if(al == nil){ + if(debug) + sys->print("dns: no default resolvers\n"); + return nil; + } + return ref NS("defaultns", al, 1, now+60) :: nil; +} + +dblook(attr: string, val: string, rattr: string): list of string +{ + rl: list of string; + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + (e, ptr) = dnsdb.findbyattr(ptr, attr, val, rattr); + if(e == nil) + break; + for(l := e.findbyattr(attr, val, rattr); l != nil; l = tl l){ + (nil, al) := hd l; + for(; al != nil; al = tl al) + if(!inlist((hd al).val, rl)) + rl = (hd al).val :: rl; + } + } + return reverse(rl); +} + +# +# starting from the ip= associated with attr=val, search over all +# containing networks for the nearest values of rattr +# +dblooknet(attr: string, val: string, rattr: string): list of string +{ +#sys->print("dblooknet: %s=%s -> %s\n", attr, val, rattr); + (results, nil) := ipattr->findnetattrs(dnsdb, attr, val, rattr::nil); + rl: list of string; + for(; results != nil; results = tl results){ + (nil, nattrs) := hd results; + for(; nattrs != nil; nattrs = tl nattrs){ + na := hd nattrs; + if(na.name == rattr){ + for(pairs := na.pairs; pairs != nil; pairs = tl pairs) + if((s := (hd pairs).val) != nil && !inlist(s, rl)) + rl = s :: rl; + } + } + } + if(rl == nil) + return dblook(attr, val, rattr); + return reverse(rl); +} + +inlist(s: string, l: list of string): int +{ + for(; l != nil; l = tl l) + if(hd l == s) + return 1; + return 0; +} + +reverse[T](l: list of T): list of T +{ + r: list of T; + for(; l != nil; l = tl l) + r = hd l :: r; + return r; +} + +append(h: list of string, s: string): list of string +{ + if(h == nil) + return s :: nil; + return hd h :: append(tl h, s); +} + +# +# subset of RR types +# +Ta: con 1; +Tns: con 2; +Tcname: con 5; +Tsoa: con 6; +Tmb: con 7; +Tptr: con 12; +Thinfo: con 13; +Tmx: con 15; +Tall: con 255; + +# +# classes +# +Cin: con 1; +Call: con 255; + +# +# opcodes +# +Oquery: con 0<<11; # normal query +Oinverse: con 1<<11; # inverse query +Ostatus: con 2<<11; # status request +Omask: con 16rF<<11; # mask for opcode + +# +# response codes +# +Rok: con 0; +Rformat: con 1; # format error +Rserver: con 2; # server failure +Rname: con 3; # bad name +Runimplemented: con 4; # unimplemented operation +Rrefused: con 5; # permission denied, not supported +Rmask: con 16rF; # mask for response + +# +# other flags in opcode +# +Fresp: con 1<<15; # message is a response +Fauth: con 1<<10; # true if an authoritative response +Ftrunc: con 1<<9; # truncated message +Frecurse: con 1<<8; # request recursion +Fcanrecurse: con 1<<7; # server can recurse + +QR: adt { + name: string; + rtype: int; + class: int; + + text: fn(q: self ref QR): string; +}; + +RR: adt { + name: string; + rtype: int; + class: int; + ttl: int; + flags: int; + pick { + Error => + reason: string; # cached negative + Host => + host: string; + Hinfo => + cpu: string; + os: string; + Mx => + pref: int; + host: string; + Soa => + soa: ref SOA; + A or + Other => + rdata: array of byte; + } + + islive: fn(r: self ref RR): int; + outlives: fn(a: self ref RR, b: ref RR): int; + match: fn(a: self ref RR, b: ref RR): int; + text: fn(a: self ref RR): string; +}; + +SOA: adt { + mname: string; + rname: string; + serial: int; + refresh: int; + retry: int; + expire: int; + minttl: int; + + text: fn(nil: self ref SOA): string; +}; + +DNSmsg: adt { + id: int; + flags: int; + qd: list of ref QR; + an: list of ref RR; + ns: list of ref RR; + ar: list of ref RR; + err: string; + + pack: fn(m: self ref DNSmsg, hdrlen: int): array of byte; + unpack: fn(a: array of byte): ref DNSmsg; + text: fn(m: self ref DNSmsg): string; +}; + +NM: adt { + name: string; + rr: list of ref RR; + stats: ref Stats; +}; + +Stats: adt { + rtt: int; +}; + +cachec: chan of (list of ref RR, int); +cache: array of list of ref NM; +Sync: con (nil, 0); # empty list sent to ensure that last cache update done + +hash(s: string): array of list of ref NM +{ + h := 0; + for(i:=0; i<len s; i++){ # hashpjw + c := s[i]; + if(c >= 'A' && c <= 'Z') + c += 'a'-'A'; + h = (h<<4) + c; + if((g := h & int 16rF0000000) != 0) + h ^= ((g>>24) & 16rFF) | g; + } + return cache[(h&~(1<<31))%len cache:]; +} + +lower(s: string): string +{ + for(i := 0; i < len s; i++){ + c := s[i]; + if(c >= 'A' && c <= 'Z'){ + n := s; + for(; i < len n; i++){ + c = n[i]; + if(c >= 'A' && c <= 'Z') + n[i] = c+('a'-'A'); + } + return n; + } + } + return s; +} + +# +# split rrl into a list of those RRs that match rr and a list of those that don't +# +partrrl(rr: ref RR, rrl: list of ref RR): (list of ref RR, list of ref RR) +{ + m: list of ref RR; + nm: list of ref RR; + name := lower(rr.name); + for(; rrl != nil; rrl = tl rrl){ + t := hd rrl; + if(t.rtype == rr.rtype && t.class == rr.class && + (t.name == name || lower(t.name) == name)) + m = t :: m; + else + nm = t :: nm; + } + return (m, nm); +} + +copyrrl(rrl: list of ref RR): list of ref RR +{ + nl: list of ref RR; + for(; rrl != nil; rrl = tl rrl) + nl = ref *hd rrl :: nl; +# return revrrl(rrl); + return rrl; # probably don't care about order +} + +dnscache(sync: chan of int) +{ + cache = array[32] of list of ref NM; + cachec = chan of (list of ref RR, int); + sync <-= sys->pctl(0, nil); + for(;;){ + (rrl, flags) := <-cachec; + #now = time(); + List: + while(rrl != nil){ + rrset: list of ref RR; + (rrset, rrl) = partrrl(hd rrl, rrl); + rr := hd rrset; + rr.flags = flags; + name := lower(rr.name); + hb := hash(name); + for(ces := hb[0]; ces != nil; ces = tl ces){ + ce := hd ces; + if(ce.name == name){ + rr.name = ce.name; # share string + x := ce.rr; + ce.rr = insertrrset(ce.rr, rr, rrset); + if(x != ce.rr && debug) + sys->print("insertrr %s:%s\n", name, rrsettext(rrset)); + continue List; + } + } + if(debug) + sys->print("newrr %s:%s\n", name, rrsettext(rrset)); + hb[0] = ref NM(name, rrset, nil) :: hb[0]; + } + } +} + +lookcache(name: string, rtype: int, rclass: int): (list of ref RR, string) +{ + results: list of ref RR; + name = lower(name); + for(ces := hash(name)[0]; ces != nil; ces = tl ces){ + ce := hd ces; + if(ce.name == name){ + for(zl := ce.rr; zl != nil; zl = tl zl){ + r := hd zl; + if((r.rtype == rtype || r.rtype == Tall || rtype == Tall) && r.class == rclass && r.name == name && r.islive()){ + pick ar := r { + Error => + if(rtype != Tall || ar.reason != "resource does not exist"){ + if(debug) + sys->print("lookcache: %s[%s]: !%s\n", name, rrtypename(rtype), ar.reason); + return (nil, ar.reason); + } + * => + results = ref *r :: results; + } + } + } + } + } + if(debug) + sys->print("lookcache: %s[%s]: %s\n", name, rrtypename(rtype), rrsettext(results)); + return (results, nil); +} + +# +# insert RRset new in existing list of RRsets rrl +# if that's desirable (it's the whole RRset or nothing, see rfc2181) +# +insertrrset(rrl: list of ref RR, rr: ref RR, new: list of ref RR): list of ref RR +{ + # TO DO: expire entries + match := 0; + for(l := rrl; l != nil; l = tl l){ + orr := hd l; + if(orr.rtype == rr.rtype && orr.class == rr.class){ # name already known to match + match = 1; + if(!orr.islive()) + break; # prefer new, unexpired data + if(tagof rr == tagof RR.Error && tagof orr != tagof RR.Error) + return rrl; # prefer unexpired positive + if(rr.flags & Fauth) + break; # prefer newly-arrived authoritative data + if(orr.flags & Fauth) + return rrl; # prefer authoritative data + if(orr.outlives(rr)) + return rrl; # prefer longer-lived data + } + } + if(match){ + # strip out existing RR set + l = rrl; + rrl = nil; + for(; l != nil; l = tl l){ + orr := hd l; + if((orr.rtype != rr.rtype || orr.class != rr.class) && orr.islive()){ + rrl = orr :: rrl;} + } + } + # add new RR set + for(; new != nil; new = tl new){ + nrr := hd new; + nrr.name = rr.name; + rrl = nrr :: rrl; + } + return rrl; +} + +rrsettext(rrl: list of ref RR): string +{ + s := ""; + for(; rrl != nil; rrl = tl rrl) + s += " ["+(hd rrl).text()+"]"; + return s; +} + +QR.text(qr: self ref QR): string +{ + s := sys->sprint("%s %s", qr.name, rrtypename(qr.rtype)); + if(qr.class != Cin) + s += sys->sprint(" [c=%d]", qr.class); + return s; +} + +RR.islive(rr: self ref RR): int +{ + return rr.ttl >= now; +} + +RR.outlives(a: self ref RR, b: ref RR): int +{ + return a.ttl > b.ttl; +} + +RR.match(a: self ref RR, b: ref RR): int +{ + # compare content, not ttl + return a.rtype == b.rtype && a.class == b.class && a.name == b.name; +} + +RR.text(rr: self ref RR): string +{ + s := sys->sprint("%s %s", rr.name, rrtypename(rr.rtype)); + pick ar := rr { + Host => + s += sys->sprint("\t%s", ar.host); + Hinfo => + s += sys->sprint("\t%s %s", ar.cpu, ar.os); + Mx => + s += sys->sprint("\t%ud %s", ar.pref, ar.host); + Soa => + s += sys->sprint("\t%s", ar.soa.text()); + A => + if(len ar.rdata == 4){ + a := ar.rdata; + s += sys->sprint("\t%d.%d.%d.%d", int a[0], int a[1], int a[2], int a[3]); + } + Error => + s += sys->sprint("\t!%s", ar.reason); + } + return s; +} + +SOA.text(soa: self ref SOA): string +{ + return sys->sprint("%s %s %ud %ud %ud %ud %ud", soa.mname, soa.rname, + soa.serial, soa.refresh, soa.retry, soa.expire, soa.minttl); +} + +NS: adt { + name: string; + addr: list of ref RR; + canrecur: int; + ttl: int; +}; + +dnslookup(name: string, attr: int): (list of string, string) +{ + case attr { + Ta => + case dbattr(name) { + "sys" => + # could apply domains + ; + "dom" => + ; + * => + return (nil, "invalid host name"); + } + if(srv != nil){ # try the host's map first + l := srv->iph2a(name); + if(l != nil) + return (fullresult(name, "ip", l), nil); + } + Tptr => + if(srv != nil){ # try host's map first + l := srv->ipa2h(arpa2addr(name)); + if(l != nil) + return (fullresult(name, "ptr", l), nil); + } + } + return dnslookup1(name, attr); +} + +fullresult(name: string, attr: string, l: list of string): list of string +{ + rl: list of string; + for(; l != nil; l = tl l) + rl = sys->sprint("%s %s\t%s", name, attr, hd l) :: rl; + return reverse(rl); +} + +arpa2addr(a: string): string +{ + (nf, flds) := sys->tokenize(a, "."); + rl: list of string; + for(; flds != nil && lower(s := hd flds) != "in-addr"; flds = tl flds) + rl = s :: rl; + dom: string; + for(; rl != nil; rl = tl rl){ + if(dom != nil) + dom[len dom] = '.'; + dom += hd rl; + } + return dom; +} + +dnslookup1(label: string, attr: int): (list of string, string) +{ + (rrl, err) := fulldnsquery(label, attr, 0); + if(err != nil || rrl == nil) + return (nil, err); + r: list of string; + for(; rrl != nil; rrl = tl rrl) + r = (hd rrl).text() :: r; + return (reverse(r), nil); +} + +trimdot(s: string): string +{ + while(s != nil && s[len s - 1] == '.') + s = s[0:len s -1]; + return s; +} + +parent(s: string): string +{ + if(s == "") + return "."; + for(i := 0; i < len s; i++) + if(s[i] == '.') + return s[i+1:]; + return ""; +} + +rootservers(): list of ref NS +{ + slist := ref NS("a.root-servers.net", + ref RR.A("a.root-servers.net", Ta, Cin, 1<<31, 0, + array[] of {byte 198, byte 41, byte 0, byte 4})::nil, 0, 1<<31) :: nil; + return slist; +} + +# +# this broadly follows the algorithm given in RFC 1034 +# as adjusted and qualified by several other RFCs. +# `label' is 1034's SNAME, `attr' is `STYPE' +# +# TO DO: +# keep statistics for name servers + +fulldnsquery(label: string, attr: int, depth: int): (list of ref RR, string) +{ + slist: list of ref NS; + fd: ref Sys->FD; + if(depth > 10) + return (nil, "dns loop"); + ncname := 0; +Step1: + for(tries:=0; tries<10; tries++){ + + # 1. see if in local information, and if so, return it + (x, err) := lookcache(label, attr, Cin); + if(x != nil) + return (x, nil); + if(err != nil) + return (nil, err); + if(attr != Tcname){ + if(++ncname > 10) + return (nil, "cname alias loop"); + (x, err) = lookcache(label, Tcname, Cin); + if(x != nil){ + pick rx := hd x { + Host => + label = rx.host; + continue; + } + } + } + + # 2. find the best servers to ask + slist = nil; + for(d := trimdot(label); d != "."; d = parent(d)){ + nsl: list of ref RR; + (nsl, err) = lookcache(d, Tns, Cin); + if(nsl == nil) + nsl = dblookns(d); + # add each to slist; put ones with known addresses first + known: list of ref NS = nil; + for(; nsl != nil; nsl = tl nsl){ + pick ns := hd nsl { + Host => + (addrs, err2) := lookcache(ns.host, Ta, Cin); + if(addrs != nil) + known = ref NS(ns.host, addrs, 0, 1<<31) :: known; + else if(err2 == nil) + slist = ref NS(ns.host, nil, 0, 1<<31) :: slist; + } + + } + for(; known != nil; known = tl known) + slist = hd known :: slist; + if(slist != nil) + break; + } + # if no servers, resort to safety belt + if(slist == nil){ + slist = defaultresolvers(); + if(slist == nil){ + slist = rootservers(); + if(slist == nil) + return (nil, "no dns servers configured"); + } + } + (id, query, err1) := mkquery(attr, Cin, label); + if(err1 != nil){ + sys->fprint(stderr, "dns: %s\n", err1); + return (nil, err1); + } + + if(debug) + printnslist(sys->sprint("ns for %s: ", d), slist); + + # 3. send them queries until one returns a response + for(qset := slist; qset != nil; qset = tl qset){ + ns := hd qset; + if(ns.addr == nil){ + if(debug) + sys->print("recursive[%d] query for %s address\n", depth+1, ns.name); + (ns.addr, nil) = fulldnsquery(ns.name, Ta, depth+1); + if(ns.addr == nil) + continue; + } + if(fd == nil){ + fd = udpport(); + if(fd == nil) + return (nil, sys->sprint("%r")); + } + (dm, err2) := udpquery(fd, id, query, ns.name, hd ns.addr); + if(dm == nil){ + sys->fprint(stderr, "dns: %s: %s\n", ns.name, err2); + # TO DO: remove from slist + continue; + } + # 4. analyse the response + # a. answers the question or has Rname, cache it and return to client + # b. delegation to other NS? cache and goto step 2. + # c. if response is CNAME and QTYPE!=CNAME change SNAME to the + # canonical name (data) of the CNAME RR and goto step 1. + # d. if response is server failure or otherwise odd, delete server from SLIST + # and goto step 3. + auth := (dm.flags & Fauth) != 0; + soa: ref RR.Soa; + (soa, dm.ns) = soaof(dm.ns); + if((dm.flags & Rmask) != Rok){ + # don't repeat the request on an error + # TO DO: should return `best error' + if(tl qset != nil && ((dm.flags & Rmask) != Rname || !auth)) + continue; + cause := reason(dm.flags & Rmask); + if(auth && soa != nil){ + # rfc2038 says to cache soa with cached negatives, and the + # negative to be retrieved for all attributes if name does not exist + if((ttl := soa.soa.minttl) > 0) + ttl += now; + else + ttl = now+10*60; + a := attr; + if((dm.flags & Rmask) == Rname) + a = Tall; + cachec <-= (ref RR.Error(label, a, Cin, ttl, auth, cause)::soa::nil, auth); + } + return (nil, cause); + } + if(dm.an != nil){ + if(1 && dm.ns != nil) + cachec <-= (dm.ns, 0); + if(1 && dm.ar != nil) + cachec <-= (dm.ar, 0); + cachec <-= (dm.an, auth); + cachec <-= Sync; + if(isresponse(dm, attr)) + return (dm.an, nil); + if(attr != Tcname && (cn := cnameof(dm)) != nil){ + if(++ncname > 10) + return (nil, "cname alias loop"); + label = cn; + continue Step1; + } + } + if(auth){ + if(soa != nil && (ttl := soa.soa.minttl) > 0) + ttl += now; + else + ttl = now+10*60; + cachec <-= (ref RR.Error(label, attr, Cin, ttl, auth, "resource does not exist")::soa::nil, auth); + return (nil, "resource does not exist"); + } + if(isdelegation(dm)){ + # cache valid name servers and hints + cachec <-= (dm.ns, 0); + if(dm.ar != nil) + cachec <-= (dm.ar, 0); + cachec <-= Sync; + continue Step1; + } + } + } + return (nil, "server failed"); +} + +isresponse(dn: ref DNSmsg, attr: int): int +{ + if(dn == nil || dn.an == nil) + return 0; + return (hd dn.an).rtype == attr; +} + +cnameof(dn: ref DNSmsg): string +{ + if(dn != nil && dn.an != nil && (rr := hd dn.an).rtype == Tcname) + pick ar := rr { + Host => + return ar.host; + } + return nil; +} + +soaof(rrl: list of ref RR): (ref RR.Soa, list of ref RR) +{ + for(l := rrl; l != nil; l = tl l) + pick rr := hd l { + Soa => + rest := tl l; + for(; rrl != l; rrl = tl rrl) + if(tagof hd rrl != tagof RR.Soa) # (just in case) + rest = hd rrl :: rest; + return (rr, rest); + } + return (nil, rrl); +} + +isdelegation(dn: ref DNSmsg): int +{ + if(dn.an != nil) + return 0; + for(al := dn.ns; al != nil; al = tl al) + if((hd al).rtype == Tns) + return 1; + return 0; +} + +printnslist(prefix: string, nsl: list of ref NS) +{ + s := prefix; + for(; nsl != nil; nsl = tl nsl){ + ns := hd nsl; + s += sys->sprint(" [%s %s]", ns.name, rrsettext(ns.addr)); + } + sys->print("%s\n", s); +} + +# +# DNS message format +# + +Udpdnslim: con 512; + +Labels: adt { + names: list of (string, int); + + new: fn(): ref Labels; + look: fn(labs: self ref Labels, s: string): int; + install: fn(labs: self ref Labels, s: string, o: int); +}; + +Labels.new(): ref Labels +{ + return ref Labels; +} + +Labels.look(labs: self ref Labels, s: string): int +{ + for(nl := labs.names; nl != nil; nl = tl nl){ + (t, o) := hd nl; + if(s == t) + return 16rC000 | o; + } + return 0; +} + +Labels.install(labs: self ref Labels, s: string, off: int) +{ + labs.names = (s, off) :: labs.names; +} + +put2(a: array of byte, o: int, val: int): int +{ + if(o < 0) + return o; + if(o + 2 > len a) + return -o; + a[o] = byte (val>>8); + a[o+1] = byte val; + return o+2; +} + +put4(a: array of byte, o: int, val: int): int +{ + if(o < 0) + return o; + if(o + 4 > len a) + return -o; + a[o] = byte (val>>24); + a[o+1] = byte (val>>16); + a[o+2] = byte (val>>8); + a[o+3] = byte val; + return o+4; +} + +puta(a: array of byte, o: int, b: array of byte): int +{ + if(o < 0) + return o; + l := len b; + if(l > 255 || o+l+1 > len a) + return -(o+l+1); + a[o++] = byte l; + a[o:] = b; + return o+len b; +} + +puts(a: array of byte, o: int, s: string): int +{ + return puta(a, o, array of byte s); +} + +get2(a: array of byte, o: int): (int, int) +{ + if(o < 0) + return (0, o); + if(o + 2 > len a) + return (0, -o); + val := (int a[o] << 8) | int a[o+1]; + return (val, o+2); +} + +get4(a: array of byte, o: int): (int, int) +{ + if(o < 0) + return (0, o); + if(o + 4 > len a) + return (0, -o); + val := (((((int a[o] << 8)| int a[o+1]) << 8) | int a[o+2]) << 8) | int a[o+3]; + return (val, o+4); +} + +gets(a: array of byte, o: int): (string, int) +{ + if(o < 0) + return (nil, o); + if(o+1 > len a) + return (nil, -o); + l := int a[o++]; + if(o+l > len a) + return (nil, -o); + return (string a[o:o+l], o+l); +} + +putdn(a: array of byte, o: int, name: string, labs: ref Labels): int +{ + if(o < 0) + return o; + o0 := o; + while(name != "") { + n := labs.look(name); + if(n != 0){ + o = put2(a, o, n); + if(o < 0) + return -o0; + return o; + } + for(l := 0; l < len name && name[l] != '.'; l++) + ; + if(o+l+1 > len a) + return -o0; + labs.install(name, o); + a[o++] = byte l; + for(i := 0; i < l; i++) + a[o++] = byte name[i]; + for(; l < len name && name[l] == '.'; l++) + ; + name = name[l:]; + } + if(o >= len a) + return -o0; + a[o++] = byte 0; + return o; +} + +getdn(a: array of byte, o: int, depth: int): (string, int) +{ + if(depth > 30) + return (nil, -o); + if(o < 0) + return (nil, o); + name := ""; + while(o < len a && (l := int a[o++]) != 0) { + if((l & 16rC0) == 16rC0) { # pointer + if(o >= len a) + return (nil, -o); + po := ((l & 16r3F)<<8) | int a[o]; + if(po >= len a) + return ("", -o); + o++; + pname: string; + (pname, po) = getdn(a, po, depth+1); + if(po < 1) + return (nil, -o); + name += pname; + break; + } + if((l & 16rC0) != 0) + return (nil, -o); # format error + if(o + l > len a) + return (nil, -o); + name += string a[o:o+l]; + o += l; + if(o < len a && a[o] != byte 0) + name += "."; + } + return (lower(name), o); +} + +putqrl(a: array of byte, o: int, qrl: list of ref QR, labs: ref Labels): int +{ + for(; qrl != nil && o >= 0; qrl = tl qrl){ + q := hd qrl; + o = putdn(a, o, q.name, labs); + o = put2(a, o, q.rtype); + o = put2(a, o, q.class); + } + return o; +} + +getqrl(nq: int, a: array of byte, o: int): (list of ref QR, int) +{ + if(o < 0) + return (nil, o); + qrl: list of ref QR; + for(i := 0; i < nq; i++) { + qd := ref QR; + (qd.name, o) = getdn(a, o, 0); + (qd.rtype, o) = get2(a, o); + (qd.class, o) = get2(a, o); + if(o < 1) + break; + qrl = qd :: qrl; + } + q: list of ref QR; + for(; qrl != nil; qrl = tl qrl) + q = hd qrl :: q; + return (q, o); +} + +putrrl(a: array of byte, o: int, rrl: list of ref RR, labs: ref Labels): int +{ + if(o < 0) + return o; + for(; rrl != nil; rrl = tl rrl){ + rr := hd rrl; + o0 := o; + o = putdn(a, o, rr.name, labs); + o = put2(a, o, rr.rtype); + o = put2(a, o, rr.class); + o = put4(a, o, rr.ttl); + pick ar := rr { + Host => + o = putdn(a, o, ar.host, labs); + Hinfo => + o = puts(a, o, ar.cpu); + o = puts(a, o, ar.os); + Mx => + o = put2(a, o, ar.pref); + o = putdn(a, o, ar.host, labs); + Soa => + soa := ar.soa; + o = putdn(a, o, soa.mname, labs); + o = putdn(a, o, soa.rname, labs); + o = put4(a, o, soa.serial); + o = put4(a, o, soa.refresh); + o = put4(a, o, soa.retry); + o = put4(a, o, soa.expire); + o = put4(a, o, soa.minttl); + A or + Other => + dlen := len ar.rdata; + o = put2(a, o, dlen); + if(o < 1) + return -o0; + if(o + dlen > len a) + return -o0; + a[o:] = ar.rdata; + o += dlen; + } + } + return o; +} + +getrrl(nr: int, a: array of byte, o: int): (list of ref RR, int) +{ + if(o < 0) + return (nil, o); + rrl: list of ref RR; + for(i := 0; i < nr; i++) { + name: string; + rtype, rclass, ttl: int; + (name, o) = getdn(a, o, 0); + (rtype, o) = get2(a, o); + (rclass, o) = get2(a, o); + (ttl, o) = get4(a, o); + if(ttl <= 0) + ttl = 0; + #ttl = 1*60; + ttl += now; + dlen: int; + (dlen, o) = get2(a, o); + if(o < 1) + return (rrl, o); + if(o+dlen > len a) + return (rrl, -(o+dlen)); + rr: ref RR; + dname: string; + case rtype { + Tsoa => + soa := ref SOA; + (soa.mname, o) = getdn(a, o, 0); + (soa.rname, o) = getdn(a, o, 0); + (soa.serial, o) = get4(a, o); + (soa.refresh, o) = get4(a, o); + (soa.retry, o) = get4(a, o); + (soa.expire, o) = get4(a, o); + (soa.minttl, o) = get4(a, o); + rr = ref RR.Soa(name, rtype, rclass, ttl, 0, soa); + Thinfo => + cpu, os: string; + (cpu, o) = gets(a, o); + (os, o) = gets(a, o); + rr = ref RR.Hinfo(name, rtype, rclass, ttl, 0, cpu, os); + Tmx => + pref: int; + host: string; + (pref, o) = get2(a, o); + (host, o) = getdn(a, o, 0); + rr = ref RR.Mx(name, rtype, rclass, ttl, 0, pref, host); + Tcname or + Tns or + Tptr => + (dname, o) = getdn(a, o, 0); + rr = ref RR.Host(name, rtype, rclass, ttl, 0, dname); + Ta => + rdata := array[dlen] of byte; + rdata[0:] = a[o:o+dlen]; + rr = ref RR.A(name, rtype, rclass, ttl, 0, rdata); + o += dlen; + * => + rdata := array[dlen] of byte; + rdata[0:] = a[o:o+dlen]; + rr = ref RR.Other(name, rtype, rclass, ttl, 0, rdata); + o += dlen; + } + rrl = rr :: rrl; + } + r: list of ref RR; + for(; rrl != nil; rrl = tl rrl) + r = (hd rrl) :: r; + return (r, o); +} + +DNSmsg.pack(msg: self ref DNSmsg, hdrlen: int): array of byte +{ + a := array[Udpdnslim+hdrlen] of byte; + + l := hdrlen; + l = put2(a, l, msg.id); + l = put2(a, l, msg.flags); + l = put2(a, l, len msg.qd); + l = put2(a, l, len msg.an); + l = put2(a, l, len msg.ns); + l = put2(a, l, len msg.ar); + labs := Labels.new(); + l = putqrl(a, l, msg.qd, labs); + l = putrrl(a, l, msg.an, labs); + l = putrrl(a, l, msg.ns, labs); + l = putrrl(a, l, msg.ar, labs); + if(l < 1) + return nil; + return a[0:l]; +} + +DNSmsg.unpack(a: array of byte): ref DNSmsg +{ + msg := ref DNSmsg; + msg.flags = Rformat; + l := 0; + (msg.id, l) = get2(a, l); + (msg.flags, l) = get2(a, l); + if(l < 0 || l > len a){ + msg.err = "length error"; + return msg; + } + if(l >= len a) + return msg; + + nqd, nan, nns, nar: int; + (nqd, l) = get2(a, l); + (nan, l) = get2(a, l); + (nns, l) = get2(a, l); + (nar, l) = get2(a, l); + if(l >= len a) + return msg; + (msg.qd, l) = getqrl(nqd, a, l); + (msg.an, l) = getrrl(nan, a, l); + (msg.ns, l) = getrrl(nns, a, l); + (msg.ar, l) = getrrl(nar, a, l); + if(l < 1){ + sys->fprint(stderr, "l=%d format error\n", l); + msg.err = "format error"; + return msg; + } + return msg; +} + +DNSmsg.text(msg: self ref DNSmsg): string +{ + s := sys->sprint("id=%ud flags=#%ux[%s]\n", msg.id, msg.flags, flagtext(msg.flags)); + s += " QR:\n"; + for(x := msg.qd; x != nil; x = tl x) + s += "\t"+(hd x).text()+"\n"; + s += " AN:\n"; + for(l := msg.an; l != nil; l = tl l) + s += "\t"+(hd l).text()+"\n"; + s += " NS:\n"; + for(l = msg.ns; l != nil; l = tl l) + s += "\t"+(hd l).text()+"\n"; + s += " AR:\n"; + for(l = msg.ar; l != nil; l = tl l) + s += "\t"+(hd l).text()+"\n"; + return s; +} + +flagtext(f: int): string +{ + s := ""; + if(f & Fresp) + s += "R"; + if(f & Fauth) + s += "A"; + if(f & Ftrunc) + s += "T"; + if(f & Frecurse) + s += "r"; + if(f & Fcanrecurse) + s += "c"; + if((f & Fresp) == 0) + return s; + if(s != "") + s += ","; + return s+reason(f & Rmask); +} + +rcodes := array[] of { + Rok => "no error", + Rformat => "format error", + Rserver => "server failure", + Rname => "name does not exist", + Runimplemented => "unimplemented", + Rrefused => "refused", +}; + +reason(n: int): string +{ + if(n < 0 || n > len rcodes) + return sys->sprint("error %d", n); + return rcodes[n]; +} + +rrtype(s: string): int +{ + case s { + "ip" => return Ta; + "ns" => return Tns; + "cname" => return Tcname; + "soa" => return Tsoa; + "ptr" => return Tptr; + "mx" => return Tmx; + "hinfo" => return Thinfo; + "all" or "any" => return Tall; + * => return -1; + } +} + +rrtypename(t: int): string +{ + case t { + Ta => return "ip"; + Tns => return "ns"; + Tcname => return "cname"; + Tsoa => return "soa"; + Tptr => return "ptr"; + Tmx => return "mx"; + Tall => return "all"; + Thinfo => return "hinfo"; + * => return string t; + } +} + +# +# format of UDP head read and written in `oldheaders' mode +# +Udphdrsize: con OUdphdrlen; +Udpraddr: con 0; +Udpladdr: con IPaddrlen; +Udprport: con 2*IPaddrlen; +Udplport: con 2*IPaddrlen+2; +dnsid := 1; + +mkquery(qtype: int, qclass: int, name: string): (int, array of byte, string) +{ + qd := ref QR(name, qtype, qclass); + dm := ref DNSmsg; + dm.id = dnsid++; # doesn't matter if two different procs use it + dm.flags = Oquery; + if(referdns || !debug) + dm.flags |= Frecurse; + dm.qd = qd :: nil; + a: array of byte; + a = dm.pack(Udphdrsize); + if(a == nil) + return (0, nil, "dns: bad query message"); # should only happen if a name is ridiculous + for(i:=0; i<Udphdrsize; i++) + a[i] = byte 0; + a[Udprport] = byte (DNSport>>8); + a[Udprport+1] = byte DNSport; + return (dm.id, a, nil); +} + +udpquery(fd: ref Sys->FD, id: int, query: array of byte, sname: string, addr: ref RR): (ref DNSmsg, string) +{ + # TO DO: check address and ports? + + if(debug) + sys->print("udp query %s\n", sname); + pick ar := addr { + A => + query[Udpraddr:] = ip->v4prefix[0:IPv4off]; + query[Udpraddr+IPv4off:] = ar.rdata[0:4]; + * => + return (nil, "not A resource"); + } + dm: ref DNSmsg; + pidc := chan of int; + c := chan of array of byte; + spawn reader(fd, c, pidc); + rpid := <-pidc; + spawn timer(c, pidc); + tpid := <-pidc; + for(ntries := 0; ntries < 8; ntries++){ + if(debug){ + ipa := query[Udpraddr+IPv4off:]; + sys->print("send udp!%d.%d.%d.%d!%d [%d] %d\n", int ipa[0], int ipa[1], + int ipa[2], int ipa[3], get2(query, Udprport).t0, ntries, len query); + } + n := sys->write(fd, query, len query); + if(n != len query) + return (nil, sys->sprint("udp write err: %r")); + buf := <-c; + if(buf != nil){ + buf = buf[Udphdrsize:]; + dm = DNSmsg.unpack(buf); + if(dm == nil){ + kill(tpid); + kill(rpid); + return (nil, "bad udp reply message"); + } + if(dm.flags & Fresp && dm.id == id){ + if(dm.flags & Ftrunc && dm.ns == nil){ + if(debug) + sys->print("id=%d was truncated\n", dm.id); + }else + break; + }else if(debug) + sys->print("id=%d got flags #%ux id %d\n", id, dm.flags, dm.id); + }else if(debug) + sys->print("timeout\n"); + } + kill(tpid); + kill(rpid); + if(dm == nil) + return (nil, "no reply"); + if(dm.err != nil){ + sys->fprint(stderr, "bad reply: %s\n", dm.err); + return (nil, dm.err); + } + if(debug) + sys->print("reply: %s\n", dm.text()); + return (dm, nil); +} + +reader(fd: ref Sys->FD, c: chan of array of byte, pidc: chan of int) +{ + pidc <-= sys->pctl(0, nil); + for(;;){ + buf := array[4096+Udphdrsize] of byte; + n := sys->read(fd, buf, len buf); + if(n > 0){ + if(debug) + sys->print("rcvd %d\n", n); + c <-= buf[0:n]; + }else + c <-= nil; + } +} + +timer(c: chan of array of byte, pidc: chan of int) +{ + pidc <-= sys->pctl(0, nil); + for(;;){ + sys->sleep(5*1000); + c <-= nil; + } +} + +kill(pid: int) +{ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); +} + +udpport(): ref Sys->FD +{ + (ok, conn) := sys->announce(mntpt+"/udp!*!0"); + if(ok < 0) + return nil; + if(sys->fprint(conn.cfd, "headers") < 0){ + sys->fprint(stderr, "dns: can't set headers mode: %r\n"); + return nil; + } + sys->fprint(conn.cfd, "oldheaders"); # plan 9 interface + conn.dfd = sys->open(conn.dir+"/data", Sys->ORDWR); + if(conn.dfd == nil){ + sys->fprint(stderr, "dns: can't open %s/data: %r\n", conn.dir); + return nil; + } + return conn.dfd; +} + +# +# TCP/IP can be used to get the whole of a truncated message +# +tcpquery(query: array of byte): (ref DNSmsg, string) +{ + # TO DO: check request id, ports etc. + + ipa := query[Udpraddr+IPv4off:]; + addr := sys->sprint("tcp!%d.%d.%d.%d!%d", int ipa[0], int ipa[1], int ipa[2], int ipa[3], DNSport); + (ok, conn) := sys->dial(addr, nil); + if(ok < 0) + return (nil, sys->sprint("can't dial %s: %r", addr)); + query = query[Udphdrsize-2:]; + put2(query, 0, len query-2); # replace UDP header by message length + n := sys->write(conn.dfd, query[Udphdrsize:], len query); + if(n != len query) + return (nil, sys->sprint("dns: %s: write err: %r", addr)); + buf := readn(conn.dfd, 2); # TCP/DNS record header + (mlen, nil) := get2(buf, 0); + if(mlen < 2 || mlen > 16384) + return (nil, sys->sprint("dns: %s: bad reply msg length=%d", addr, mlen)); + buf = readn(conn.dfd, mlen); + if(buf == nil) + return (nil, sys->sprint("dns: %s: read err: %r", addr)); + dm := DNSmsg.unpack(buf); + if(dm == nil) + return (nil, "dns: bad reply message"); + if(dm.err != nil){ + sys->fprint(stderr, "dns: %s: bad reply: %s\n", addr, dm.err); + return (nil, dm.err); + } + return (dm, nil); +} + +readn(fd: ref Sys->FD, nb: int): array of byte +{ + buf:= array[nb] of byte; + for(n:=0; n<nb;){ + m := sys->read(fd, buf[n:], nb-n); + if(m <= 0) + return nil; + n += m; + } + return buf; +} + +timefd: ref Sys->FD; + +time(): int +{ + if(timefd == nil){ + timefd = sys->open("/dev/time", Sys->OREAD); + if(timefd == nil) + return 0; + } + buf := array[128] of byte; + sys->seek(timefd, big 0, 0); + n := sys->read(timefd, buf, len buf); + if(n < 0) + return 0; + return int ((big string buf[0:n]) / big 1000000); +} + +parseip(s: string): array of byte +{ + (ok, a) := IPaddr.parse(s); + if(ok < 0 || !a.isv4()) + return nil; + return a.v4(); +} diff --git a/appl/cmd/ndb/dnsquery.b b/appl/cmd/ndb/dnsquery.b new file mode 100644 index 00000000..194c08a2 --- /dev/null +++ b/appl/cmd/ndb/dnsquery.b @@ -0,0 +1,177 @@ +implement Dnsquery; + +# +# Copyright © 2003 Vita Nuova Holdings LImited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Dnsquery: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: dnsquery [-x /net] [-s server] [address ...]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + cantload(Bufio->PATH); + + net := "/net"; + server: string; + arg := load Arg Arg->PATH; + if(arg == nil) + cantload(Arg->PATH); + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'x' => + net = arg->arg(); + if(net == nil) + usage(); + 's' => + server = arg->arg(); + if(server == nil) + usage(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + if(server == nil) + server = net+"/dns"; + if(args != nil){ + for(; args != nil; args = tl args) + dnsquery(server, hd args); + }else{ + f := bufio->fopen(sys->fildes(0), Sys->OREAD); + if(f == nil) + exit; + for(;;){ + sys->print("> "); + s := f.gets('\n'); + if(s == nil) + break; + dnsquery(server, s[0:len s-1]); + } + } +} + +cantload(s: string) +{ + sys->fprint(sys->fildes(2), "dnsquery: can't load %s: %r\n", s); + raise "fail:load"; +} + +dnsquery(server: string, query: string) +{ + dns := sys->open(server, Sys->ORDWR); + if(dns == nil){ + sys->fprint(sys->fildes(2), "dnsquery: can't open %s: %r\n", server); + raise "fail:open"; + } + stdout := sys->fildes(1); + for(i := len query; --i >= 0 && query[i] != ' ';) + {} + if(i < 0){ + i = len query; + case dbattr(query) { + "ip" => + query += " ptr"; + * => + query += " ip"; + } + } + if(query[i+1:] == "ptr"){ + while(i > 0 && query[i-1] == ' ') + i--; + if(!hastail(query[0:i], ".in-addr.arpa") && !hastail(query[0:i], ".IN-ADDR.ARPA")) + query = addr2arpa(query[0:i])+" ptr"; + } + b := array of byte query; + if(sys->write(dns, b, len b) > 0){ + sys->seek(dns, big 0, Sys->SEEKSTART); + buf := array[256] of byte; + while((n := sys->read(dns, buf, len buf)) > 0) + sys->print("%s\n", string buf[0:n]); + if(n == 0) + return; + } + sys->print("!%r\n"); +} + +hastail(s: string, t: string): int +{ + if(len s >= len t && s[len s - len t:] == t) + return 1; + return 0; +} + +addr2arpa(a: string): string +{ + (nf, flds) := sys->tokenize(a, "."); + rl: list of string; + for(; flds != nil; flds = tl flds) + rl = hd flds :: rl; + addr: string; + for(; rl != nil; rl = tl rl){ + if(addr != nil) + addr[len addr] = '.'; + addr += hd rl; + } + return addr+".in-addr.arpa"; +} + +dbattr(s: string): string +{ + digit := 0; + dot := 0; + alpha := 0; + hex := 0; + colon := 0; + for(i := 0; i < len s; i++){ + case c := s[i] { + '0' to '9' => + digit = 1; + 'a' to 'f' or 'A' to 'F' => + hex = 1; + '.' => + dot = 1; + ':' => + colon = 1; + * => + if(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '-' || c == '&') + alpha = 1; + } + } + if(alpha){ + if(dot) + return "dom"; + return "sys"; + } + if(colon) + return "ip"; + if(dot){ + if(!hex) + return "ip"; + return "dom"; + } + return "sys"; +} diff --git a/appl/cmd/ndb/mkfile b/appl/cmd/ndb/mkfile new file mode 100644 index 00000000..fb1a7074 --- /dev/null +++ b/appl/cmd/ndb/mkfile @@ -0,0 +1,28 @@ +<../../../mkconfig + +TARG=\ + cs.dis\ + csquery.dis\ + dns.dis\ + dnsquery.dis\ + mkhash.dis\ + query.dis\ + registry.dis\ + regquery.dis\ + +SYSMODULES=\ + sys.m\ + draw.m\ + bufio.m\ + arg.m\ + attrdb.m\ + ip.m\ + ipattr.m\ + styx.m\ + styxservers.m\ + +MODULES=\ + +DISBIN=$ROOT/dis/ndb + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/ndb/mkhash.b b/appl/cmd/ndb/mkhash.b new file mode 100644 index 00000000..f1876355 --- /dev/null +++ b/appl/cmd/ndb/mkhash.b @@ -0,0 +1,119 @@ +implement Mkhash; + +# +# for compatibility, this is closely modelled on Plan 9's ndb/mkhash +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; +include "attrdb.m"; + attrdb: Attrdb; + Db, Dbf, Dbentry, Tuples, Attr: import attrdb; + attrhash: Attrhash; + NDBPLEN, NDBHLEN, NDBCHAIN, NDBNAP: import Attrhash; + +Mkhash: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + attrdb = load Attrdb Attrdb->PATH; + if(attrdb == nil) + error(sys->sprint("can't load %s: %r", Attrdb->PATH)); + attrdb->init(); + attrhash = load Attrhash Attrhash->PATH; + if(attrhash == nil) + error(sys->sprint("can't load %s: %r", Attrhash->PATH)); + + if(len args != 3) + error("usage: mkhash file attr"); + args = tl args; + dbname := hd args; + args = tl args; + attr := hd args; + dbf := Dbf.open(dbname); + if(dbf == nil) + error(sys->sprint("can't open %s: %r", dbname)); + offset := 0; + n := 0; + for(;;){ + (e, nil, next) := dbf.readentry(offset, nil, nil, 0); + if(e == nil) + break; + m := len e.find(attr); + if(0 && m != 0) + sys->fprint(sys->fildes(2), "%ud [%d]\n", offset, m); + n += m; + offset = next; + } + hlen := 2*n+1; + chains := n*2*NDBPLEN; + file := array[NDBHLEN + hlen*NDBPLEN + chains] of byte; + tab := file[NDBHLEN:]; + for(i:=0; i<len tab; i+=NDBPLEN) + put3(tab[i:], NDBNAP); + offset = 0; + chain := hlen*NDBPLEN; + for(;;){ + (e, nil, next) := dbf.readentry(offset, nil, nil, 0); + if(e == nil) + break; + for(l := e.find(attr); l != nil; l = tl l) + for((nil, al) := hd l; al != nil; al = tl al) + chain = enter(tab, hd al, hlen, chain, offset); + offset = next; + } + hashfile := dbname+"."+attr; + hfd := sys->create(hashfile, Sys->OWRITE, 8r666); + if(hfd == nil) + error(sys->sprint("can't create %s: %r", hashfile)); + mtime := 0; + if(dbf.dir != nil) + mtime = dbf.dir.mtime; + put4(file, mtime); + put4(file[4:], hlen); + if(sys->write(hfd, file, NDBHLEN+chain) != NDBHLEN+chain) + error(sys->sprint("error writing %s: %r", hashfile)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "mkhash: %s\n", s); + raise "fail:error"; +} + +enter(tab: array of byte, a: ref Attr, hlen: int, chain: int, offset: int): int +{ + o := attrhash->hash(a.val, hlen)*NDBPLEN; + for(; (p := attrhash->get3(tab[o:])) != NDBNAP; o = p & ~NDBCHAIN) + if((p & NDBCHAIN) == 0){ + put3(tab[o:], chain | NDBCHAIN); + put3(tab[chain:], p); + put3(tab[chain+NDBPLEN:], offset); + return chain+2*NDBPLEN; + } + put3(tab[o:], offset); + return chain; +} + +put3(a: array of byte, v: int) +{ + a[0] = byte v; + a[1] = byte (v>>8); + a[2] = byte (v>>16); +} + +put4(a: array of byte, v: int) +{ + a[0] = byte v; + a[1] = byte (v>>8); + a[2] = byte (v>>16); + a[3] = byte (v>>24); +} diff --git a/appl/cmd/ndb/query.b b/appl/cmd/ndb/query.b new file mode 100644 index 00000000..b636492d --- /dev/null +++ b/appl/cmd/ndb/query.b @@ -0,0 +1,135 @@ +implement Query; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + +include "attrdb.m"; + attrdb: Attrdb; + Attr, Tuples, Dbentry, Db: import attrdb; + +include "arg.m"; + +Query: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: query attr [value [rattr]]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + dbfile := "/lib/ndb/local"; + arg := load Arg Arg->PATH; + if(arg == nil) + badload(Arg->PATH); + arg->init(args); + arg->setusage("query [-a] [-f dbfile] attr [value [rattr]]"); + all := 0; + while((o := arg->opt()) != 0) + case o { + 'f' => dbfile = arg->earg(); + 'a' => all = 1; + * => arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + attr := hd args; + args = tl args; + value, rattr: string; + vflag := 0; + if(args != nil){ + vflag = 1; + value = hd args; + args = tl args; + if(args != nil) + rattr = hd args; + } + arg = nil; + + attrdb = load Attrdb Attrdb->PATH; + if(attrdb == nil) + badload(Attrdb->PATH); + err := attrdb->init(); + if(err != nil) + error(sys->sprint("can't init Attrdb: %s", err)); + + db := Db.open(dbfile); + if(db == nil) + error(sys->sprint("can't open %s: %r", dbfile)); + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + if(rattr != nil) + (e, ptr) = db.findbyattr(ptr, attr, value, rattr); + else if(vflag) + (e, ptr) = db.findpair(ptr, attr, value); + else + (e, ptr) = db.find(ptr, attr); + if(e == nil) + break; + if(rattr != nil){ + matches: list of (ref Tuples, list of ref Attr); + if(rattr != nil) + matches = e.findbyattr(attr, value, rattr); + else + matches = e.find(attr); + for(; matches != nil; matches = tl matches){ + (line, attrs) := hd matches; + if(attrs != nil) + printvals(attrs, all); + if(!all) + exit; + } + }else + printentry(e); + if(!all) + exit; + } +} + +badload(s: string) +{ + error(sys->sprint("can't load %s: %r", s)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "query: %s\n", s); + raise "fail:error"; +} + +printentry(e: ref Dbentry) +{ + s := ""; + for(lines := e.lines; lines != nil; lines = tl lines){ + line := hd lines; + for(al := line.pairs; al != nil; al = tl al){ + a := hd al; + s += sys->sprint(" %q=%q", a.attr, a.val); + } + } + if(s != "") + s = s[1:]; + sys->print("%s\n", s); +} + +printvals(al: list of ref Attr, all: int) +{ + for(; al != nil; al = tl al){ + a := hd al; + sys->print("%q\n", a.val); + if(!all) + break; + } +} diff --git a/appl/cmd/ndb/registry.b b/appl/cmd/ndb/registry.b new file mode 100644 index 00000000..f720781f --- /dev/null +++ b/appl/cmd/ndb/registry.b @@ -0,0 +1,671 @@ +implement Registry; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "string.m"; + str: String; +include "daytime.m"; + daytime: Daytime; +include "bufio.m"; +include "attrdb.m"; + attrdb: Attrdb; + Db, Dbf, Dbentry: import attrdb; +include "styx.m"; + styx: Styx; + Rmsg, Tmsg: import styx; +include "styxservers.m"; + styxservers: Styxservers; + Styxserver, Fid, Navigator, Navop: import styxservers; + Enotdir, Enotfound: import Styxservers; +include "arg.m"; + +# files: +# 'new' +# write name of new service; (and possibly attribute column names) +# entry appears in directory of that name +# can then write attributes/values +# 'index' +# read to get info on all services and their attributes. +# 'find' +# write to set filter. +# read to get info on all services with matching attributes +# 'event' (not needed initially) +# read to block until changes happen. +# servicename +# write to change attributes (only by owner) +# remove to unregister service. + +Registry: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +Qroot, +Qnew, +Qindex, +Qevent, +Qfind, +Qsvc: con iota; + + +Shift: con 4; +Mask: con 2r1111; + +Egreg: con "buggy program!"; +Maxreplyidle: con 3; + +Service: adt { + id: int; + slot: int; + owner: string; + name: string; + atime: int; + mtime: int; + vers: int; + fid: int; # fid that created it (NOFID if static) + attrs: list of (string, string); + + new: fn(owner: string): ref Service; + find: fn(id: int): ref Service; + remove: fn(svc: self ref Service); + set: fn(svc: self ref Service, attr, val: string); + get: fn(svc: self ref Service, attr: string): string; +}; + +Filter: adt { + id: int; # filter ID (it's a fid) + attrs: array of (string, string); + + new: fn(id: int): ref Filter; + find: fn(id: int): ref Filter; + set: fn(f: self ref Filter, a: array of (string, string)); + match: fn(f: self ref Filter, attrs: list of (string, string)): int; + remove: fn(f: self ref Filter); +}; + +filters: list of ref Filter; + + +services := array[9] of ref Service; +nservices := 0; +idseq := 0; +rootvers := 0; +now: int; +startdate: int; +dbfile: string; + +srv: ref Styxserver; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + if(str == nil) + loaderr(String->PATH); + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + loaderr(Daytime->PATH); + styx = load Styx Styx->PATH; + if (styx == nil) + loaderr(Styx->PATH); + styx->init(); + styxservers = load Styxservers Styxservers->PATH; + if (styxservers == nil) + loaderr(Styxservers->PATH); + styxservers->init(styx); + + arg := load Arg Arg->PATH; + if(arg == nil) + loaderr(Arg->PATH); + arg->init(args); + arg->setusage("ndb/registry [-f initdb]"); + while((o := arg->opt()) != 0) + case o { + 'f' => dbfile = arg->earg(); + * => arg->usage(); + } + args = arg->argv(); + if(args != nil) + arg->usage(); + arg = nil; + + sys->pctl(Sys->FORKNS|Sys->NEWFD, 0::1::2::nil); + startdate = now = daytime->now(); + if(dbfile != nil){ + attrdb = load Attrdb Attrdb->PATH; + if(attrdb == nil) + loaderr(Attrdb->PATH); + attrdb->init(); + db := Db.open(dbfile); + if(db == nil) + error(sys->sprint("can't open %s: %r", dbfile)); + dbload(db); + db = nil; # for now assume it's static + } + navops := chan of ref Navop; + spawn navigator(navops); + tchan: chan of ref Tmsg; + (tchan, srv) = Styxserver.new(sys->fildes(0), Navigator.new(navops), big Qroot); + spawn serve(tchan, navops); +} + +loaderr(p: string) +{ + error(sys->sprint("can't load %s: %r", p)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "registry: %s\n", s); + raise "fail:error"; +} + +serve(tchan: chan of ref Tmsg, navops: chan of ref Navop) +{ +Serve: + while((gm := <-tchan) != nil){ + now = daytime->now(); + err := ""; + pick m := gm { + Readerror => + error(sys->sprint("fatal read error: %s\n", m.error)); + break Serve; + Open => + (fid, mode, d, e) := srv.canopen(m); + if((err = e) != nil) + break; + if(fid.qtype & Sys->QTDIR) + srv.default(m); + else + open(m, fid); + Read => + (fid, e) := srv.canread(m); + if((err = e) != nil) + break; + if(fid.qtype & Sys->QTDIR) + srv.read(m); + else + err = read(m, fid); + Write => + (fid, e) := srv.canwrite(m); + if((err = e) != nil) + break; + err = write(m, fid); + if(err == nil) + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + Clunk => + clunk(srv.clunk(m)); + Remove => + (fid, nil, e) := srv.canremove(m); + srv.delfid(fid); # always clunked even on error + if((err = e) != nil) + break; + err = remove(fid); + if(err == nil) + srv.reply(ref Rmsg.Remove(m.tag)); + * => + srv.default(gm); + } + if(err != "") + srv.reply(ref Rmsg.Error(gm.tag, err)); + } + navops <-= nil; +} + +open(m: ref Tmsg.Open, fid: ref Fid) +{ + path := int fid.path; + case path & Mask { + Qnew => + svc := Service.new(fid.uname); + svc.fid = fid.fid; + fid.open(m.mode, (big ((svc.id << Shift)|Qsvc), 0, Sys->QTFILE)); + * => + fid.open(m.mode, (fid.path, 0, fid.qtype)); + } + srv.reply(ref Rmsg.Open(m.tag, (fid.path, 0, fid.qtype), 0)); +} + +read(m: ref Tmsg.Read, fid: ref Fid): string +{ + path := int fid.path; + case path & Mask { + Qindex => + if(fid.data == nil || m.offset == big 0) + fid.data = getindexdata(-1, Styx->NOFID); + srv.reply(styxservers->readbytes(m, fid.data)); + Qfind => + if(fid.data == nil || m.offset == big 0) + fid.data = getindexdata(-1, fid.fid); + srv.reply(styxservers->readbytes(m, fid.data)); + Qsvc => + if(fid.data == nil || m.offset == big 0){ + svc := Service.find(path >> Shift); + if(svc != nil) + svc.atime = now; + fid.data = getindexdata(path >> Shift, Styx->NOFID); + } + srv.reply(styxservers->readbytes(m, fid.data)); + Qevent => + return "not implemented yet"; + * => + return Egreg; + } + return nil; +} + +write(m: ref Tmsg.Write, fid: ref Fid): string +{ + path := int fid.path; + case path & Mask { + Qsvc => + svc := Service.find(path >> Shift); + if(svc == nil) + return Egreg; + s := string m.data; + toks := str->unquoted(s); + if(toks == nil) + return "bad syntax"; + # first write names the service (possibly with attributes) + if(svc.name == nil){ + if((e := svcnameok(hd toks)) != nil) + return "bad service name"; + svc.name = hd toks; + toks = tl toks; + } + if(len toks % 2 != 0) + return "odd attribute/value pairs"; + svc.mtime = now; + svc.vers++; + for(; toks != nil; toks = tl tl toks) + svc.set(hd toks, hd tl toks); + Qfind => + s := string m.data; + toks := str->unquoted(s); + n := len toks; + if(n % 2 != 0) + return "odd attribute/value pairs"; + f := Filter.find(fid.fid); + if(n != 0){ + a := array[n/2] of (string, string); + for(n=0; toks != nil; n++){ + a[n] = (hd toks, hd tl toks); + toks = tl tl toks; + } + if(f == nil) + f = Filter.new(fid.fid); + f.set(a); + }else{ + if(f != nil) + f.remove(); + } + * => + return Egreg; + } + return nil; +} + +clunk(fid: ref Fid) +{ + path := int fid.path; + case path & Mask { + Qsvc => + svc := Service.find(path >> Shift); + if(svc != nil && svc.fid == fid.fid && int svc.get("persist") == 0) + svc.remove(); + Qevent => + ; # remove queued events? + Qfind => + if((f := Filter.find(fid.fid)) != nil) + f.remove(); + } +} + +remove(fid: ref Fid): string +{ + path := int fid.path; + if((path & Mask) == Qsvc){ + svc := Service.find(path >> Shift); + if(fid.uname == svc.owner){ + svc.remove(); + return nil; + } + } + return "permission denied"; +} + +svcnameok(s: string): string +{ + # could require that a service name contains at least one (or two) '!' characters. + for(i := 0; i < len s; i++){ + c := s[i]; + if(c <= 32 || c == '/' || c == 16r7f) + return "bad character in service name"; + } + case s { + "new" or + "event" or + "find" or + "index" => + return "bad service name"; + } + for(i = 0; i < nservices; i++) + if(services[i].name == s) + return "duplicate service name"; + return nil; +} + +getindexdata(id: int, filterid: int): array of byte +{ + f: ref Filter; + if(filterid != Styx->NOFID) + f = Filter.find(filterid); + s := ""; + for(i := 0; i < nservices; i++){ + svc := services[i]; + if(svc == nil || svc.name == nil) + continue; + if(id == -1){ + if(f != nil && !f.match(svc.attrs)) + continue; + }else if(svc.id != id) + continue; + s += sys->sprint("%q", services[i].name); + for(a := svc.attrs; a != nil; a = tl a){ + (attr, val) := hd a; + s += sys->sprint(" %q %q", attr, val); + } + s[len s] = '\n'; + } + return array of byte s; +} + +navigator(navops: chan of ref Navop) +{ + while((m := <-navops) != nil){ + path := int m.path; + pick n := m { + Stat => + n.reply <-= dirgen(int n.path); + Walk => + name := n.name; + case path & Mask { + Qroot => + case name{ + ".." => + ; # nop + "new" => + path = Qnew; + "index" => + path = Qindex; + "event" => + path = Qevent; + "find" => + path = Qfind; + * => + for(i := 0; i < nservices; i++) + if(services[i].name == name){ + path = (services[i].id << Shift) | Qsvc; + break; + } + if(i == nservices){ + n.reply <-= (nil, Enotfound); + continue; + } + } + * => + if(name == ".."){ + path = Qroot; + break; + } + n.reply <-= (nil, Enotdir); + continue; + } + n.reply <-= dirgen(path); + Readdir => + d: array of int; + case path & Mask { + Qroot => + Nstatic: con 3; + d = array[Nstatic + nservices] of int; + d[0] = Qnew; + d[1] = Qindex; + d[2] = Qfind; +# d[3] = Qevent; + for(i := 0; i < nservices; i++) + if(services[i].name != nil) + d[i + Nstatic] = (services[i].id<<Shift) | Qsvc; + } + if(d == nil){ + n.reply <-= (nil, Enotdir); + break; + } + for (i := n.offset; i < len d; i++) + n.reply <-= dirgen(d[i]); + n.reply <-= (nil, nil); + } + } +} + +dirgen(path: int): (ref Sys->Dir, string) +{ + name: string; + perm: int; + svc: ref Service; + case path & Mask { + Qroot => + name = "."; + perm = 8r777|Sys->DMDIR; + Qnew => + name = "new"; + perm = 8r666; + Qindex => + name = "index"; + perm = 8r444; + Qevent => + name = "event"; + perm = 8r444; + Qfind => + name = "find"; + perm = 8r666; + Qsvc => + id := path >> Shift; + for(i := 0; i < nservices; i++) + if(services[i].id == id) + break; + if(i >= nservices) + return (nil, Enotfound); + svc = services[i]; + name = svc.name; + perm = 8r644; + * => + return (nil, Enotfound); + } + return (dir(path, name, perm, svc), nil); +} + +dir(path: int, name: string, perm: int, svc: ref Service): ref Sys->Dir +{ + d := ref sys->zerodir; + d.qid.path = big path; + if(perm & Sys->DMDIR) + d.qid.qtype = Sys->QTDIR; + d.mode = perm; + d.name = name; + if(svc != nil){ + d.uid = svc.owner; + d.gid = svc.owner; + d.atime = svc.atime; + d.mtime = svc.mtime; + d.qid.vers = svc.vers; + }else{ + d.uid = "registry"; + d.gid = "registry"; + d.atime = startdate; + d.mtime = startdate; + if(path == Qroot) + d.qid.vers = rootvers; + } + return d; +} + +blanksvc: Service; +Service.new(owner: string): ref Service +{ + if(nservices == len services){ + s := array[nservices * 3 / 2] of ref Service; + s[0:] = services; + services = s; + } + svc := ref blanksvc; + svc.id = idseq++; + svc.owner = owner; + svc.atime = now; + svc.mtime = now; + + services[nservices] = svc; + svc.slot = nservices; + nservices++; + rootvers++; + return svc; +} + +Service.find(id: int): ref Service +{ + for(i := 0; i < nservices; i++) + if(services[i].id == id) + return services[i]; + return nil; +} + +Service.remove(svc: self ref Service) +{ + slot := svc.slot; + services[slot] = nil; + nservices--; + rootvers++; + if(slot != nservices){ + services[slot] = services[nservices]; + services[slot].slot = slot; + services[nservices] = nil; + } +} + +Service.get(svc: self ref Service, attr: string): string +{ + for(a := svc.attrs; a != nil; a = tl a) + if((hd a).t0 == attr) + return (hd a).t1; + return nil; +} + +Service.set(svc: self ref Service, attr, val: string) +{ + for(a := svc.attrs; a != nil; a = tl a) + if((hd a).t0 == attr) + break; + if(a == nil){ + svc.attrs = (attr, val) :: svc.attrs; + return; + } + attrs := (attr, val) :: tl a; + for(a = svc.attrs; a != nil; a = tl a){ + if((hd a).t0 == attr) + break; + attrs = hd a :: attrs; + } + svc.attrs = attrs; +} + +Filter.new(id: int): ref Filter +{ + f := ref Filter(id, nil); + filters = f :: filters; + return f; +} + +Filter.find(id: int): ref Filter +{ + if(id != Styx->NOFID) + for(fl := filters; fl != nil; fl = tl fl) + if((hd fl).id == id) + return hd fl; + return nil; +} + +Filter.set(f: self ref Filter, a: array of (string, string)) +{ + f.attrs = a; +} + +Filter.remove(f: self ref Filter) +{ + rl: list of ref Filter; + for(l := filters; l != nil; l = tl l) + if((hd l).id != f.id) + rl = hd l :: rl; + filters = rl; +} + +Filter.match(f: self ref Filter, attrs: list of (string, string)): int +{ + for(i := 0; i < len f.attrs; i++){ + (qn, qv) := f.attrs[i]; + for(al := attrs; al != nil; al = tl al){ + (n, v) := hd al; + if(n == qn && (qv == "*" || v == qv)) + break; + } + if(al == nil) + break; + } + return i == len f.attrs; +} + +dbload(db: ref Db) +{ + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + (e, ptr) = db.find(ptr, "service"); + if(e == nil) + break; + svcname := e.findfirst("service"); + if(svcname == nil || svcnameok(svcname) != nil) + continue; + svc := Service.new("registry"); # TO DO: read user's name + svc.name = svcname; + svc.fid = Styx->NOFID; + for(l := e.lines; l != nil; l = tl l){ + for(al := (hd l).pairs; al != nil; al = tl al){ + a := hd al; + if(a.attr != "service") + svc.set(a.attr, a.val); + } + } + } +} + +# return index i >= start such that +# s[i-1] == eoc, or len s if no such index exists. +# eoc shouldn't be ' +qsplit(s: string, start: int, eoc: int): int +{ + inq := 0; + for(i := start; i < len s;){ + c := s[i++]; + if(inq){ + if(c == '\'' && i < len s){ + if(s[i] == '\'') + i++; + else + inq = 0; + } + }else{ + if(c == eoc) + return i; + if(c == '\'') + inq = 1; + } + } + return i; +} diff --git a/appl/cmd/ndb/regquery.b b/appl/cmd/ndb/regquery.b new file mode 100644 index 00000000..f7f32462 --- /dev/null +++ b/appl/cmd/ndb/regquery.b @@ -0,0 +1,104 @@ +implement Regquery; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "arg.m"; + +Regquery: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + cantload(Bufio->PATH); + str = load String String->PATH; + if(str == nil) + cantload(String->PATH); + + mntpt := "/mnt/registry"; + arg := load Arg Arg->PATH; + if(arg == nil) + cantload(Arg->PATH); + arg->init(args); + arg->setusage("regquery [-m mntpt] [-n] [attr val attr val ...]"); + namesonly := 0; + while((c := arg->opt()) != 0) + case c { + 'm' => mntpt = arg->earg(); + 'n' => namesonly = 1; + * => arg->usage(); + } + args = arg->argv(); + arg = nil; + + finder := mntpt+"/find"; + if(args != nil){ + s := ""; + for(; args != nil; args = tl args) + s += sys->sprint(" %q", hd args); + if(s != nil) + s = s[1:]; + regquery(finder, s, namesonly); + }else{ + f := bufio->fopen(sys->fildes(0), Sys->OREAD); + if(f == nil) + exit; + for(;;){ + sys->print("> "); + s := f.gets('\n'); + if(s == nil) + break; + regquery(finder, s[0:len s-1], namesonly); + } + } +} + +cantload(s: string) +{ + sys->fprint(sys->fildes(2), "regquery: can't load %s: %r\n", s); + raise "fail:load"; +} + +regquery(server: string, addr: string, namesonly: int) +{ + fd := sys->open(server, Sys->ORDWR); + if(fd == nil){ + sys->fprint(sys->fildes(2), "regquery: can't open %s: %r\n", server); + raise "fail:open"; + } + stdout := sys->fildes(1); + b := array of byte addr; + if(sys->write(fd, b, len b) >= 0){ + sys->seek(fd, big 0, Sys->SEEKSTART); + if(namesonly){ + bio := bufio->fopen(fd, Bufio->OREAD); + while((s := bio.gets('\n')) != nil){ + l := str->unquoted(s); + if(l != nil) + sys->print("%s\n", hd l); + } + return; + }else{ + buf := array[Sys->ATOMICIO] of byte; + while((n := sys->read(fd, buf, len buf)) > 0) + sys->print("%s", string buf[0:n]); + if(n == 0) + return; + } + } + sys->fprint(sys->fildes(2), "regquery: %r\n"); +} |
