code: 9ferno

ref: 57446af9a447c3a81414131f2407a9ca6f6a1a94
dir: /appl/lib/dial.b/

View raw version
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:]);
}