implement Dial; include "sys.m"; sys: Sys; include "dial.m"; # # the dialstring is of the form '[/net/]proto!dest' # dial(addr: string, local: string): ref Connection { if(sys == nil) sys = load Sys Sys->PATH; (netdir, proto, rem) := dialparse(addr); if(netdir != nil) return csdial(netdir, proto, rem, local); c := csdial("/net", proto, rem, local); if(c != nil) return c; err := sys->sprint("%r"); if(lookstr(err, "refused") >= 0) return nil; c = csdial("/net.alt", proto, rem, local); if(c != nil) return c; # ignore the least precise one alterr := sys->sprint("%r"); if(lookstr(alterr, "translate")>=0 || lookstr(alterr, "does not exist")>=0) sys->werrstr(err); else sys->werrstr(alterr); return nil; } # # ask the connection server to translate # csdial(netdir: string, proto: string, rem: string, local: string): ref Connection { fd := sys->open(netdir+"/cs", Sys->ORDWR); if(fd == nil){ # no connection server, don't translate return call(netdir+"/"+proto+"/clone", rem, local); } if(sys->fprint(fd, "%s!%s", proto, rem) < 0) return nil; # try each recipe until we get one that works besterr, err: string; sys->seek(fd, big 0, 0); for(;;){ (clonefile, addr) := csread(fd); if(clonefile == nil) break; c := call(redir(clonefile, netdir), addr, local); if(c != nil) return c; err = sys->sprint("%r"); if(lookstr(err, "does not exist") < 0) besterr = err; } if(besterr != nil) sys->werrstr(besterr); else sys->werrstr(err); return nil; } call(clonefile: string, dest: string, local: string): ref Connection { (cfd, convdir) := clone(clonefile); if(cfd == nil) return nil; if(local != nil) rv := sys->fprint(cfd, "connect %s %s", dest, local); else rv = sys->fprint(cfd, "connect %s", dest); if(rv < 0) return nil; fd := sys->open(convdir+"/data", Sys->ORDWR); if(fd == nil) return nil; return ref Connection(fd, cfd, convdir); } clone(clonefile: string): (ref Sys->FD, string) { pdir := parent(clonefile); if(pdir == nil){ sys->werrstr(sys->sprint("bad clone file name: %q", clonefile)); return (nil, nil); } cfd := sys->open(clonefile, Sys->ORDWR); if(cfd == nil) return (nil, nil); lno := readchan(cfd); if(lno == nil) return (nil, nil); return (cfd, pdir+"/"+lno); } readchan(cfd: ref Sys->FD): string { buf := array[Sys->NAMEMAX] of byte; n := sys->read(cfd, buf, len buf); if(n < 0) return nil; if(n == 0){ sys->werrstr("empty clone file"); return nil; } return string int string buf[0: n]; } redir(old: string, newdir: string): string { # because cs is in a different name space, replace the mount point # assumes the mount point is directory in root (eg, /net/proto/clone) if(len old > 1 && old[0] == '/'){ p := lookc(old[1:], '/'); if(p >= 0) return newdir+"/"+old[1+p+1:]; } return newdir+"/"+old; } lookc(s: string, c: int): int { for(i := 0; i < len s; i++) if(s[i] == c) return i; return -1; } backc(s: string, i: int, c: int): int { if(i >= len s) return -1; while(i >= 0 && s[i] != c) i--; return i; } lookstr(s: string, t: string): int { lt := len t; # we know it's not zero Search: for(i := 0; i <= len s - lt; i++){ for(j := 0; j < lt; j++) if(s[i+j] != t[j]) continue Search; return i; } return -1; } # # [[/netdir/]proto!]remainder # dialparse(addr: string): (string, string, string) { p := lookc(addr, '!'); if(p < 0) return (nil, "net", addr); if(addr[0] != '/' && addr[0] != '#') return (nil, addr[0: p], addr[p+1:]); p2 := backc(addr, p, '/'); if(p2 <= 0) return (addr[0: p], "net", addr[p+1:]); # plan 9 returns proto "" return (addr[0: p2], addr[p2+1: p], addr[p+1:]); } # # announce a network service # announce(addr: string): ref Connection { if(sys == nil) sys = load Sys Sys->PATH; (naddr, clonefile) := nettrans(addr); if(naddr == nil) return nil; (ctl, convdir) := clone(clonefile); if(ctl == nil){ sys->werrstr(sys->sprint("announce %r")); return nil; } if(sys->fprint(ctl, "announce %s", naddr) < 0){ sys->werrstr(sys->sprint("announce writing %s: %r", clonefile)); return nil; } return ref Connection(nil, ctl, convdir); } # # listen for an incoming call on announced connection # listen(ac: ref Connection): ref Connection { if(sys == nil) sys = load Sys Sys->PATH; pdir := parent(ac.dir); # ac.dir should be /netdir/N if(pdir == nil){ sys->werrstr(sys->sprint("listen directory format: %q", ac.dir)); return nil; } ctl := sys->open(ac.dir+"/listen", Sys->ORDWR); if(ctl == nil){ sys->werrstr(sys->sprint("listen opening %s: %r", ac.dir+"/listen")); return nil; } lno := readchan(ctl); if(lno == nil){ sys->werrstr(sys->sprint("listen reading %s/listen: %r", ac.dir)); return nil; } return ref Connection(nil, ctl, pdir+"/"+lno); } # # translate an address [[/netdir/]proto!rem] using /netdir/cs # returning (newaddress, clonefile) # nettrans(addr: string): (string, string) { (netdir, proto, rem) := dialparse(addr); if(proto == nil || proto == "net"){ sys->werrstr(sys->sprint("bad dial string: %s", addr)); return (nil, nil); } if(netdir == nil) netdir = "/net"; # try to translate using connection server fd := sys->open(netdir+"/cs", Sys->ORDWR); if(fd == nil){ # use it untranslated if(rem == nil){ sys->werrstr(sys->sprint("bad dial string: %s", addr)); return (nil, nil); } return (rem, netdir+"/"+proto+"/clone"); } if(sys->fprint(fd, "%s!%s", proto, rem) < 0) return (nil, nil); sys->seek(fd, big 0, 0); (clonefile, naddr) := csread(fd); if(clonefile == nil) return (nil, nil); return (naddr, redir(clonefile, netdir)); } csread(fd: ref Sys->FD): (string, string) { buf := array[Sys->NAMEMAX] of byte; n := sys->read(fd, buf, len buf); if(n < 0) return (nil, nil); line := string buf[0: n]; p := lookc(line, ' '); if(p < 0) return (nil, nil); if(p == 0){ sys->werrstr("cs: no translation"); return (nil, nil); } return (line[0:p], line[p+1:]); } # # accept a call, return an fd to the open data file # accept(c: ref Connection): ref Sys->FD { if(sys == nil) sys = load Sys Sys->PATH; #sys->fprint(c.cfd, "accept %s", lastname(c.dir)); # ignore return value, network might not need accepts return sys->open(c.dir+"/data", Sys->ORDWR); } # # reject a call, tell device the reason for the rejection # reject(c: ref Connection, why: string): int { if(sys == nil) sys = load Sys Sys->PATH; if(sys->fprint(c.cfd, "reject %s %q", lastname(c.dir), why) < 0) return -1; return 0; } lastname(dir: string): string { p := backc(dir, len dir-1, '/'); if(p < 0) return dir; return dir[p+1:]; # N in /net/N } parent(dir: string): string { p := backc(dir, len dir-1, '/'); if(p < 0) return nil; return dir[0: p]; } netmkaddr(addr, net, svc: string): string { if(sys == nil) sys = load Sys Sys->PATH; if(net == nil) net = "net"; (n, nil) := sys->tokenize(addr, "!"); if(n <= 1){ if(svc== nil) return sys->sprint("%s!%s", net, addr); return sys->sprint("%s!%s!%s", net, addr, svc); } if(svc == nil || n > 2) return addr; return sys->sprint("%s!%s", addr, svc); } netinfo(c: ref Connection): ref Conninfo { if(sys == nil) sys = load Sys Sys->PATH; if((dir := c.dir) == nil){ if(c.dfd == nil) return nil; dir = parent(sys->fd2path(c.dfd)); if(dir == nil) return nil; } ci := ref Conninfo; ci.dir = dir; ci.root = parent(dir); while((p := parent(ci.root)) != nil && p != "/") ci.root = p; (ok, d) := sys->stat(ci.dir); if(ok >= 0) ci.spec = sys->sprint("#%c%d", d.dtype, d.dev); (ci.lsys, ci.lserv) = getendpoint(ci.dir, "local"); (ci.rsys, ci.rserv) = getendpoint(ci.dir, "remote"); p = parent(ci.dir); if(p == nil) return nil; if(len p >= 5 && p[0:5] == "/net/") p = p[5:]; ci.laddr = sys->sprint("%s!%s!%s", p, ci.lsys, ci.lserv); ci.raddr = sys->sprint("%s!%s!%s", p, ci.rsys, ci.rserv); return ci; } getendpoint(dir: string, file: string): (string, string) { fd := sys->open(dir+"/"+file, Sys->OREAD); buf := array[128] of byte; if(fd == nil || (n := sys->read(fd, buf, len buf)) <= 0) return ("???", "???"); # compatible, but probably poor defaults if(n > 0 && buf[n-1] == byte '\n') n--; s := string buf[0: n]; p := lookc(s, '!'); if(p < 0) return (s, "???"); return (s[0:p], s[p+1:]); }