#include <u.h>
#include <libc.h>
#include <auth.h>
#include <fcall.h>
#define LOGFILE "telco"
/*
* Rather than reading /adm/users, which is a lot of work for
* a toy progdev, we assume all groups have the form
* NNN:user:user:
* meaning that each user is the leader of his own group.
*/
enum
{
OPERM = 0x3, /* mask of all permission types in open mode */
Ndev = 8,
Nreq = (Ndev*3)/2,
Nrbuf = 32*1024,
};
typedef struct Fid Fid;
typedef struct Dev Dev;
typedef struct Request Request;
typedef struct Type Type;
struct Fid
{
Qid qid;
short busy;
short open;
int fid;
Fid *next;
char *user;
};
struct Request
{
Request *next;
Fid *fid;
ulong tag;
int count;
int flushed;
};
struct Dev
{
Lock;
/* device state */
int ctl; /* control fd */
int data; /* data fd */
char *path; /* to device */
Type *t;
Type *baset;
int speed;
int fclass;
/* fs emulation */
int open;
long perm;
char *name;
char *user;
char msgbuf[128];
Request *r;
Request *rlast;
/* input reader */
int monitoring; /* monitor pid */
char rbuf[Nrbuf];
char *rp;
char *wp;
long pid;
};
enum
{
Devmask= (Ndev-1)<<8,
Qlvl1= 0,
Qlvl2= 1,
Qclone= 2,
Qlvl3= 3,
Qdata= 4,
Qctl= 5,
Pexec = 1,
Pwrite = 2,
Pread = 4,
Pother = 1,
Pgroup = 8,
Powner = 64,
};
char *names[] =
{
[Qlvl1] "/",
[Qlvl2] "telco",
[Qclone] "clone",
[Qlvl3] "",
[Qdata] "data",
[Qctl] "ctl",
};
#define DEV(q) ((((ulong)(q).path)&Devmask)>>8)
#define TYPE(q) (((ulong)(q).path)&((1<<8)-1))
#define MKQID(t, i) ((((i)<<8)&Devmask) | (t))
enum
{
/*
* modem specific commands
*/
Cerrorcorrection = 0, /* error correction */
Ccompression, /* compression */
Cflowctl, /* CTS/RTS */
Crateadjust, /* follow line speed */
Cfclass2, /* set up for fax */
Cfclass0, /* set up for data */
Ncommand,
};
struct Type
{
char *name;
char *ident; /* inquire request */
char *response; /* inquire response (strstr) */
char *basetype; /* name of base type */
char *commands[Ncommand];
};
/*
* Fax setup summary
*
* FCLASS=2 - set to service class 2, i.e., one where the fax handles timing
* FTBC=0 - ???
* FREL=1 - ???
* FCQ=1 - receive copy quality checking enabled
* FBOR=1 - set reversed bit order for phase C data
* FCR=1 - the DCE can receive message data, bit 10 in the DIS or
* DTC frame will be set
* FDIS=,3 - limit com speed to 9600 baud
*/
Type typetab[] =
{
{ "Rockwell", 0, 0, 0,
"AT\\N7", /* auto reliable (V.42, fall back to MNP, to none) */
"AT%C1\\J0", /* negotiate for compression, don't change port baud rate */
"AT\\Q3", /* CTS/RTS flow control */
"AT\\J1",
"AT+FCLASS=2\rAT+FCR=1\r",
"AT+FCLASS=0",
},
{ "ATT2400", "ATI9", "E2400", "Rockwell",
"AT\\N3", /* auto reliable (MNP, fall back to none) */
0,
0,
0,
0,
0,
},
{ "ATT14400", "ATI9", "E14400", "Rockwell",
0,
0,
0,
0,
0,
0,
},
{ "MT1432", "ATI2", "MT1432", 0,
"AT&E1", /* auto reliable (V.42, fall back to none) */
"AT&E15$BA0", /* negotiate for compression */
"AT&E4", /* CTS/RTS flow control */
"AT$BA1", /* don't change port baud rate */
"AT+FCLASS=2\rAT+FTBC=0\rAT+FREL=1\rAT+FCQ=1\rAT+FBOR=1\rAT+FCR=1\rAT+FDIS=,3",
"AT+FCLASS=0",
},
{ "MT2834", "ATI2", "MT2834", "MT1432",
0,
0,
0,
0,
"AT+FCLASS=2\rAT+FTBC=0\rAT+FREL=1\rAT+FCQ=1\rAT+FBOR=1\rAT+FCR=1",
0,
},
{ "VOCAL", "ATI6", "144DPL+FAX", "Rockwell",
"AT\\N3", /* auto reliable (V.42, fall back to MNP, fall back to none) */
"AT%C3\\J0", /* negotiate for compression, don't change port baud rate */
0,
0,
"AT+FCLASS=2\rAT+FTBC=0\rAT+FREL=1\rAT+FCQ=1\rAT+FBOR=1\rAT+FCR=1",
"AT+FCLASS=0",
},
{ 0, },
};
/*
* modem return codes
*/
enum
{
Ok,
Success,
Failure,
Noise,
Found,
};
/*
* modem return messages
*/
typedef struct Msg Msg;
struct Msg
{
char *text;
int type;
};
Msg msgs[] =
{
{ "OK", Ok, },
{ "NO CARRIER", Failure, },
{ "ERROR", Failure, },
{ "NO DIALTONE", Failure, },
{ "BUSY", Failure, },
{ "NO ANSWER", Failure, },
{ "CONNECT", Success, },
{ 0, 0 },
};
Fid *fids;
Dev *dev;
int ndev;
int mfd[2];
char *user;
uchar mdata[8192+IOHDRSZ];
int messagesize = sizeof mdata;
Fcall thdr;
Fcall rhdr;
char errbuf[ERRMAX];
uchar statbuf[STATMAX];
int pulsed;
int verbose;
int maxspeed = 56000;
char *srcid = "plan9";
int answer = 1;
Fid *newfid(int);
int devstat(Dir*, uchar*, int);
int devgen(Qid, int, Dir*, uchar*, int);
void error(char*);
void io(void);
void *erealloc(void*, ulong);
void *emalloc(ulong);
void usage(void);
int perm(Fid*, Dev*, int);
void setspeed(Dev*, int);
int getspeed(char*, int);
char *dialout(Dev*, char*);
void onhook(Dev*);
int readmsg(Dev*, int, char*);
void monitor(Dev*);
int getinput(Dev*, char*, int);
void serve(Dev*);
void receiver(Dev*);
char* modemtype(Dev*, int, int);
char *rflush(Fid*), *rversion(Fid*),
*rattach(Fid*), *rauth(Fid*), *rwalk(Fid*),
*ropen(Fid*), *rcreate(Fid*),
*rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
*rremove(Fid*), *rstat(Fid*), *rwstat(Fid*);
char *(*fcalls[])(Fid*) = {
[Tflush] rflush,
[Tversion] rversion,
[Tattach] rattach,
[Tauth] rauth,
[Twalk] rwalk,
[Topen] ropen,
[Tcreate] rcreate,
[Tread] rread,
[Twrite] rwrite,
[Tclunk] rclunk,
[Tremove] rremove,
[Tstat] rstat,
[Twstat] rwstat,
};
char Eperm[] = "permission denied";
char Enotdir[] = "not a directory";
char Enotexist[] = "file does not exist";
char Ebadaddr[] = "bad address";
char Eattn[] = "can't get modem's attention";
char Edial[] = "can't dial";
char Enoauth[] = "telco: authentication not required";
char Eisopen[] = "file already open for I/O";
char Enodev[] = "no free modems";
char Enostream[] = "stream closed prematurely";
void
usage(void)
{
fprint(2, "usage: %s [-vp] [-i srcid] dev ...\n", argv0);
exits("usage");
}
void
notifyf(void *a, char *s)
{
USED(a);
if(strncmp(s, "interrupt", 9) == 0)
noted(NCONT);
noted(NDFLT);
}
void
main(int argc, char *argv[])
{
int p[2];
int fd;
char buf[10];
Dev *d;
ARGBEGIN{
case 'p':
pulsed = 1;
break;
case 'v':
verbose = 1;
break;
case 'i':
srcid = ARGF();
break;
case 's':
maxspeed = atoi(ARGF());
break;
case 'n':
answer = 0;
break;
default:
usage();
}ARGEND
if(argc == 0)
usage();
if(argc > Ndev)
argc = Ndev;
if(pipe(p) < 0)
error("pipe failed");
notify(notifyf);
fmtinstall('F', fcallfmt);
user = getuser();
switch(rfork(RFFDG|RFPROC|RFREND|RFNOTEG)){
case -1:
error("fork");
case 0:
close(p[1]);
mfd[0] = mfd[1] = p[0];
break;
default:
close(p[0]);
fd = create("/srv/telco", OWRITE, 0666);
if(fd < 0)
error("create of /srv/telco failed");
sprint(buf, "%d", p[1]);
if(write(fd, buf, strlen(buf)) < 0)
error("writing /srv/telco");
close(fd);
if(mount(p[1], -1, "/net", MBEFORE, "") < 0)
error("mount failed");
exits(0);
}
dev = mallocz(argc*sizeof(Dev), 1);
for(ndev = 0; ndev < argc; ndev++){
d = &dev[ndev];
d->path = argv[ndev];
d->rp = d->wp = d->rbuf;
monitor(d);
d->open++;
onhook(d);
d->open--;
}
io();
}
/*
* generate a stat structure for a qid
*/
int
devstat(Dir *dir, uchar *buf, int nbuf)
{
Dev *d;
int t;
static char tmp[10][32];
static int ntmp;
t = TYPE(dir->qid);
if(t != Qlvl3)
dir->name = names[t];
else{
dir->name = tmp[ntmp % nelem(tmp)];
sprint(dir->name, "%lud", DEV(dir->qid));
ntmp++;
}
dir->mode = 0755;
dir->uid = user;
dir->gid = user;
dir->muid = user;
if(t >= Qlvl3){
d = &dev[DEV(dir->qid)];
if(d->open){
dir->mode = d->perm;
dir->uid = d->user;
}
}
if(dir->qid.type & QTDIR)
dir->mode |= DMDIR;
if(t == Qdata){
d = &dev[DEV(dir->qid)];
dir->length = d->wp - d->rp;
if(dir->length < 0)
dir->length += Nrbuf;
} else
dir->length = 0;
dir->atime = time(0);
dir->mtime = dir->atime;
if(buf)
return convD2M(dir, buf, nbuf);
return 0;
}
/*
* enumerate file's we can walk to from q
*/
int
devgen(Qid q, int i, Dir *d, uchar *buf, int nbuf)
{
static ulong v;
d->qid.vers = v++;
switch(TYPE(q)){
case Qlvl1:
if(i != 0)
return -1;
d->qid.type = QTDIR;
d->qid.path = Qlvl2;
break;
case Qlvl2:
switch(i){
case -1:
d->qid.type = QTDIR;
d->qid.path = Qlvl1;
break;
case 0:
d->qid.type = QTFILE;
d->qid.path = Qclone;
break;
default:
if(i > ndev)
return -1;
d->qid.type = QTDIR;
d->qid.path = MKQID(Qlvl3, i-1);
break;
}
break;
case Qlvl3:
switch(i){
case -1:
d->qid.type = QTDIR;
d->qid.path = Qlvl2;
break;
case 0:
d->qid.type = QTFILE;
d->qid.path = MKQID(Qdata, DEV(q));
break;
case 1:
d->qid.type = QTFILE;
d->qid.path = MKQID(Qctl, DEV(q));
break;
default:
return -1;
}
break;
default:
return -1;
}
return devstat(d, buf, nbuf);
}
char*
rversion(Fid *)
{
Fid *f;
for(f = fids; f; f = f->next)
if(f->busy)
rclunk(f);
if(thdr.msize < 256)
return "version: message size too small";
messagesize = thdr.msize;
if(messagesize > sizeof mdata)
messagesize = sizeof mdata;
rhdr.msize = messagesize;
if(strncmp(thdr.version, "9P2000", 6) != 0)
return "unrecognized 9P version";
rhdr.version = "9P2000";
return 0;
}
char*
rflush(Fid *f)
{
Request *r, **l;
Dev *d;
USED(f);
for(d = dev; d < &dev[ndev]; d++){
lock(d);
for(l = &d->r; r = *l; l = &r->next)
if(r->tag == thdr.oldtag){
*l = r->next;
free(r);
break;
}
unlock(d);
}
return 0;
}
char *
rauth(Fid *f)
{
USED(f);
return Enoauth;
}
char*
rattach(Fid *f)
{
f->busy = 1;
f->qid.type = QTDIR;
f->qid.path = Qlvl1;
f->qid.vers = 0;
rhdr.qid = f->qid;
if(thdr.uname[0])
f->user = strdup(thdr.uname);
else
f->user = "none";
return 0;
}
char*
rwalk(Fid *f)
{
Fid *nf;
int i, nqid;
char *name, *err;
Dir dir;
Qid q;
nf = nil;
if(thdr.fid != thdr.newfid){
if(f->open)
return Eisopen;
if(f->busy == 0)
return Enotexist;
nf = newfid(thdr.newfid);
nf->busy = 1;
nf->open = 0;
nf->qid = f->qid;
nf->user = strdup(f->user);
f = nf; /* walk f */
}
err = nil;
dir.qid = f->qid;
nqid = 0;
if(thdr.nwname > 0){
for(; nqid < thdr.nwname; nqid++) {
if((dir.qid.type & QTDIR) == 0){
err = Enotdir;
break;
}
name = thdr.wname[nqid];
if(strcmp(name, ".") == 0){
/* nothing to do */
}else if(strcmp(name, "..") == 0) {
if(devgen(f->qid, -1, &dir, 0, 0) < 0)
break;
}
else{
q = dir.qid;
for(i = 0;; i++){
if(devgen(q, i, &dir, 0, 0) < 0)
goto Out;
if(strcmp(name, dir.name) == 0)
break;
}
}
rhdr.wqid[nqid] = dir.qid;
}
Out:
if(nqid == 0 && err == nil)
err = Enotexist;
if(nf != nil && thdr.fid != thdr.newfid && nqid < thdr.nwname)
rclunk(nf);
}
rhdr.nwqid = nqid;
if(nqid > 0 && nqid == thdr.nwname)
f->qid = dir.qid;
return err;
}
char *
ropen(Fid *f)
{
Dev *d;
int mode, t;
if(f->open)
return Eisopen;
mode = thdr.mode;
mode &= OPERM;
if(f->qid.type & QTDIR){
if(mode != OREAD)
return Eperm;
rhdr.qid = f->qid;
return 0;
}
if(mode==OEXEC)
return Eperm;
t = TYPE(f->qid);
if(t == Qclone){
for(d = dev; d < &dev[ndev]; d++)
if(d->open == 0)
break;
if(d == &dev[ndev])
return Enodev;
f->qid.path = MKQID(Qctl, d-dev);
t = Qctl;
}
switch(t){
case Qdata:
case Qctl:
d = &dev[DEV(f->qid)];
if(d->open == 0){
d->user = strdup(f->user);
d->perm = 0660;
}else {
if(mode==OWRITE || mode==ORDWR)
if(!perm(f, d, Pwrite))
return Eperm;
if(mode==OREAD || mode==ORDWR)
if(!perm(f, d, Pread))
return Eperm;
}
d->open++;
break;
}
rhdr.qid = f->qid;
rhdr.iounit = messagesize - IOHDRSZ;
f->open = 1;
return 0;
}
char *
rcreate(Fid *f)
{
USED(f);
return Eperm;
}
/*
* intercept a note
*/
void
takeanote(void *u, char *note)
{
USED(u);
if(strstr(note, "flushed"))
noted(NCONT);
noted(NDFLT);
}
char*
rread(Fid *f)
{
char *buf;
long off, start;
int i, m, n, cnt, t;
Dir dir;
char num[32];
Dev *d;
Request *r;
n = 0;
rhdr.count = 0;
off = thdr.offset;
cnt = thdr.count;
buf = rhdr.data;
t = TYPE(f->qid);
switch(t){
default:
start = 0;
for(i = 0; n < cnt; i++){
m = devgen(f->qid, i, &dir, (uchar*)buf+n, cnt-n);
if(m <= BIT16SZ)
break;
if(start >= off)
n += m;
start += m;
}
break;
case Qctl:
i = sprint(num, "%lud", DEV(f->qid));
if(off < i){
n = cnt;
if(off + n > i)
n = i - off;
memmove(buf, num + off, n);
} else
n = 0;
break;
case Qdata:
d = &dev[DEV(f->qid)];
r = mallocz(sizeof(Request), 1);
r->tag = thdr.tag;
r->count = thdr.count;
r->fid = f;
r->flushed = 0;
lock(d);
if(d->r)
d->rlast->next = r;
else
d->r = r;
d->rlast = r;
serve(d);
unlock(d);
return "";
}
rhdr.count = n;
return 0;
}
char *cmsg = "connect ";
int clen;
char*
rwrite(Fid *f)
{
Dev *d;
ulong off;
int cnt;
char *cp;
char buf[64];
off = thdr.offset;
cnt = thdr.count;
switch(TYPE(f->qid)){
default:
return "file is a directory";
case Qctl:
d = &dev[DEV(f->qid)];
clen = strlen(cmsg);
if(cnt < clen || strncmp(thdr.data, cmsg, clen) != 0){
/*
* send control message to real control file
*/
if(seek(d->ctl, off, 0) < 0 || write(d->ctl, thdr.data, cnt) < 0){
errstr(errbuf, sizeof errbuf);
return errbuf;
}
} else {
/*
* connect
*/
cnt -= clen;
if(cnt >= sizeof(buf))
cnt = sizeof(buf) - 1;
if(cnt < 0)
return Ebadaddr;
strncpy(buf, &thdr.data[clen], cnt);
buf[cnt] = 0;
cp = dialout(d, buf);
if(cp)
return cp;
}
rhdr.count = cnt;
break;
case Qdata:
d = &dev[DEV(f->qid)];
if(write(d->data, thdr.data, cnt) < 0){
errstr(errbuf, sizeof errbuf);
return errbuf;
}
rhdr.count = cnt;
break;
}
return 0;
}
char *
rclunk(Fid *f)
{
Dev *d;
if(f->open)
switch(TYPE(f->qid)){
case Qdata:
case Qctl:
d = &dev[DEV(f->qid)];
if(d->open == 1)
onhook(d);
d->open--;
break;
}
free(f->user);
f->busy = 0;
f->open = 0;
return 0;
}
char *
rremove(Fid *f)
{
USED(f);
return Eperm;
}
char *
rstat(Fid *f)
{
Dir d;
d.qid = f->qid;
rhdr.stat = statbuf;
rhdr.nstat = devstat(&d, statbuf, sizeof statbuf);
return 0;
}
char *
rwstat(Fid *f)
{
Dev *d;
Dir dir;
if(TYPE(f->qid) < Qlvl3)
return Eperm;
convM2D(thdr.stat, thdr.nstat, &dir, rhdr.data); /* rhdr.data is a known place to scribble */
d = &dev[DEV(f->qid)];
/*
* To change mode, must be owner
*/
if(d->perm != dir.mode){
if(strcmp(f->user, d->user) != 0)
if(strcmp(f->user, user) != 0)
return Eperm;
}
/* all ok; do it */
d->perm = dir.mode & ~DMDIR;
return 0;
}
Fid *
newfid(int fid)
{
Fid *f, *ff;
ff = 0;
for(f = fids; f; f = f->next)
if(f->fid == fid)
return f;
else if(!ff && !f->busy)
ff = f;
if(ff){
ff->fid = fid;
return ff;
}
f = emalloc(sizeof *f);
f->fid = fid;
f->next = fids;
fids = f;
return f;
}
/*
* read fs requests and dispatch them
*/
void
io(void)
{
char *err;
int n;
for(;;){
/*
* reading from a pipe or a network device
* will give an error after a few eof reads
* however, we cannot tell the difference
* between a zero-length read and an interrupt
* on the processes writing to us,
* so we wait for the error
*/
n = read9pmsg(mfd[0], mdata, messagesize);
if(n == 0)
continue;
if(n < 0)
error("mount read");
if(convM2S(mdata, n, &thdr) != n)
error("convM2S error");
rhdr.data = (char*)mdata + IOHDRSZ;
if(!fcalls[thdr.type])
err = "bad fcall type";
else
err = (*fcalls[thdr.type])(newfid(thdr.fid));
if(err){
if(*err == 0)
continue; /* assigned to a slave */
rhdr.type = Rerror;
rhdr.ename = err;
}else{
rhdr.type = thdr.type + 1;
rhdr.fid = thdr.fid;
}
rhdr.tag = thdr.tag;
n = convS2M(&rhdr, mdata, messagesize);
if(write(mfd[1], mdata, n) != n)
error("mount write");
}
}
int
perm(Fid *f, Dev *d, int p)
{
if((p*Pother) & d->perm)
return 1;
if(strcmp(f->user, user)==0 && ((p*Pgroup) & d->perm))
return 1;
if(strcmp(f->user, d->user)==0 && ((p*Powner) & d->perm))
return 1;
return 0;
}
void
error(char *s)
{
fprint(2, "%s: %s: %r\n", argv0, s);
syslog(0, LOGFILE, "%s: %r", s);
remove("/srv/telco");
postnote(PNGROUP, getpid(), "exit");
exits(s);
}
void *
emalloc(ulong n)
{
void *p;
p = mallocz(n, 1);
if(!p)
error("out of memory");
return p;
}
void *
erealloc(void *p, ulong n)
{
p = realloc(p, n);
if(!p)
error("out of memory");
return p;
}
/*
* send bytes to modem
*/
int
send(Dev *d, char *x)
{
if(verbose)
syslog(0, LOGFILE, "->%s", x);
return write(d->data, x, strlen(x));
}
/*
* apply a string of commands to modem
*/
int
apply(Dev *d, char *s, char *substr, int secs)
{
char buf[128];
char *p;
int c, m;
p = buf;
m = Ok;
while(*s){
c = *p++ = *s++;
if(c == '\r' || *s == 0){
if(c != '\r')
*p++ = '\r';
*p = 0;
if(send(d, buf) < 0)
return Failure;
m = readmsg(d, secs, substr);
p = buf;
}
}
return m;
}
/*
* apply a command type
*/
int
applyspecial(Dev *d, int index)
{
char *cmd;
cmd = d->t->commands[index];
if(cmd == 0 && d->baset)
cmd = d->baset->commands[index];
if(cmd == 0)
return Failure;
return apply(d, cmd, 0, 2);
}
/*
* get modem into command mode if it isn't already
*/
int
attention(Dev *d)
{
int i;
for(i = 0; i < 2; i++){
sleep(250);
if(send(d, "+") < 0)
continue;
sleep(250);
if(send(d, "+") < 0)
continue;
sleep(250);
if(send(d, "+") < 0)
continue;
sleep(250);
readmsg(d, 0, 0);
if(apply(d, "ATZH0", 0, 2) == Ok)
return Ok;
}
return Failure;
}
int portspeed[] = { 56000, 38400, 19200, 14400, 9600, 4800, 2400, 1200, 600, 300, 0 };
/*
* get the modem's type and speed
*/
char*
modemtype(Dev *d, int limit, int fax)
{
int *p;
Type *t, *bt;
char buf[28];
d->t = typetab;
d->baset = 0;
/* assume we're at a good speed, try getting attention a few times */
attention(d);
/* find a common port rate */
for(p = portspeed; *p; p++){
if(*p > limit)
continue;
setspeed(d, *p);
if(attention(d) == Ok)
break;
}
if(*p == 0)
return Eattn;
d->speed = *p;
if(verbose)
syslog(0, LOGFILE, "port speed %d", *p);
/*
* basic Hayes commands everyone implements (we hope)
* Q0 = report result codes
* V1 = full word result codes
* E0 = don't echo commands
* M1 = speaker on until on-line
* S0=0 = autoanswer off
*/
if(apply(d, "ATQ0V1E0M1S0=0", 0, 2) != Ok)
return Eattn;
/* find modem type */
for(t = typetab; t->name; t++){
if(t->ident == 0 || t->response == 0)
continue;
if(apply(d, t->ident, t->response, 2) == Found)
break;
readmsg(d, 0, 0);
}
readmsg(d, 0, 0);
if(t->name){
d->t = t;
if(t->basetype){
for(bt = typetab; bt->name; bt++)
if(strcmp(bt->name, t->basetype) == 0)
break;
if(bt->name)
d->baset = bt;
}
}
if(verbose)
syslog(0, LOGFILE, "modem %s", d->t->name);
/* try setting fax modes */
d->fclass = 0;
if(fax){
/* set up fax parameters */
if(applyspecial(d, Cfclass2) != Failure)
d->fclass = 2;
/* setup a source id */
if(srcid){
sprint(buf, "AT+FLID=\"%s\"", srcid);
apply(d, buf, 0, 2);
}
/* allow both data and fax calls in */
apply(d, "AT+FAA=1", 0, 2);
} else
applyspecial(d, Cfclass0);
return 0;
}
/*
* a process to read input from a modem.
*/
void
monitor(Dev *d)
{
int n;
char *p;
char file[256];
int background;
background = 0;
d->ctl = d->data = -1;
for(;;){
lock(d);
sprint(file, "%sctl", d->path);
d->ctl = open(file, ORDWR);
if(d->ctl < 0)
error("opening ctl");
d->data = open(d->path, ORDWR);
if(d->data < 0)
error("opening data");
d->wp = d->rp = d->rbuf;
unlock(d);
if(!background){
background = 1;
switch(d->pid = rfork(RFPROC|RFMEM)){
case -1:
error("out of processes");
case 0:
break;
default:
return;
}
}
/* wait for ring or off hook */
while(d->open == 0){
d->rp = d->rbuf;
p = d->wp;
n = read(d->data, p, 1);
if(n < 1)
continue;
if(p < &d->rbuf[Nrbuf] - 2)
d->wp++;
if(*p == '\r' || *p == '\n'){
*(p+1) = 0;
if(verbose)
syslog(0, LOGFILE, "<:-%s", d->rp);
if(answer && strncmp(d->rp, "RING", 4) == 0){
receiver(d);
continue;
}
if(d->open == 0)
d->wp = d->rbuf;
}
}
/* shuttle bytes till on hook */
while(d->open){
if(d->wp >= d->rp)
n = &d->rbuf[Nrbuf] - d->wp;
else
n = d->rp - d->wp - 1;
if(n > 0)
n = read(d->data, d->wp, n);
else {
read(d->data, file, sizeof(file));
continue;
}
if(n < 0)
break;
lock(d);
if(d->wp + n >= &d->rbuf[Nrbuf])
d->wp = d->rbuf;
else
d->wp += n;
serve(d);
unlock(d);
}
close(d->ctl);
close(d->data);
}
}
/*
* get bytes input by monitor() (only routine that changes d->rp)
*/
int
getinput(Dev *d, char *buf, int n)
{
char *p;
int i;
p = buf;
while(n > 0){
if(d->wp == d->rp)
break;
if(d->wp < d->rp)
i = &d->rbuf[Nrbuf] - d->rp;
else
i = d->wp - d->rp;
if(i > n)
i = n;
memmove(p, d->rp, i);
if(d->rp + i == &d->rbuf[Nrbuf])
d->rp = d->rbuf;
else
d->rp += i;
n -= i;
p += i;
}
return p - buf;
}
/*
* fulfill a read request (we assume d is locked)
*/
void
serve(Dev *d)
{
Request *r;
int n;
Fcall rhdr;
uchar *mdata;
char *buf;
mdata = malloc(messagesize);
buf = malloc(messagesize-IOHDRSZ);
for(;;){
if(d->r == 0 || d->rp == d->wp)
break;
r = d->r;
if(r->count > sizeof(buf))
r->count = sizeof(buf);
n = getinput(d, buf, r->count);
if(n == 0)
break;
d->r = r->next;
rhdr.type = Rread;
rhdr.fid = r->fid->fid;
rhdr.tag = r->tag;
rhdr.data = buf;
rhdr.count = n;
n = convS2M(&rhdr, mdata, messagesize);
if(write(mfd[1], mdata, n) != n)
fprint(2, "telco: error writing\n");
free(r);
}
free(mdata);
free(buf);
}
/*
* dial a number
*/
char*
dialout(Dev *d, char *number)
{
int i, m, compress, rateadjust, speed, fax;
char *err;
char *field[5];
char dialstr[128];
compress = Ok;
rateadjust = Failure;
speed = maxspeed;
fax = Failure;
m = getfields(number, field, 5, 1, "!");
for(i = 1; i < m; i++){
if(field[i][0] >= '0' && field[i][0] <= '9')
speed = atoi(field[i]);
else if(strcmp(field[i], "nocompress") == 0)
compress = Failure;
else if(strcmp(field[i], "fax") == 0)
fax = Ok;
}
syslog(0, LOGFILE, "dialing %s speed=%d %s", number, speed, fax==Ok?"fax":"");
err = modemtype(d, speed, fax == Ok);
if(err)
return err;
/*
* extented Hayes commands, meaning depends on modem (VGA all over again)
*/
if(fax != Ok){
if(d->fclass != 0)
applyspecial(d, Cfclass0);
applyspecial(d, Cerrorcorrection);
if(compress == Ok)
compress = applyspecial(d, Ccompression);
if(compress != Ok)
rateadjust = applyspecial(d, Crateadjust);
}
applyspecial(d, Cflowctl);
/* dialout */
sprint(dialstr, "ATD%c%s\r", pulsed ? 'P' : 'T', number);
if(send(d, dialstr) < 0)
return Edial;
if(fax == Ok)
return 0; /* fax sender worries about the rest */
switch(readmsg(d, 120, 0)){
case Success:
break;
default:
return d->msgbuf;
}
/* change line rate if not compressing */
if(rateadjust == Ok)
setspeed(d, getspeed(d->msgbuf, d->speed));
return 0;
}
/*
* start a receiving process
*/
void
receiver(Dev *d)
{
int fd;
char file[256];
char *argv[8];
int argc;
int pfd[2];
char *prog;
pipe(pfd);
switch(rfork(RFPROC|RFMEM|RFFDG|RFNAMEG)){
case -1:
return;
case 0:
fd = open("/srv/telco", ORDWR);
if(fd < 0){
syslog(0, LOGFILE, "can't open telco: %r");
exits(0);
}
if(mount(fd, -1, "/net", MAFTER, "") < 0){
syslog(0, LOGFILE, "can't mount: %r");
exits(0);
}
close(fd);
/* open connection through the file system interface */
sprint(file, "/net/telco/%ld/data", d - dev);
fd = open(file, ORDWR);
if(fd < 0){
syslog(0, LOGFILE, "can't open %s: %r", file);
exits(0);
}
/* let parent continue */
close(pfd[0]);
close(pfd[1]);
/* answer the phone and see what flavor call this is */
prog = "/bin/service/telcodata";
switch(apply(d, "ATA", "+FCON", 30)){
case Success:
break;
case Found:
prog = "/bin/service/telcofax";
break;
default:
syslog(0, LOGFILE, "bad ATA response");
exits(0);
}
/* fork a receiving process */
dup(fd, 0);
dup(fd, 1);
close(fd);
argc = 0;
argv[argc++] = strrchr(prog, '/')+1;
argv[argc++] = file;
argv[argc++] = dev->t->name;
argv[argc] = 0;
exec(prog, argv);
syslog(0, LOGFILE, "can't exec %s: %r\n", prog);
exits(0);
default:
/* wait till child gets the device open */
close(pfd[1]);
read(pfd[0], file, 1);
close(pfd[0]);
break;
}
}
/*
* hang up an connections in progress
*/
void
onhook(Dev *d)
{
write(d->ctl, "d0", 2);
write(d->ctl, "r0", 2);
sleep(250);
write(d->ctl, "r1", 2);
write(d->ctl, "d1", 2);
modemtype(d, maxspeed, 1);
}
/*
* read till we see a message or we time out
*/
int
readmsg(Dev *d, int secs, char *substr)
{
ulong start;
char *p;
int i, len;
Msg *pp;
int found = 0;
p = d->msgbuf;
len = sizeof(d->msgbuf) - 1;
for(start = time(0); time(0) <= start+secs;){
if(len && d->rp == d->wp){
sleep(100);
continue;
}
i = getinput(d, p, 1);
if(i == 0)
continue;
if(*p == '\n' || *p == '\r' || len == 0){
*p = 0;
if(verbose && p != d->msgbuf)
syslog(0, LOGFILE, "<-%s", d->msgbuf);
if(substr && strstr(d->msgbuf, substr))
found = 1;
for(pp = msgs; pp->text; pp++)
if(strncmp(pp->text, d->msgbuf, strlen(pp->text))==0)
return found ? Found : pp->type;
start = time(0);
p = d->msgbuf;
len = sizeof(d->msgbuf) - 1;
continue;
}
len--;
p++;
}
strcpy(d->msgbuf, "No response from modem");
return found ? Found : Noise;
}
/*
* get baud rate from a connect message
*/
int
getspeed(char *msg, int speed)
{
char *p;
int s;
p = msg + sizeof("CONNECT") - 1;
while(*p == ' ' || *p == '\t')
p++;
s = atoi(p);
if(s <= 0)
return speed;
else
return s;
}
/*
* set speed and RTS/CTS modem flow control
*/
void
setspeed(Dev *d, int baud)
{
char buf[32];
if(d->ctl < 0)
return;
sprint(buf, "b%d", baud);
write(d->ctl, buf, strlen(buf));
write(d->ctl, "m1", 2);
}
|