|
|
Plan 9 NeXT
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"
#define DPRINT if(0) print
#define NOW (MACHP(0)->ticks)
typedef struct Dkmsg Dkmsg;
typedef struct Line Line;
typedef struct Dk Dk;
enum {
Maxdk = 4,
};
/*
* types of possible dkcalls
*/
enum {
Dial,
Announce,
Redial
};
/*
* format of messages to/from the datakit controller on the common
* signalling line
*/
struct Dkmsg {
uchar type;
uchar srv;
uchar param0l;
uchar param0h;
uchar param1l;
uchar param1h;
uchar param2l;
uchar param2h;
uchar param3l;
uchar param3h;
uchar param4l;
uchar param4h;
};
/*
* message codes (T_xxx == dialin.type, D_xxx == dialin.srv)
*/
#define T_SRV 1 /* service request */
#define D_SERV 1 /* (host to dkmux) announce a service */
#define D_DIAL 2 /* (host to dkmux) connect to a service */
#define D_XINIT 7 /* (dkmux to host) line has been spliced */
#define T_REPLY 2 /* reply to T_SRV/D_SERV or T_SRV/D_DIAL */
#define D_OK 1 /* not used */
#define D_OPEN 2 /* (dkmux to host) connection established */
#define D_FAIL 3 /* (dkmux to host) connection failed */
#define T_CHG 3 /* change the status of a connection */
#define D_CLOSE 1 /* close the connection */
#define D_ISCLOSED 2 /* (dkmux to host) confirm a close */
#define D_CLOSEALL 3 /* (dkmux to host) close all connections */
#define D_REDIAL 6 /* (host to dkmux) redial a call */
#define T_ALIVE 4 /* (host to dkmux) keep alive message */
#define D_CONTINUE 0 /* host has not died since last msg */
#define D_RESTART 1 /* host has restarted */
#define D_MAXCHAN 2 /* request maximum line number */
#define T_RESTART 8 /* (dkmux to host) datakit restarted */
/*
* macros for cracking/forming the window negotiation parameter
*/
#define MIN(x,y) (x < y ? x : y)
#define W_WINDOW(o,d,t) ((o<<8) | (d<<4) | t | 0100000)
#define W_VALID(x) ((x) & 0100000)
#define W_ORIG(x) (((x)>>8) & 017)
#define W_DEST(x) (((x)>>4) & 017)
#define W_TRAF(x) ((x) & 017)
#define W_DESTMAX(x,y) (W_WINDOW(W_ORIG(x),MIN(W_DEST(x),y),W_TRAF(x)))
#define W_LIMIT(x,y) (W_WINDOW(MIN(W_ORIG(x),y),MIN(W_DEST(x),y),W_TRAF(x)))
#define W_VALUE(x) (1<<((x)+4))
struct Line {
QLock;
Netprot; /* stat info */
int lineno;
Rendez r; /* wait here for dial */
int state; /* dial state */
int err; /* dialing error (if non zero) */
int window; /* negotiated window */
int timestamp; /* timestamp of last call received on this line */
int calltolive; /* multiple of 15 seconds for dialing state to last */
Queue *rq;
char addr[64];
char raddr[64];
char ruser[32];
Dk *dp; /* interface contianing this line */
};
/*
* a dkmux dk. one exists for every stream that a
* dkmux line discipline is pushed onto.
*/
struct Dk
{
QLock netlock;
Network net; /* stat info */
Line **linep; /* array of line structures */
QLock csclock;
Chan *csc;
char name[64]; /* dk name */
Queue *wq; /* dk output queue */
int ncsc; /* csc line number */
int lines; /* number of lines */
int restart;
int urpwindow;
Rendez timer;
int closeall; /* set when we receive a closeall message */
Rendez closeallr; /* wait here for a closeall */
};
Lock dklock;
Dk *dk[Maxdk];
/*
* conversation states (for Line.state)
*/
typedef enum {
Lclosed=0,
Lopened, /* opened but no call out */
Lconnected, /* opened and a call set up on htis line */
Lrclose, /* remote end has closed down */
Llclose, /* local end has closed down */
Ldialing, /* dialing a new call */
Llistening, /* this line listening for calls */
Lackwait, /* incoming call waiting for ack/nak */
Laccepting, /* waiting for user to accept or reject the call */
} Lstate;
char *dkstate[] =
{
[Lclosed] "Closed",
[Lopened] "Opened",
[Lconnected] "Established",
[Lrclose] "Rclose",
[Llclose] "Lclose",
[Ldialing] "Dialing",
[Llistening] "Listen",
[Lackwait] "Ackwait",
[Laccepting] "Accepting",
};
/*
* map datakit error to errno
*/
enum {
DKok,
DKbusy,
DKnetotl,
DKdestotl,
DKbadnet,
DKnetbusy,
DKinuse,
DKreject,
};
char* dkerr[]={
[DKok] "",
[DKbusy] "devdk: destination busy",
[DKnetotl] "devdk: network not answering",
[DKdestotl] "devdk: destination not answering",
[DKbadnet] "devdk: unknown address",
[DKnetbusy] "devdk: network busy",
[DKinuse] "devdk: service in use",
[DKreject] "devdk: connection refused",
};
#define DKERRS sizeof(dkerr)/sizeof(char*)
/*
* imported
*/
extern Qinfo urpinfo;
/*
* predeclared
*/
Chan* dkattach(char*);
static void dkmuxconfig(Queue*, Block*);
static Chan* dkopenline(Dk*, int);
static Chan* dkopencsc(Dk*);
static int dkmesg(Chan*, int, int, int, int);
static void dkcsckproc(void*);
static void dkanswer(Chan*, int, int);
static void dkwindow(Chan*);
static void dkcall(int, Chan*, char*, char*, char*);
static void dktimer(void*);
static void dkchgmesg(Chan*, Dk*, Dkmsg*, int);
static void dkreplymesg(Dk*, Dkmsg*, int);
Chan* dkopen(Chan*, int);
static void dkhangup(Line*);
/*
* for standard network interface (net.c)
*/
static int dkcloneline(Chan*);
static int dklisten(Chan*);
static void dkfilladdr(Chan*, char*, int);
static void dkfillraddr(Chan*, char*, int);
static void dkfillruser(Chan*, char*, int);
static void dkfillstatus(Chan*, char*, int);
extern Qinfo dkinfo;
/*
* the datakit multiplexor stream module definition
*/
static Streamopen dkmuxopen;
static Streamput dkmuxoput;
static Streamput dkmuxiput;
Qinfo dkmuxinfo =
{
dkmuxiput,
dkmuxoput,
dkmuxopen,
0,
"dkmux"
};
/*
* allocate a line if it doesn't exist
*/
static Line*
linealloc(Dk *dp, int lineno, int dolock)
{
Line *lp;
if(dolock)
qlock(&dp->netlock);
if(lineno > dp->lines)
panic("linealloc");
lp = dp->linep[lineno];
if(lp == 0){
lp = smalloc(sizeof(Line));
lp->lineno = lineno;
netadd(&dp->net, lp, lineno);
dp->linep[lineno] = lp;
}
if(dolock)
qunlock(&dp->netlock);
return lp;
}
/*
* a new dkmux. hold the stream in place so it can never be closed down.
*/
static void
dkmuxopen(Queue *q, Stream *s)
{
RD(q)->ptr = 0;
WR(q)->ptr = 0;
naildownstream(s);
}
/*
* handle configuration
*/
static void
dkmuxoput(Queue *q, Block *bp)
{
if(bp->type != M_DATA){
if(streamparse("config", bp))
dkmuxconfig(q, bp);
else
PUTNEXT(q, bp);
return;
}
PUTNEXT(q, bp);
}
/*
* gather a message and send it up the appropriate stream
*
* The first two bytes of each message contains the channel
* number, low order byte first.
*
* Simplifying assumption: one put == one message && the channel number
* is in the first block. If this isn't true, demultiplexing will not
* work.
*/
static void
dkmuxiput(Queue *q, Block *bp)
{
Dk *dp;
Line *lp;
int line;
/*
* not configured yet
*/
if(q->other->ptr == 0){
freeb(bp);
return;
}
dp = (Dk *)q->ptr;
if(bp->type != M_DATA){
PUTNEXT(q, bp);
return;
}
line = bp->rptr[0] | (bp->rptr[1]<<8);
bp->rptr += 2;
if(line<0 || line>=dp->lines){
DPRINT("dkmuxiput bad line %d\n", line);
freeb(bp);
return;
}
lp = linealloc(dp, line, 1);
if(lp && canqlock(lp)){
if(lp->rq)
PUTNEXT(lp->rq, bp);
else{
DPRINT("dkmuxiput unopened line %d\n", line);
freeb(bp);
}
qunlock(lp);
} else {
DPRINT("dkmuxiput unopened line %d\n", line);
freeb(bp);
}
}
/*
* the datakit line stream module definition
*/
static Streamopen dkstopen;
static Streamclose dkstclose;
static Streamput dkoput, dkiput;
Qinfo dkinfo =
{
dkiput,
dkoput,
dkstopen,
dkstclose,
"dk"
};
/*
* open and save a pointer to the conversation
*/
static void
dkstopen(Queue *q, Stream *s)
{
Dk *dp;
Line *lp;
dp = dk[s->dev];
q->other->ptr = q->ptr = lp = dp->linep[s->id];
lp->dp = dp;
lp->rq = q;
if(lp->state==Lclosed)
lp->state = Lopened;
}
/*
* close down a datakit conversation
*/
static void
dkstclose(Queue *q)
{
Dk *dp;
Line *lp;
Chan *c;
lp = (Line *)q->ptr;
dp = lp->dp;
/*
* if we never got going, we're done
*/
if(lp->rq == 0){
lp->state = Lclosed;
return;
}
/*
* these states don't need the datakit
*/
switch(lp->state){
case Lclosed:
case Llclose:
case Lopened:
lp->state = Lclosed;
goto out;
}
c = 0;
if(waserror()){
lp->state = Lclosed;
if(c)
close(c);
goto out;
}
c = dkopencsc(dp);
/*
* shake hands with dk
*/
switch(lp->state){
case Lrclose:
dkmesg(c, T_CHG, D_CLOSE, lp->lineno, 0);
lp->state = Lclosed;
break;
case Lackwait:
dkmesg(c, T_CHG, D_CLOSE, lp->lineno, 0);
lp->state = Llclose;
break;
case Llistening:
dkmesg(c, T_CHG, D_CLOSE, lp->lineno, 0);
lp->state = Llclose;
break;
case Lconnected:
dkmesg(c, T_CHG, D_CLOSE, lp->lineno, 0);
lp->state = Llclose;
break;
}
poperror();
close(c);
out:
qlock(lp);
lp->rq = 0;
qunlock(lp);
netdisown(lp);
lp->window = 0;
}
/*
* this is only called by hangup
*/
static void
dkiput(Queue *q, Block *bp)
{
PUTNEXT(q, bp);
}
/*
* we assume that each put is a message.
*
* add a 2 byte channel number to the start of each message,
* low order byte first. Make sure the first block contains
* both the 2 channel bytes and the control byte.
*/
static void
dkoput(Queue *q, Block *bp)
{
Line *lp;
Dk *dp;
int line;
if(bp->type != M_DATA){
freeb(bp);
error(Ebadarg);
}
lp = (Line *)q->ptr;
dp = lp->dp;
line = lp->lineno;
bp = padb(bp, 2);
bp->rptr[0] = line;
bp->rptr[1] = line>>8;
FLOWCTL(dp->wq, bp);
}
/*
* configure a datakit multiplexor. this takes 5 arguments separated
* by spaces:
* the line number of the common signalling channel (must be > 0)
* the number of lines in the device (optional)
* the word `restart' or `norestart' (optional/default==restart)
* the name of the dk (default==dk)
* the urp window size (default==2048)
*
* we can configure only once
*/
static int
haveca(void *arg)
{
Dk *dp;
dp = arg;
return dp->closeall;
}
static void
dkmuxconfig(Queue *q, Block *bp)
{
Dk *dp;
char *fields[5];
int n;
char buf[64];
char name[NAMELEN];
int lines;
int ncsc;
int restart;
int window;
if(WR(q)->ptr){
freeb(bp);
error(Egreg);
}
/*
* defaults
*/
ncsc = 1;
restart = 1;
lines = 16;
window = 2048;
strcpy(name, "dk");
/*
* parse
*/
n = getfields((char *)bp->rptr, fields, 5, " ");
switch(n){
case 5:
window = strtoul(fields[4], 0, 0);
if(window < 16)
window = 1<<(window+4);
case 4:
strncpy(name, fields[3], sizeof(name));
name[sizeof(name)-1] = 0;
case 3:
if(strcmp(fields[2], "restart")!=0)
restart = 0;
case 2:
lines = strtoul(fields[1], 0, 0);
case 1:
ncsc = strtoul(fields[0], 0, 0);
break;
default:
freeb(bp);
error(Ebadarg);
}
freeb(bp);
if(ncsc <= 0 || lines <= ncsc)
error(Ebadarg);
/*
* find a free dk slot. it name is already configured
* or no slots are left, error.
*/
lock(&dklock);
if(waserror()){
unlock(&dklock);
nexterror();
}
for(n = 0; n < Maxdk; n++){
dp = dk[n];
if(dp == 0)
break;
if(strcmp(name, dp->name) == 0)
error(Einuse);
}
if(n == Maxdk)
error(Enoifc);
/*
* allocate both a dk structure and an array of pointers to line
* structures
*/
dp = smalloc(sizeof(Dk));
dp->ncsc = ncsc;
dp->lines = lines;
dp->linep = smalloc(sizeof(Line*) * dp->lines);
strcpy(dp->name, name);
dp->net.name = dp->name;
dp->net.nconv = dp->lines;
dp->net.devp = &dkinfo;
dp->net.protop = &urpinfo;
dp->net.listen = dklisten;
dp->net.clone = dkcloneline;
dp->net.ninfo = 5;
dp->net.info[0].name = "local";
dp->net.info[0].fill = dkfilladdr;
dp->net.info[1].name = "remote";
dp->net.info[1].fill = dkfillraddr;
dp->net.info[2].name = "ruser";
dp->net.info[2].fill = dkfillruser;
dp->net.info[3].name = "urpstats";
dp->net.info[3].fill = urpfillstats;
dp->net.info[4].name = "status";
dp->net.info[4].fill = dkfillstatus;
dp->restart = restart;
dp->urpwindow = window;
dp->wq = WR(q);
q->ptr = q->other->ptr = dp;
dk[n] = dp;
unlock(&dklock);
poperror();
/*
* open csc here so that boot, dktimer, and dkcsckproc aren't
* all fighting for it at once.
*/
dkopencsc(dp);
/*
* start a process to listen to csc messages
*/
sprint(buf, "csc.%s.%d", dp->name, dp->ncsc);
kproc(buf, dkcsckproc, dp);
/*
* tell datakit we've rebooted. It should close all channels.
* do this here to get it done before trying to open a channel.
*/
if(dp->restart) {
DPRINT("dktimer: restart %s\n", dp->name);
dp->closeall = 0;
dkmesg(dp->csc, T_ALIVE, D_RESTART, 0, 0);
}
tsleep(&dp->closeallr, haveca, dp, 15000);
/*
* start a keepalive process
*/
sprint(buf, "timer.%s.%d", dp->name, dp->ncsc);
kproc(buf, dktimer, dp);
}
void
dkreset(void)
{
newqinfo(&dkmuxinfo);
}
void
dkinit(void)
{
}
Chan*
dkattach(char *spec)
{
Chan *c;
Dk *dp;
int dev;
/*
* find a multiplexor with the same name (default dk)
*/
if(*spec == 0)
spec = "dk";
for(dev = 0; dev < Maxdk; dev++){
dp = dk[dev];
if(dp && strcmp(dp->name, spec) == 0)
break;
}
if(dev == Maxdk)
error(Enoifc);
/*
* return the new channel
*/
c = devattach('k', spec);
c->dev = dev;
return c;
}
Chan*
dkclone(Chan *c, Chan *nc)
{
return devclone(c, nc);
}
int
dkwalk(Chan *c, char *name)
{
return netwalk(c, name, &dk[c->dev]->net);
}
void
dkstat(Chan *c, char *dp)
{
netstat(c, dp, &dk[c->dev]->net);
}
Chan*
dkopen(Chan *c, int omode)
{
Dk *dp;
dp = dk[c->dev];
linealloc(dp, STREAMID(c->qid.path), 1);
return netopen(c, omode, &dp->net);
}
void
dkcreate(Chan *c, char *name, int omode, ulong perm)
{
USED(c);
USED(name);
USED(omode);
USED(perm);
error(Eperm);
}
void
dkclose(Chan *c)
{
if(c->stream)
streamclose(c);
}
long
dkread(Chan *c, void *a, long n, ulong offset)
{
return netread(c, a, n, offset, &dk[c->dev]->net);
}
long
dkwrite(Chan *c, void *a, long n, ulong offset)
{
int t;
char buf[256];
char *field[5];
int m;
USED(offset);
t = STREAMTYPE(c->qid.path);
/*
* get data dispatched as quickly as possible
*/
if(t == Sdataqid)
return streamwrite(c, a, n, 0);
/*
* easier to do here than in dkoput
*/
if(t == Sctlqid){
if(n > sizeof buf - 1)
n = sizeof buf - 1;
strncpy(buf, a, n);
buf[n] = '\0';
m = getfields(buf, field, 5, " ");
if(strncmp(field[0], "connect", 7)==0){
if(m < 2)
error(Ebadarg);
dkcall(Dial, c, field[1], 0, 0);
} else if(strncmp(field[0], "announce", 8)==0){
if(m < 2)
error(Ebadarg);
dkcall(Announce, c, field[1], 0, 0);
} else if(strncmp(field[0], "redial", 6)==0){
if(m < 4)
error(Ebadarg);
dkcall(Redial, c, field[1], field[2], field[3]);
} else if(strncmp(field[0], "accept", 6)==0){
if(m < 2)
error(Ebadarg);
dkanswer(c, strtoul(field[1], 0, 0), 0);
} else if(strncmp(field[0], "reject", 6)==0){
if(m < 3)
error(Ebadarg);
for(m = 0; m < DKERRS-1; m++)
if(strcmp(field[2], dkerr[m]) == 0)
break;
dkanswer(c, strtoul(field[1], 0, 0), m);
} else
return streamwrite(c, a, n, 0);
return n;
}
error(Eperm);
return -1; /* never reached */
}
void
dkremove(Chan *c)
{
USED(c);
error(Eperm);
}
void
dkwstat(Chan *c, char *dp)
{
netwstat(c, dp, &dk[c->dev]->net);
}
/*
* return the number of an unused line (reserve it)
*/
static int
dkcloneline(Chan *c)
{
Line *lp;
Dk *dp;
int line;
dp = dk[c->dev];
/*
* get an unused device and open its control file
*/
qlock(&dp->netlock);
for(line = dp->ncsc+1; line < dp->lines; line++){
lp = dp->linep[line];
if(lp == 0 || lp->state == Lclosed){
lp = linealloc(dp, line, 0);
lp->state = Lopened;
/* current user becomes owner */
netown(lp, u->p->user, 0);
qunlock(&dp->netlock);
return lp->lineno;
}
}
qunlock(&dp->netlock);
error(Enodev);
return -1; /* never reached */
}
static Chan*
dkopenline(Dk *dp, int line)
{
Chan *c;
c = 0;
if(waserror()){
if(c)
close(c);
nexterror();
}
c = dkattach(dp->name);
c->qid.path = STREAMQID(line, Sdataqid);
dkopen(c, ORDWR);
poperror();
return c;
}
/*
* open the common signalling channel (dp->csc's reference count never goes below 1)
*/
static Chan*
dkopencsc(Dk *dp)
{
qlock(&dp->csclock);
if(dp->csc == 0)
dp->csc = dkopenline(dp, dp->ncsc);
incref(dp->csc);
qunlock(&dp->csclock);
return dp->csc;
}
/*
* return the contents of the info files
*/
void
dkfilladdr(Chan *c, char *buf, int len)
{
if(len < sizeof(dk[0]->linep[0]->addr)+2)
error(Ebadarg);
sprint(buf, "%s\n", dk[c->dev]->linep[STREAMID(c->qid.path)]->addr);
}
void
dkfillraddr(Chan *c, char *buf, int len)
{
if(len < sizeof(dk[0]->linep[0]->raddr)+2)
error(Ebadarg);
sprint(buf, "%s\n", dk[c->dev]->linep[STREAMID(c->qid.path)]->raddr);
}
void
dkfillruser(Chan *c, char *buf, int len)
{
if(len < sizeof(dk[0]->linep[0]->ruser)+2)
error(Ebadarg);
sprint(buf, "%s\n", dk[c->dev]->linep[STREAMID(c->qid.path)]->ruser);
}
void
dkfillstatus(Chan *c, char *buf, int len)
{
Dk *dp;
Line *lp;
int line;
char lbuf[65];
line = STREAMID(c->qid.path);
dp = dk[c->dev];
lp = linealloc(dp, line, 1);
sprint(lbuf, "%s/%d %d %s window %d\n", dp->name, line,
lp->state != Lclosed ? 1 : 0, dkstate[lp->state], lp->window);
strncpy(buf, lbuf, len);
}
/*
* send a message to the datakit on the common signaling line
*/
static int
dkmesg(Chan *c, int type, int srv, int p0, int p1)
{
Dkmsg d;
if(waserror()){
print("dkmesg: error\n");
return -1;
}
d.type = type;
d.srv = srv;
d.param0l = p0;
d.param0h = p0>>8;
d.param1l = p1;
d.param1h = p1>>8;
d.param2l = 0;
d.param2h = 0;
d.param3l = 0;
d.param3h = 0;
d.param4l = 0;
d.param4h = 0;
streamwrite(c, (char *)&d, sizeof(Dkmsg), 1);
poperror();
return 0;
}
/*
* call out on a datakit
*/
static int
calldone(void *a)
{
Line *lp;
lp = (Line *)a;
return lp->state != Ldialing;
}
static void
dkcall(int type, Chan *c, char *addr, char *nuser, char *machine)
{
char dialstr[66];
int line, win;
char dialtone;
int t_val, d_val;
Dk *dp;
Line *lp;
Chan *dc;
Chan *csc;
char *bang, *dot;
line = STREAMID(c->qid.path);
dp = dk[c->dev];
lp = linealloc(dp, line, 1);
/*
* only dial on virgin lines
*/
if(lp->state != Lopened)
error(Ebadarg);
DPRINT("dkcall(line=%d, type=%d, dest=%s)\n", line, type, addr);
/*
* build dial string
* - guard against new lines
* - change ! into . to delimit service
*/
if(strchr(addr, '\n'))
error(Ebadarg);
if(strlen(addr)+strlen(u->p->user)+2 >= sizeof(dialstr))
error(Ebadarg);
strcpy(dialstr, addr);
bang = strchr(dialstr, '!');
if(bang){
dot = strchr(dialstr, '.');
if(dot==0 || dot > bang)
*bang = '.';
}
switch(type){
case Dial:
t_val = T_SRV;
d_val = D_DIAL;
strcat(dialstr, "\n");
strcat(dialstr, u->p->user);
strcat(dialstr, "\n");
break;
case Announce:
t_val = T_SRV;
d_val = D_SERV;
break;
case Redial:
t_val = T_CHG;
d_val = D_REDIAL;
strcat(dialstr, "\n");
strcat(dialstr, nuser);
strcat(dialstr, "\n");
strcat(dialstr, machine);
strcat(dialstr, "\n");
break;
default:
t_val = 0;
d_val = 0;
panic("bad dial type");
}
/*
* open the data file
*/
dc = dkopenline(dp, line);
if(waserror()){
close(dc);
nexterror();
}
lp->calltolive = 4;
lp->state = Ldialing;
/*
* tell the controller we want to make a call
*/
DPRINT("dialout\n");
csc = dkopencsc(dp);
if(waserror()){
close(csc);
nexterror();
}
for(win = 0; ; win++)
if(W_VALUE(win) >= dp->urpwindow || win == 15)
break;
dkmesg(csc, t_val, d_val, line, W_WINDOW(win, win, 2));
poperror();
close(csc);
/*
* if redial, wait for a dial tone (otherwise we might send
* the dialstr to the previous other end and not the controller)
*/
if(type==Redial){
if(streamread(dc, &dialtone, 1L) != 1L){
lp->state = Lconnected;
error(Ebadarg);
}
}
/*
* make the call
*/
DPRINT("dialstr %s\n", dialstr);
streamwrite(dc, dialstr, (long)strlen(dialstr), 1);
close(dc);
poperror();
/*
* redial's never get a reply, assume it worked
*/
if(type == Redial) {
lp->state = Lconnected;
return;
}
/*
* wait for a reply
*/
DPRINT("reply wait\n");
sleep(&lp->r, calldone, lp);
/*
* if there was an error, translate it to a plan 9
* errno and report it to the user.
*/
DPRINT("got reply %d\n", lp->state);
if(lp->state != Lconnected) {
if(lp->err >= DKERRS)
error(dkerr[0]);
else
error(dkerr[lp->err]);
}
/*
* change state if serving
*/
if(type == D_SERV){
lp->state = Llistening;
}
DPRINT("connected!\n");
/*
* decode the window size
*/
if (W_VALID(lp->window)){
/*
* a 1127 window negotiation
*/
lp->window = W_VALUE(W_DEST(lp->window));
} else if(lp->window>2 && lp->window<31){
/*
* a generic window negotiation
*/
lp->window = 1<<lp->window;
} else
lp->window = 0;
/*
* tag the connection
*/
strncpy(lp->addr, addr, sizeof(lp->addr)-1);
strncpy(lp->raddr, addr, sizeof(lp->raddr)-1);
/*
* reset the protocol
*/
dkwindow(c);
}
/*
* listen for a call, reflavor the
*/
static int
dklisten(Chan *c)
{
char dialstr[512];
char *line[12];
char *field[8];
Line *lp;
Dk *dp;
int n, lineno, ts, window;
int from;
Chan *dc;
static int dts;
char *cp;
dp = dk[c->dev];
from = STREAMID(c->qid.path);
/*
* open the data file
*/
dc = dkopenline(dp, STREAMID(c->qid.path));
if(waserror()){
close(dc);
nexterror();
}
/*
* wait for a call in
*/
for(;;){
/*
* read the dialstring and null terminate it
*/
n = streamread(dc, dialstr, sizeof(dialstr)-1);
DPRINT("returns %d\n", n);
if(n <= 0)
error(Eio);
dialstr[n] = 0;
DPRINT("dialstr = %s\n", dialstr);
/*
* break the dial string into lines
*/
n = getfields(dialstr, line, 12, "\n");
if (n < 2) {
DPRINT("bad dialstr from dk (1 line)\n");
error(Eio);
}
/*
* line 0 is `line.tstamp.traffic[.urpparms.window]'
*/
window = 0;
switch(getfields(line[0], field, 5, ".")){
case 5:
/*
* generic way of passing window
*/
window = strtoul(field[4], 0, 0);
if(window > 0 && window <31)
window = 1<<window;
else
window = 0;
/*
* intentional fall through
*/
case 3:
/*
* 1127 way of passing window
*/
if(window == 0){
window = strtoul(field[2], 0, 0);
if(W_VALID(window))
window = W_VALUE(W_ORIG(window));
else
window = 0;
}
break;
default:
print("bad message from dk(bad first line)\n");
continue;
}
lineno = strtoul(field[0], 0, 0);
if(lineno >= dp->lines){
print("dklisten: illegal line %d\n", lineno);
continue;
}
lp = linealloc(dp, lineno, 1);
ts = strtoul(field[1], 0, 0);
/*
* this could be a duplicate request
*/
if(ts == lp->timestamp){
if((dts++ % 1000) == 0)
print("dklisten: repeat timestamp %d\n", lineno);
if(lp->state != Lconnected)
dkanswer(c, lineno, DKbusy);
continue;
}
/*
* take care of glare (datakit picked an inuse channel
* for the call to come in on).
*/
if(!canqlock(lp)){
DPRINT("DKbusy1\n");
dkanswer(c, lineno, DKbusy);
continue;
} else {
if(lp->state != Lclosed){
qunlock(lp);
DPRINT("DKbusy2 %ux\n", lp->state);
dkanswer(c, lineno, DKbusy);
continue;
}
}
lp->window = window;
/*
* Line 1 is `my-dk-name.service[.more-things]'.
* Special characters are escaped by '\'s. Convert to
* a plan 9 address, i.e. system!service.
*/
strncpy(lp->addr, line[1], sizeof(lp->addr)-1);
if(cp = strchr(lp->addr, '.')){
*cp = '!';
if(cp = strchr(cp, '.'))
*cp = 0;
}
/*
* the rest is variable length
*/
switch(n) {
case 2:
/* no more lines */
lp->ruser[0] = 0;
lp->raddr[0] = 0;
break;
case 3:
/* line 2 is `source.user.param1.param2' */
getfields(line[2], field, 3, ".");
strncpy(lp->raddr, field[0], sizeof(lp->raddr)-1);
strncpy(lp->ruser, field[1], sizeof(lp->ruser)-1);
break;
case 4:
/* line 2 is `user.param1.param2' */
getfields(line[2], field, 2, ".");
strncpy(lp->ruser, field[0], sizeof(lp->ruser)-1);
/* line 3 is `source.node.mod.line' */
strncpy(lp->raddr, line[3], sizeof(lp->raddr)-1);
break;
default:
print("bad message from dk(>4 line)\n");
qunlock(lp);
error(Ebadarg);
}
DPRINT("src(%s)user(%s)dest(%s)w(%d)\n", lp->raddr, lp->ruser,
lp->addr, W_TRAF(lp->window));
lp->timestamp = ts;
lp->state = Lconnected;
/* listener becomes owner */
netown(lp, dp->linep[from]->owner, 0);
qunlock(lp);
close(dc);
poperror();
DPRINT("dklisten returns %d\n", lineno);
return lineno;
}
panic("dklisten terminates strangely\n");
return -1; /* never reached */
}
/*
* answer a call
*/
static void
dkanswer(Chan *c, int line, int code)
{
char reply[64];
Chan *dc;
Line *lp;
Dk *dp;
dp = dk[c->dev];
lp = linealloc(dp, line, 1);
/*
* open the data file (c is a control file)
*/
dc = dkattach(dp->name);
if(waserror()){
close(dc);
nexterror();
}
dc->qid.path = STREAMQID(STREAMID(c->qid.path), Sdataqid);
dkopen(dc, ORDWR);
/*
* send the reply
*/
sprint(reply, "%ud.%ud.%ud", line, lp->timestamp, code);
DPRINT("dkanswer %s\n", reply);
streamwrite(dc, reply, strlen(reply), 1);
close(dc);
poperror();
/*
* set window size
*/
if(code == 0){
if(waserror()){
close(dc);
nexterror();
}
sprint(reply, "init %d %d", lp->window, Streamhi);
dc = dkopenline(dp, line);
dc->qid.path = STREAMQID(line, Sctlqid);
streamwrite(dc, reply, strlen(reply), 1);
close(dc);
poperror();
}
}
/*
* set the window size and reset the protocol
*/
static void
dkwindow(Chan *c)
{
char buf[64];
Line *lp;
lp = linealloc(dk[c->dev], STREAMID(c->qid.path), 1);
if(lp->window == 0)
lp->window = 64;
sprint(buf, "init %d %d", lp->window, Streamhi);
streamwrite(c, buf, strlen(buf), 1);
}
/*
* hangup a datakit connection
*/
static void
dkhangup(Line *lp)
{
Block *bp;
qlock(lp);
if(lp->rq){
bp = allocb(0);
bp->type = M_HANGUP;
PUTNEXT(lp->rq, bp);
}
qunlock(lp);
}
/*
* A process which listens to all input on a csc line
*/
static void
dkcsckproc(void *a)
{
long n;
Dk *dp;
Dkmsg d;
int line;
dp = a;
if(waserror()){
close(dp->csc);
return;
}
DPRINT("dkcsckproc: %d\n", dp->ncsc);
/*
* loop forever listening
*/
for(;;){
n = streamread(dp->csc, (char *)&d, (long)sizeof(d));
if(n != sizeof(d)){
if(n == 0)
error(Ehungup);
print("strange csc message %d\n", n);
continue;
}
line = (d.param0h<<8) + d.param0l;
DPRINT("t(%d)s(%d)l(%d)\n", d.type, d.srv, line);
switch (d.type) {
case T_CHG: /* controller wants to close a line */
dkchgmesg(dp->csc, dp, &d, line);
break;
case T_REPLY: /* reply to a dial request */
dkreplymesg(dp, &d, line);
break;
case T_SRV: /* ignore it, it's useless */
/* print("dksrvmesg(%d)\n", line); /**/
break;
case T_RESTART: /* datakit reboot */
if(line >=0 && line<dp->lines)
dp->lines = line+1;
break;
default:
print("unrecognized csc message %o.%o(%o)\n",
d.type, d.srv, line);
break;
}
}
}
/*
* datakit requests or confirms closing a line
*/
static void
dkchgmesg(Chan *c, Dk *dp, Dkmsg *dialp, int line)
{
Line *lp;
switch (dialp->srv) {
case D_CLOSE: /* remote shutdown */
if (line <= 0 || line >= dp->lines || (lp = dp->linep[line]) == 0) {
/* tell controller this line is not in use */
dkmesg(c, T_CHG, D_CLOSE, line, 0);
return;
}
switch (lp->state) {
case Ldialing:
/* simulate a failed connection */
dkreplymesg(dp, (Dkmsg *)0, line);
lp->state = Lrclose;
break;
case Lrclose:
case Lconnected:
case Llistening:
case Lackwait:
dkhangup(lp);
lp->state = Lrclose;
break;
case Lopened:
dkmesg(c, T_CHG, D_CLOSE, line, 0);
break;
case Llclose:
case Lclosed:
dkhangup(lp);
dkmesg(c, T_CHG, D_CLOSE, line, 0);
lp->state = Lclosed;
break;
}
break;
case D_ISCLOSED: /* acknowledging a local shutdown */
if (line <= 0 || line >= dp->lines || (lp = dp->linep[line]) == 0)
return;
switch (lp->state) {
case Llclose:
case Lclosed:
lp->state = Lclosed;
break;
case Lrclose:
case Lconnected:
case Llistening:
case Lackwait:
break;
}
break;
case D_CLOSEALL:
/*
* datakit wants us to close all lines
*/
for(line = dp->ncsc+1; line < dp->lines; line++){
lp = dp->linep[line];
if(lp == 0)
continue;
switch (lp->state) {
case Ldialing:
/* simulate a failed connection */
dkreplymesg(dp, (Dkmsg *)0, line);
lp->state = Lrclose;
break;
case Lrclose:
case Lconnected:
case Llistening:
case Lackwait:
lp->state = Lrclose;
dkhangup(lp);
break;
case Lopened:
break;
case Llclose:
case Lclosed:
lp->state = Lclosed;
break;
}
}
dp->closeall = 1;
wakeup(&dp->closeallr);
break;
default:
print("unrecognized T_CHG\n");
}
}
/*
* datakit replies to a dialout. capture reply code and traffic parameters
*/
static void
dkreplymesg(Dk *dp, Dkmsg *dialp, int line)
{
Line *lp;
DPRINT("dkreplymesg(%d)\n", line);
if(line < 0 || line >= dp->lines || (lp = dp->linep[line]) == 0)
return;
if(lp->state != Ldialing)
return;
if(dialp){
/*
* a reply from the dk
*/
lp->state = (dialp->srv==D_OPEN) ? Lconnected : Lrclose;
lp->err = (dialp->param1h<<8) + dialp->param1l;
lp->window = lp->err;
DPRINT("dkreplymesg: %d\n", lp->state);
} else {
/*
* a local abort
*/
lp->state = Lrclose;
lp->err = 0;
}
if(lp->state==Lrclose){
dkhangup(lp);
}
wakeup(&lp->r);
}
/*
* send a I'm alive message every 7.5 seconds and remind the dk of
* any closed channels it hasn't acknowledged.
*/
static void
dktimer(void *a)
{
int i;
Dk *dp;
Line *lp;
Chan *c;
dp = (Dk *)a;
c = dkopencsc(dp);
while(waserror());
for(;;){
/*
* send keep alive
*/
DPRINT("keep alive\n");
dkmesg(c, T_ALIVE, D_CONTINUE, 0, 0);
/*
* remind controller of dead lines and
* timeout calls that take to long
*/
for (i=dp->ncsc+1; i<dp->lines; i++){
lp = dp->linep[i];
if(lp == 0)
continue;
switch(lp->state){
case Llclose:
dkmesg(c, T_CHG, D_CLOSE, i, 0);
break;
case Ldialing:
if(lp->calltolive==0 || --lp->calltolive!=0)
break;
dkreplymesg(dp, (Dkmsg *)0, i);
break;
}
}
tsleep(&dp->timer, return0, 0, 7500);
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.