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; inulldir; 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(s); 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[512] 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; iprint("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; dnetw := netw; if(s != nil){ (divert, err) := ipattr->findnetattr(ndb, "ip", s, "divert-"+netw); if(err == nil && divert != nil){ dnetw = divert; if(!isnetwork(dnetw)) return (nil, "network unavailable "+dnetw); # XXX should only give up if all addresses fail? } } if(s != "") s[len s] = '!'; s += service; repl = mntpt+"/"+dnetw+"/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; }