summaryrefslogtreecommitdiff
path: root/appl/cmd/ndb/cs.b
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/ndb/cs.b
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/cmd/ndb/cs.b')
-rw-r--r--appl/cmd/ndb/cs.b676
1 files changed, 676 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;
+}