summaryrefslogtreecommitdiff
path: root/sys/src/cmd/telco
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/telco
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/telco')
-rwxr-xr-xsys/src/cmd/telco/mkfile11
-rwxr-xr-xsys/src/cmd/telco/telco.c1555
-rwxr-xr-xsys/src/cmd/telco/telcodata3
-rwxr-xr-xsys/src/cmd/telco/telcofax2
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