diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/telco |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/telco')
-rwxr-xr-x | sys/src/cmd/telco/mkfile | 11 | ||||
-rwxr-xr-x | sys/src/cmd/telco/telco.c | 1555 | ||||
-rwxr-xr-x | sys/src/cmd/telco/telcodata | 3 | ||||
-rwxr-xr-x | sys/src/cmd/telco/telcofax | 2 |
4 files changed, 1571 insertions, 0 deletions
diff --git a/sys/src/cmd/telco/mkfile b/sys/src/cmd/telco/mkfile new file mode 100755 index 000000000..c3ed075a2 --- /dev/null +++ b/sys/src/cmd/telco/mkfile @@ -0,0 +1,11 @@ +</$objtype/mkfile + +TARG=telco +OFILES=telco.$O\ + +BIN=/$objtype/bin +</sys/src/cmd/mkone + +install.rc:V: + cp telcodata telcofax /rc/bin/service + chmod +x /rc/bin/service/telcofax /rc/bin/service/telcodata diff --git a/sys/src/cmd/telco/telco.c b/sys/src/cmd/telco/telco.c new file mode 100755 index 000000000..f0cb5f8ea --- /dev/null +++ b/sys/src/cmd/telco/telco.c @@ -0,0 +1,1555 @@ +#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); +} diff --git a/sys/src/cmd/telco/telcodata b/sys/src/cmd/telco/telcodata new file mode 100755 index 000000000..e03efd646 --- /dev/null +++ b/sys/src/cmd/telco/telcodata @@ -0,0 +1,3 @@ +#!/bin/rc +echo This is the plan 9 incoming fax line. +echo Please do not make data calls to us. diff --git a/sys/src/cmd/telco/telcofax b/sys/src/cmd/telco/telcofax new file mode 100755 index 000000000..77ec7cde3 --- /dev/null +++ b/sys/src/cmd/telco/telcofax @@ -0,0 +1,2 @@ +#!/bin/rc +/bin/aux/faxreceive |