summaryrefslogtreecommitdiff
path: root/appl/lib/dial.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/lib/dial.b')
-rw-r--r--appl/lib/dial.b384
1 files changed, 384 insertions, 0 deletions
diff --git a/appl/lib/dial.b b/appl/lib/dial.b
new file mode 100644
index 00000000..42f9119b
--- /dev/null
+++ b/appl/lib/dial.b
@@ -0,0 +1,384 @@
+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(clone == 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:]);
+}