plan9fox/sys/src/cmd/sshnet.c

1287 lines
20 KiB
C
Raw Normal View History

/*
* SSH network file system.
* Presents remote TCP stack as /net-style file system.
*/
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ndb.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
typedef struct Client Client;
typedef struct Msg Msg;
enum
{
Qroot,
Qcs,
Qtcp,
Qclone,
Qn,
Qctl,
Qdata,
Qlocal,
Qremote,
Qstatus,
};
#define PATH(type, n) ((type)|((n)<<8))
#define TYPE(path) ((int)(path) & 0xFF)
#define NUM(path) ((uint)(path)>>8)
Channel *sshmsgchan; /* chan(Msg*) */
Channel *fsreqchan; /* chan(Req*) */
Channel *fsreqwaitchan; /* chan(nil) */
Channel *fsclunkchan; /* chan(Fid*) */
Channel *fsclunkwaitchan; /* chan(nil) */
ulong time0;
enum
{
Closed,
Dialing,
Established,
Teardown,
};
char *statestr[] = {
"Closed",
"Dialing",
"Established",
"Teardown",
};
struct Client
{
int ref;
int state;
int num;
int servernum;
char *connect;
int sendpkt;
int sendwin;
int recvwin;
int recvacc;
Req *wq;
Req **ewq;
Req *rq;
Req **erq;
Msg *mq;
Msg **emq;
};
enum {
MSG_CHANNEL_OPEN = 90,
MSG_CHANNEL_OPEN_CONFIRMATION,
MSG_CHANNEL_OPEN_FAILURE,
MSG_CHANNEL_WINDOW_ADJUST,
MSG_CHANNEL_DATA,
MSG_CHANNEL_EXTENDED_DATA,
MSG_CHANNEL_EOF,
MSG_CHANNEL_CLOSE,
MSG_CHANNEL_REQUEST,
MSG_CHANNEL_SUCCESS,
MSG_CHANNEL_FAILURE,
MaxPacket = 1<<15,
WinPackets = 8,
};
struct Msg
{
Msg *link;
uchar *rp;
uchar *wp;
uchar *ep;
uchar buf[MaxPacket];
};
#define PUT4(p, u) (p)[0] = (u)>>24, (p)[1] = (u)>>16, (p)[2] = (u)>>8, (p)[3] = (u)
#define GET4(p) (u32int)(p)[3] | (u32int)(p)[2]<<8 | (u32int)(p)[1]<<16 | (u32int)(p)[0]<<24
int nclient;
Client **client;
char *mtpt;
int sshfd;
int localport;
char localip[] = "::";
int
vpack(uchar *p, int n, char *fmt, va_list a)
{
uchar *p0 = p, *e = p+n;
u32int u;
void *s;
int c;
for(;;){
switch(c = *fmt++){
case '\0':
return p - p0;
case '_':
if(++p > e) goto err;
break;
case '.':
*va_arg(a, void**) = p;
break;
case 'b':
if(p >= e) goto err;
*p++ = va_arg(a, int);
break;
case '[':
case 's':
s = va_arg(a, void*);
u = va_arg(a, int);
if(c == 's'){
if(p+4 > e) goto err;
PUT4(p, u), p += 4;
}
if(u > e-p) goto err;
memmove(p, s, u);
p += u;
break;
case 'u':
u = va_arg(a, int);
if(p+4 > e) goto err;
PUT4(p, u), p += 4;
break;
}
}
err:
return -1;
}
int
vunpack(uchar *p, int n, char *fmt, va_list a)
{
uchar *p0 = p, *e = p+n;
u32int u;
void *s;
for(;;){
switch(*fmt++){
case '\0':
return p - p0;
case '_':
if(++p > e) goto err;
break;
case '.':
*va_arg(a, void**) = p;
break;
case 'b':
if(p >= e) goto err;
*va_arg(a, int*) = *p++;
break;
case 's':
if(p+4 > e) goto err;
u = GET4(p), p += 4;
if(u > e-p) goto err;
*va_arg(a, void**) = p;
*va_arg(a, int*) = u;
p += u;
break;
case '[':
s = va_arg(a, void*);
u = va_arg(a, int);
if(u > e-p) goto err;
memmove(s, p, u);
p += u;
break;
case 'u':
if(p+4 > e) goto err;
u = GET4(p);
*va_arg(a, int*) = u;
p += 4;
break;
}
}
err:
return -1;
}
Msg*
allocmsg(void)
{
Msg *m;
m = emalloc9p(sizeof(Msg));
m->link = nil;
m->rp = m->wp = m->buf;
m->ep = m->rp + sizeof(m->buf);
return m;
}
Msg*
pack(Msg *m, char *fmt, ...)
{
va_list a;
int n;
if(m == nil)
m = allocmsg();
va_start(a, fmt);
n = vpack(m->wp, m->ep - m->wp, fmt, a);
if(n < 0)
sysfatal("pack faild");
m->wp += n;
va_end(a);
return m;
}
int
unpack(Msg *m, char *fmt, ...)
{
va_list a;
int n;
va_start(a, fmt);
n = vunpack(m->rp, m->wp - m->rp, fmt, a);
if(n > 0)
m->rp += n;
va_end(a);
return n;
}
void
sendmsg(Msg *m)
{
int n;
if(m == nil)
return;
n = m->wp - m->rp;
if(n > 0){
if(write(sshfd, m->rp, n) != n)
sysfatal("write to ssh failed: %r");
}
free(m);
}
int
newclient(void)
{
int i;
Client *c;
for(i=0; i<nclient; i++)
if(client[i]->ref==0 && client[i]->state == Closed)
return i;
if(nclient%16 == 0)
client = erealloc9p(client, (nclient+16)*sizeof(client[0]));
c = emalloc9p(sizeof(Client));
memset(c, 0, sizeof(*c));
c->num = nclient;
client[nclient++] = c;
return c->num;
}
Client*
getclient(int num)
{
if(num < 0 || num >= nclient)
return nil;
return client[num];
}
void
adjustwin(Client *c, int len)
{
c->recvacc += len;
if(c->recvacc >= MaxPacket*WinPackets/2 || c->recvwin < MaxPacket){
sendmsg(pack(nil, "buu", MSG_CHANNEL_WINDOW_ADJUST, c->servernum, c->recvacc));
c->recvacc = 0;
}
c->recvwin += len;
}
void
senddata(Client *c, void *data, int len)
{
sendmsg(pack(nil, "bus", MSG_CHANNEL_DATA, c->servernum, (char*)data, len));
c->sendwin -= len;
}
void
queuerreq(Client *c, Req *r)
{
if(c->rq==nil)
c->erq = &c->rq;
*c->erq = r;
r->aux = nil;
c->erq = (Req**)&r->aux;
}
void
queuermsg(Client *c, Msg *m)
{
if(c->mq==nil)
c->emq = &c->mq;
*c->emq = m;
m->link = nil;
c->emq = (Msg**)&m->link;
}
void
matchrmsgs(Client *c)
{
Req *r;
Msg *m;
int n, rm;
while(c->rq != nil && c->mq != nil){
r = c->rq;
c->rq = r->aux;
rm = 0;
m = c->mq;
n = r->ifcall.count;
if(n >= m->wp - m->rp){
n = m->wp - m->rp;
c->mq = m->link;
rm = 1;
}
memmove(r->ofcall.data, m->rp, n);
if(rm)
free(m);
else
m->rp += n;
r->ofcall.count = n;
respond(r, nil);
adjustwin(c, n);
}
}
void
queuewreq(Client *c, Req *r)
{
if(c->wq==nil)
c->ewq = &c->wq;
*c->ewq = r;
r->aux = nil;
c->ewq = (Req**)&r->aux;
}
void
procwreqs(Client *c)
{
Req *r;
int n;
while((r = c->wq) != nil && (n = c->sendwin) > 0){
if(n > c->sendpkt)
n = c->sendpkt;
if(r->ifcall.count > n){
senddata(c, r->ifcall.data, n);
r->ifcall.count -= n;
memmove(r->ifcall.data, (char*)r->ifcall.data + n, r->ifcall.count);
continue;
}
c->wq = (Req*)r->aux;
r->aux = nil;
senddata(c, r->ifcall.data, r->ifcall.count);
r->ofcall.count = r->ifcall.count;
respond(r, nil);
}
}
Req*
findreq(Client *c, Req *r)
{
Req **l;
for(l=&c->rq; *l; l=(Req**)&(*l)->aux){
if(*l == r){
*l = r->aux;
if(*l == nil)
c->erq = l;
return r;
}
}
for(l=&c->wq; *l; l=(Req**)&(*l)->aux){
if(*l == r){
*l = r->aux;
if(*l == nil)
c->ewq = l;
return r;
}
}
return nil;
}
void
dialedclient(Client *c)
{
Req *r;
if(r=c->wq){
if(r->aux != nil)
sysfatal("more than one outstanding dial request (BUG)");
if(c->state == Established)
respond(r, nil);
else
respond(r, "connect failed");
}
c->wq = nil;
}
void
teardownclient(Client *c)
{
c->state = Teardown;
sendmsg(pack(nil, "bu", MSG_CHANNEL_EOF, c->servernum));
}
void
hangupclient(Client *c)
{
Req *r, *next;
Msg *m, *mnext;
c->state = Closed;
for(m=c->mq; m; m=mnext){
mnext = m->link;
free(m);
}
c->mq = nil;
for(r=c->rq; r; r=next){
next = r->aux;
respond(r, "hangup on network connection");
}
c->rq = nil;
for(r=c->wq; r; r=next){
next = r->aux;
respond(r, "hangup on network connection");
}
c->wq = nil;
}
void
closeclient(Client *c)
{
Msg *m, *next;
if(--c->ref)
return;
if(c->rq != nil || c->wq != nil)
sysfatal("ref count reached zero with requests pending (BUG)");
for(m=c->mq; m; m=next){
next = m->link;
free(m);
}
c->mq = nil;
if(c->state != Closed)
teardownclient(c);
}
void
sshreadproc(void*)
{
Msg *m;
int n;
for(;;){
m = allocmsg();
n = read(sshfd, m->rp, m->ep - m->rp);
if(n <= 0)
sysfatal("eof on ssh connection");
m->wp += n;
sendp(sshmsgchan, m);
}
}
typedef struct Tab Tab;
struct Tab
{
char *name;
ulong mode;
};
Tab tab[] =
{
"/", DMDIR|0555,
"cs", 0666,
"tcp", DMDIR|0555,
"clone", 0666,
nil, DMDIR|0555,
"ctl", 0666,
"data", 0666,
"local", 0444,
"remote", 0444,
"status", 0444,
};
static void
fillstat(Dir *d, uvlong path)
{
Tab *t;
memset(d, 0, sizeof(*d));
d->uid = estrdup9p("ssh");
d->gid = estrdup9p("ssh");
d->qid.path = path;
d->atime = d->mtime = time0;
t = &tab[TYPE(path)];
if(t->name)
d->name = estrdup9p(t->name);
else{
d->name = smprint("%ud", NUM(path));
if(d->name == nil)
sysfatal("out of memory");
}
d->qid.type = t->mode>>24;
d->mode = t->mode;
}
static void
fsattach(Req *r)
{
if(r->ifcall.aname && r->ifcall.aname[0]){
respond(r, "invalid attach specifier");
return;
}
r->fid->qid.path = PATH(Qroot, 0);
r->fid->qid.type = QTDIR;
r->fid->qid.vers = 0;
r->ofcall.qid = r->fid->qid;
respond(r, nil);
}
static void
fsstat(Req *r)
{
fillstat(&r->d, r->fid->qid.path);
respond(r, nil);
}
static int
rootgen(int i, Dir *d, void*)
{
i += Qroot+1;
if(i <= Qtcp){
fillstat(d, i);
return 0;
}
return -1;
}
static int
tcpgen(int i, Dir *d, void*)
{
i += Qtcp+1;
if(i < Qn){
fillstat(d, i);
return 0;
}
i -= Qn;
if(i < nclient){
fillstat(d, PATH(Qn, i));
return 0;
}
return -1;
}
static int
clientgen(int i, Dir *d, void *aux)
{
Client *c;
c = aux;
i += Qn+1;
if(i <= Qstatus){
fillstat(d, PATH(i, c->num));
return 0;
}
return -1;
}
static char*
fswalk1(Fid *fid, char *name, Qid *qid)
{
int i, n;
char buf[32];
ulong path;
path = fid->qid.path;
if(!(fid->qid.type&QTDIR))
return "walk in non-directory";
if(strcmp(name, "..") == 0){
switch(TYPE(path)){
case Qn:
qid->path = PATH(Qtcp, NUM(path));
qid->type = tab[Qtcp].mode>>24;
return nil;
case Qtcp:
qid->path = PATH(Qroot, 0);
qid->type = tab[Qroot].mode>>24;
return nil;
case Qroot:
return nil;
default:
return "bug in fswalk1";
}
}
i = TYPE(path)+1;
for(; i<nelem(tab); i++){
if(i==Qn){
n = atoi(name);
snprint(buf, sizeof buf, "%d", n);
if(n < nclient && strcmp(buf, name) == 0){
qid->path = PATH(i, n);
qid->type = tab[i].mode>>24;
return nil;
}
break;
}
if(strcmp(name, tab[i].name) == 0){
qid->path = PATH(i, NUM(path));
qid->type = tab[i].mode>>24;
return nil;
}
if(tab[i].mode&DMDIR)
break;
}
return "directory entry not found";
}
typedef struct Cs Cs;
struct Cs
{
char *resp;
int isnew;
};
static int
ndbfindport(char *p)
{
char *s, *port;
int n;
static Ndb *db;
if(*p == '\0')
return -1;
n = strtol(p, &s, 0);
if(*s == '\0')
return n;
if(db == nil){
db = ndbopen("/lib/ndb/common");
if(db == nil)
return -1;
}
port = ndbgetvalue(db, nil, "tcp", p, "port", nil);
if(port == nil)
return -1;
n = atoi(port);
free(port);
return n;
}
static void
csread(Req *r)
{
Cs *cs;
cs = r->fid->aux;
if(cs->resp==nil){
respond(r, "cs read without write");
return;
}
if(r->ifcall.offset==0){
if(!cs->isnew){
r->ofcall.count = 0;
respond(r, nil);
return;
}
cs->isnew = 0;
}
readstr(r, cs->resp);
respond(r, nil);
}
static void
cswrite(Req *r)
{
int port, nf;
char err[ERRMAX], *f[4], *s, *ns;
Cs *cs;
cs = r->fid->aux;
s = emalloc9p(r->ifcall.count+1);
memmove(s, r->ifcall.data, r->ifcall.count);
s[r->ifcall.count] = '\0';
nf = getfields(s, f, nelem(f), 0, "!");
if(nf != 3){
free(s);
respond(r, "can't translate");
return;
}
if(strcmp(f[0], "tcp") != 0 && strcmp(f[0], "net") != 0){
free(s);
respond(r, "unknown protocol");
return;
}
port = ndbfindport(f[2]);
if(port <= 0){
free(s);
respond(r, "no translation found");
return;
}
ns = smprint("%s/tcp/clone %s!%d", mtpt, f[1], port);
if(ns == nil){
free(s);
rerrstr(err, sizeof err);
respond(r, err);
return;
}
free(s);
free(cs->resp);
cs->resp = ns;
cs->isnew = 1;
r->ofcall.count = r->ifcall.count;
respond(r, nil);
}
static void
ctlread(Req *r, Client *c)
{
char buf[32];
sprint(buf, "%d", c->num);
readstr(r, buf);
respond(r, nil);
}
static void
ctlwrite(Req *r, Client *c)
{
char *f[3], *s;
int nf;
s = emalloc9p(r->ifcall.count+1);
memmove(s, r->ifcall.data, r->ifcall.count);
s[r->ifcall.count] = '\0';
nf = tokenize(s, f, 3);
if(nf == 0){
free(s);
r->ofcall.count = r->ifcall.count;
respond(r, nil);
return;
}
if(strcmp(f[0], "hangup") == 0){
if(c->state != Established)
goto Badarg;
if(nf != 1)
goto Badarg;
teardownclient(c);
r->ofcall.count = r->ifcall.count;
respond(r, nil);
}else if(strcmp(f[0], "connect") == 0){
if(c->state != Closed)
goto Badarg;
if(nf != 2)
goto Badarg;
c->connect = estrdup9p(f[1]);
nf = getfields(f[1], f, nelem(f), 0, "!");
if(nf != 2){
free(c->connect);
c->connect = nil;
goto Badarg;
}
c->sendwin = MaxPacket;
c->recvwin = WinPackets * MaxPacket;
c->recvacc = 0;
c->state = Dialing;
queuewreq(c, r);
sendmsg(pack(nil, "bsuuususu", MSG_CHANNEL_OPEN,
"direct-tcpip", 12,
c->num, c->recvwin, MaxPacket,
f[0], strlen(f[0]), ndbfindport(f[1]),
localip, strlen(localip), localport));
}else{
Badarg:
respond(r, "bad or inappropriate tcp control message");
}
free(s);
}
static void
dataread(Req *r, Client *c)
{
if(c->state != Established){
respond(r, "not connected");
return;
}
queuerreq(c, r);
matchrmsgs(c);
}
static void
datawrite(Req *r, Client *c)
{
if(c->state != Established){
respond(r, "not connected");
return;
}
if(r->ifcall.count == 0){
r->ofcall.count = r->ifcall.count;
respond(r, nil);
return;
}
queuewreq(c, r);
procwreqs(c);
}
static void
localread(Req *r)
{
char buf[128];
snprint(buf, sizeof buf, "%s!%d\n", localip, localport);
readstr(r, buf);
respond(r, nil);
}
static void
remoteread(Req *r, Client *c)
{
char *s;
char buf[128];
s = c->connect;
if(s == nil)
s = "::!0";
snprint(buf, sizeof buf, "%s\n", s);
readstr(r, buf);
respond(r, nil);
}
static void
statusread(Req *r, Client *c)
{
char *s;
s = statestr[c->state];
readstr(r, s);
respond(r, nil);
}
static void
fsread(Req *r)
{
char e[ERRMAX];
ulong path;
path = r->fid->qid.path;
switch(TYPE(path)){
default:
snprint(e, sizeof e, "bug in fsread path=%lux", path);
respond(r, e);
break;
case Qroot:
dirread9p(r, rootgen, nil);
respond(r, nil);
break;
case Qcs:
csread(r);
break;
case Qtcp:
dirread9p(r, tcpgen, nil);
respond(r, nil);
break;
case Qn:
dirread9p(r, clientgen, client[NUM(path)]);
respond(r, nil);
break;
case Qctl:
ctlread(r, client[NUM(path)]);
break;
case Qdata:
dataread(r, client[NUM(path)]);
break;
case Qlocal:
localread(r);
break;
case Qremote:
remoteread(r, client[NUM(path)]);
break;
case Qstatus:
statusread(r, client[NUM(path)]);
break;
}
}
static void
fswrite(Req *r)
{
ulong path;
char e[ERRMAX];
path = r->fid->qid.path;
switch(TYPE(path)){
default:
snprint(e, sizeof e, "bug in fswrite path=%lux", path);
respond(r, e);
break;
case Qcs:
cswrite(r);
break;
case Qctl:
ctlwrite(r, client[NUM(path)]);
break;
case Qdata:
datawrite(r, client[NUM(path)]);
break;
}
}
static void
fsopen(Req *r)
{
static int need[4] = { 4, 2, 6, 1 };
ulong path;
int n;
Tab *t;
Cs *cs;
/*
* lib9p already handles the blatantly obvious.
* we just have to enforce the permissions we have set.
*/
path = r->fid->qid.path;
t = &tab[TYPE(path)];
n = need[r->ifcall.mode&3];
if((n&t->mode) != n){
respond(r, "permission denied");
return;
}
switch(TYPE(path)){
case Qcs:
cs = emalloc9p(sizeof(Cs));
r->fid->aux = cs;
respond(r, nil);
break;
case Qclone:
n = newclient();
path = PATH(Qctl, n);
r->fid->qid.path = path;
r->ofcall.qid.path = path;
if(chatty9p)
fprint(2, "open clone => path=%lux\n", path);
t = &tab[Qctl];
/* fall through */
default:
if(t-tab >= Qn)
client[NUM(path)]->ref++;
respond(r, nil);
break;
}
}
static void
fsflush(Req *r)
{
int i;
for(i=0; i<nclient; i++)
if(findreq(client[i], r->oldreq))
respond(r->oldreq, "interrupted");
respond(r, nil);
}
static void
handlemsg(Msg *m)
{
int chan, win, pkt, n;
Client *c;
char *s;
switch(m->rp[0]){
case MSG_CHANNEL_WINDOW_ADJUST:
if(unpack(m, "_uu", &chan, &n) < 0)
break;
c = getclient(chan);
if(c != nil && c->state==Established){
c->sendwin += n;
procwreqs(c);
}
break;
case MSG_CHANNEL_DATA:
if(unpack(m, "_us", &chan, &s, &n) < 0)
break;
c = getclient(chan);
if(c != nil && c->state==Established){
c->recvwin -= n;
m->rp = (uchar*)s;
queuermsg(c, m);
matchrmsgs(c);
return;
}
break;
case MSG_CHANNEL_EOF:
if(unpack(m, "_u", &chan) < 0)
break;
c = getclient(chan);
if(c != nil){
hangupclient(c);
m->rp = m->wp = m->buf;
sendmsg(pack(m, "bu", MSG_CHANNEL_CLOSE, c->servernum));
return;
}
break;
case MSG_CHANNEL_CLOSE:
if(unpack(m, "_u", &chan) < 0)
break;
c = getclient(chan);
if(c != nil)
hangupclient(c);
break;
case MSG_CHANNEL_OPEN_CONFIRMATION:
if(unpack(m, "_uuuu", &chan, &n, &win, &pkt) < 0)
break;
c = getclient(chan);
if(c == nil || c->state != Dialing)
break;
if(pkt <= 0 || pkt > MaxPacket)
pkt = MaxPacket;
c->sendpkt = pkt;
c->sendwin = win;
c->servernum = n;
c->state = Established;
dialedclient(c);
break;
case MSG_CHANNEL_OPEN_FAILURE:
if(unpack(m, "_uu", &chan, &n) < 0)
break;
c = getclient(chan);
if(c == nil || c->state != Dialing)
break;
c->servernum = n;
c->state = Closed;
dialedclient(c);
break;
}
free(m);
}
void
fsnetproc(void*)
{
ulong path;
Alt a[4];
Cs *cs;
Fid *fid;
Req *r;
Msg *m;
threadsetname("fsthread");
a[0].op = CHANRCV;
a[0].c = fsclunkchan;
a[0].v = &fid;
a[1].op = CHANRCV;
a[1].c = fsreqchan;
a[1].v = &r;
a[2].op = CHANRCV;
a[2].c = sshmsgchan;
a[2].v = &m;
a[3].op = CHANEND;
for(;;){
switch(alt(a)){
case 0:
path = fid->qid.path;
switch(TYPE(path)){
case Qcs:
cs = fid->aux;
if(cs){
free(cs->resp);
free(cs);
}
break;
}
if(fid->omode != -1 && TYPE(path) >= Qn)
closeclient(client[NUM(path)]);
sendp(fsclunkwaitchan, nil);
break;
case 1:
switch(r->ifcall.type){
case Tattach:
fsattach(r);
break;
case Topen:
fsopen(r);
break;
case Tread:
fsread(r);
break;
case Twrite:
fswrite(r);
break;
case Tstat:
fsstat(r);
break;
case Tflush:
fsflush(r);
break;
default:
respond(r, "bug in fsthread");
break;
}
sendp(fsreqwaitchan, 0);
break;
case 2:
handlemsg(m);
break;
}
}
}
static void
fssend(Req *r)
{
sendp(fsreqchan, r);
recvp(fsreqwaitchan); /* avoids need to deal with spurious flushes */
}
static void
fsdestroyfid(Fid *fid)
{
sendp(fsclunkchan, fid);
recvp(fsclunkwaitchan);
}
void
takedown(Srv*)
{
threadexitsall("done");
}
Srv fs =
{
.attach= fssend,
.destroyfid= fsdestroyfid,
.walk1= fswalk1,
.open= fssend,
.read= fssend,
.write= fssend,
.stat= fssend,
.flush= fssend,
.end= takedown,
};
int pfd[2];
int sshargc;
char **sshargv;
void
startssh(void *)
{
char *f;
close(pfd[0]);
dup(pfd[1], 0);
dup(pfd[1], 1);
close(pfd[1]);
if(strncmp(sshargv[0], "./", 2) != 0)
f = smprint("/bin/%s", sshargv[0]);
else
f = sshargv[0];
procexec(nil, f, sshargv);
sysfatal("exec: %r");
}
void
usage(void)
{
fprint(2, "usage: sshnet [-m mtpt] [ssh options]\n");
exits("usage");
}
void
threadmain(int argc, char **argv)
{
char *service;
fmtinstall('H', encodefmt);
mtpt = "/net";
service = nil;
ARGBEGIN{
case 'D':
chatty9p++;
break;
case 'm':
mtpt = EARGF(usage());
break;
case 's':
service = EARGF(usage());
break;
default:
usage();
}ARGEND
if(argc == 0)
usage();
sshargc = argc + 2;
sshargv = emalloc9p(sizeof(char *) * (sshargc + 1));
sshargv[0] = "ssh";
sshargv[1] = "-X";
memcpy(sshargv + 2, argv, argc * sizeof(char *));
pipe(pfd);
sshfd = pfd[0];
procrfork(startssh, nil, mainstacksize, RFFDG|RFNOTEG|RFNAMEG);
close(pfd[1]);
time0 = time(0);
sshmsgchan = chancreate(sizeof(Msg*), 16);
fsreqchan = chancreate(sizeof(Req*), 0);
fsreqwaitchan = chancreate(sizeof(void*), 0);
fsclunkchan = chancreate(sizeof(Fid*), 0);
fsclunkwaitchan = chancreate(sizeof(void*), 0);
procrfork(sshreadproc, nil, mainstacksize, RFNAMEG|RFNOTEG);
procrfork(fsnetproc, nil, mainstacksize, RFNAMEG|RFNOTEG);
threadpostmountsrv(&fs, service, mtpt, MREPL);
exits(0);
}