#include <u.h>
#include <libc.h>
enum
{
Maxconcurr = 4,
Maxstring = 128,
};
typedef struct DS DS;
typedef struct Conn Conn;
struct DS {
/* dist string */
char *netdir;
char *proto;
char *rem;
/* other args */
char *local;
char *dir;
int *cfdp;
};
struct Conn {
Conn *next;
int pid;
int cfd;
int dfd;
char dest[Maxstring];
char dir[NETPATHLEN];
};
static Conn*
openconn(char *clone, char *dest, char *netdir)
{
char *x, *p, *e;
Conn *c;
int n;
if((c = malloc(sizeof(Conn))) == nil)
return nil;
c->next = nil;
c->pid = 0;
c->cfd = -1;
c->dfd = -1;
snprint(c->dest, sizeof c->dest, "%s", dest);
if(netdir){
if(*clone == '/' && (p = strchr(clone+1, '/')))
clone = ++p;
snprint(c->dir, sizeof c->dir, "%s/%s", netdir, clone);
} else
snprint(c->dir, sizeof c->dir, "%s", clone);
e = c->dir + sizeof c->dir;
if((p = strrchr(c->dir, '/')) == nil)
goto err;
if((c->cfd = open(c->dir, ORDWR)) < 0)
goto err;
if((n = (e - p)-1) <= 0)
goto err;
if((n = read(c->cfd, p, n)) <= 0)
goto err;
p[n] = 0;
for(x = p; *x == ' '; x++)
;
snprint(p, e - p, "/%ld/data", strtoul(x, 0, 0));
if((c->dfd = open(c->dir, ORDWR)) < 0)
goto err;
if(p = strrchr(c->dir, '/'))
*p = 0;
return c;
err:
if(c->cfd >= 0)
close(c->cfd);
if(c->dfd >= 0)
close(c->dfd);
free(c);
return nil;
}
static char*
readcs(int cs, char *buf, int nbuf, char **destp)
{
char *p;
int n;
if((n = read(cs, buf, nbuf-1)) <= 0)
return nil;
if(buf[n-1] == '\n')
n--;
buf[n] = 0;
if((p = strchr(buf, ' ')) == nil)
return nil;
*p++ = 0;
if(destp)
*destp = p;
return buf;
}
static Conn*
getconn(int cs, char *buf, int nbuf, char *netdir)
{
char *clone, *dest;
if(clone = readcs(cs, buf, nbuf, &dest))
return openconn(clone, dest, netdir);
return nil;
}
static int
connect(Conn *c, char *local)
{
if(local)
return fprint(c->cfd, "connect %s %s", c->dest, local) > 0;
else
return fprint(c->cfd, "connect %s", c->dest) > 0;
}
static int
aconnect(Conn *c, char *local)
{
if((c->pid = rfork(RFPROC)) < 0)
return 0;
else if(c->pid > 0)
return 1;
notify(nil);
if(connect(c, local))
_exits(nil);
_exits("%r");
return -1;
}
static int
canfork(char *buf, int nbuf)
{
int fd;
snprint(buf, nbuf, "/proc/%d/note", getpid());
if((fd = open(buf, OWRITE)) >= 0)
close(fd);
return fd >= 0;
}
static int
csdial(DS *ds)
{
char buf[Maxstring+NETPATHLEN+4];
Conn *conns, *winner, *c;
int cs, ret, kids, more;
conns = winner = nil;
snprint(buf, sizeof buf, "%s/cs", ds->netdir);
if((cs = open(buf, ORDWR)) < 0){
snprint(buf, sizeof buf, "%s/%s/clone", ds->netdir, ds->proto);
if((conns = openconn(buf, ds->rem, nil)) == nil)
goto out;
if(connect(conns, ds->local))
winner = conns;
goto out;
}
if(fprint(cs, "%s!%s", ds->proto, ds->rem) < 0)
goto out;
seek(cs, 0, 0);
if((conns = getconn(cs, buf, sizeof buf, ds->netdir)) == nil){
werrstr("no address to dial");
goto out;
}
conns->next = getconn(cs, buf, sizeof buf, ds->netdir);
if(conns->next == nil || !canfork(buf, sizeof buf)){
if(connect(c = conns, ds->local)){
winner = c;
goto out;
}
if((c = c->next) == nil)
goto out;
if(connect(c, ds->local)){
winner = c;
goto out;
}
while(c->next = getconn(cs, buf, sizeof buf, ds->netdir)){
if(connect(c = c->next, ds->local)){
winner = c;
goto out;
}
}
goto out;
}
more = 1;
kids = 0;
if(aconnect(conns, ds->local))
kids++;
if(aconnect(conns->next, ds->local))
kids++;
for(;;){
Waitmsg *m;
while(more && kids < Maxconcurr){
if((c = getconn(cs, buf, sizeof buf, ds->netdir)) == nil){
more = 0;
break;
}
c->next = conns;
conns = c;
if(aconnect(c, ds->local))
kids++;
}
if(kids == 0)
break;
if(m = wait()){
for(c = conns; c; c = c->next){
if(c->pid != m->pid)
continue;
c->pid = 0;
--kids;
if(m->msg[0]){
char *p;
if(p = strchr(m->msg, ':'))
p++;
else
p = m->msg;
while(*p == ' ')
p++;
werrstr("%s", p);
} else if(winner)
fprint(c->cfd, "hangup");
else
winner = c;
break;
}
free(m);
}
if(winner || m == nil){
more = 0;
for(c = conns; c; c = c->next)
if(c->pid)
postnote(PNPROC, c->pid, "die");
}
}
out:
if(cs >= 0)
close(cs);
if(c = winner){
if(ds->dir)
strncpy(ds->dir, c->dir, NETPATHLEN);
if(ds->cfdp)
*ds->cfdp = c->cfd;
else
close(c->cfd);
ret = c->dfd;
} else
ret = -1;
while(c = conns){
conns = c->next;
if(c != winner){
close(c->cfd);
close(c->dfd);
}
free(c);
}
return ret;
}
int
dial(char *dest, char *local, char *dir, int *cfdp)
{
char buf[Maxstring], *p, *x;
int ret;
DS ds;
ds.local = local;
ds.dir = dir;
ds.cfdp = cfdp;
snprint(buf, sizeof buf, "%s", dest);
if((p = strchr(buf, '!')) == 0) {
ds.netdir = 0;
ds.proto = "net";
ds.rem = buf;
} else {
if(buf[0] != '/' && buf[0] != '#'){
ds.netdir = 0;
ds.proto = buf;
} else {
for(x = p; *x != '/'; x--)
;
*x++ = 0;
ds.netdir = buf;
ds.proto = x;
}
*p++ = 0;
ds.rem = p;
}
if(ds.netdir)
return csdial(&ds);
ds.netdir = "/net";
if((ret = csdial(&ds)) < 0){
char err[ERRMAX];
rerrstr(err, sizeof err);
if(strstr(err, "refused"))
return ret;
ds.netdir = "/net.alt";
if((ret = csdial(&ds)) < 0){
char alterr[ERRMAX];
/* use previous error if /net.alt was not available */
rerrstr(alterr, sizeof alterr);
if(strstr(alterr, "translate") || strstr(alterr, "does not exist"))
werrstr("%s", err);
}
}
return ret;
}
|