summaryrefslogtreecommitdiff
path: root/sys/src/cmd/fax
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/fax
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/fax')
-rwxr-xr-xsys/src/cmd/fax/fax2modem.c119
-rwxr-xr-xsys/src/cmd/fax/fax2receive.c193
-rwxr-xr-xsys/src/cmd/fax/fax2send.c171
-rwxr-xr-xsys/src/cmd/fax/file.c164
-rwxr-xr-xsys/src/cmd/fax/mkfile45
-rwxr-xr-xsys/src/cmd/fax/modem.c220
-rwxr-xr-xsys/src/cmd/fax/modem.h107
-rwxr-xr-xsys/src/cmd/fax/receive.c73
-rwxr-xr-xsys/src/cmd/fax/receiverc58
-rwxr-xr-xsys/src/cmd/fax/send.c55
-rwxr-xr-xsys/src/cmd/fax/subr.c71
11 files changed, 1276 insertions, 0 deletions
diff --git a/sys/src/cmd/fax/fax2modem.c b/sys/src/cmd/fax/fax2modem.c
new file mode 100755
index 000000000..00640ab49
--- /dev/null
+++ b/sys/src/cmd/fax/fax2modem.c
@@ -0,0 +1,119 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+#include "modem.h"
+
+int
+initfaxmodem(Modem *m)
+{
+ m->fax = 1;
+ m->phase = 'A';
+ m->valid = 0;
+
+ return Eok;
+}
+
+static int
+parameters(long a[], char *s)
+{
+ char *p;
+ int i;
+
+ i = 0;
+ if((p = strchr(s, ':')) == 0)
+ return 0;
+ p++;
+ while(s = strchr(p, ',')){
+ a[i++] = strtol(p, 0, 10);
+ p = s+1;
+ }
+ if(p)
+ a[i++] = strtol(p, 0, 10);
+
+ return i;
+}
+
+int
+fcon(Modem *m)
+{
+ verbose("fcon: %s", m->response);
+ if(m->fax == 0 || m->phase != 'A')
+ return Rrerror;
+ m->phase = 'B';
+ return Rcontinue;
+}
+
+int
+ftsi(Modem *m)
+{
+ char *p, *q;
+
+ verbose("ftsi: %s", m->response);
+ if((p = strchr(m->response, '"')) == 0 || (q = strrchr(p+1, '"')) == 0)
+ return Rrerror;
+ while(*++p == ' ')
+ ;
+ *q = 0;
+ if((m->valid & Vftsi) == 0){
+ strncpy(m->ftsi, p, sizeof(m->ftsi)-1);
+ m->valid |= Vftsi;
+ }
+ return Rcontinue;
+}
+
+int
+fdcs(Modem *m)
+{
+ verbose("fdcs: %s", m->response);
+ parameters(m->fdcs, m->response);
+ m->valid |= Vfdcs;
+ return Rcontinue;
+}
+
+int
+fcfr(Modem *m)
+{
+ verbose("fcfr: %s", m->response);
+ if(m->fax == 0)
+ return Rrerror;
+ /* ???? */
+ return Rcontinue;
+}
+
+int
+fpts(Modem *m)
+{
+ verbose("fpts: %s", m->response);
+ if(m->fax == 0)
+ return Rrerror;
+ parameters(m->fpts, m->response);
+ m->valid |= Vfpts;
+ return Rcontinue;
+}
+
+int
+fet(Modem *m)
+{
+ char *p;
+
+ verbose("fet: %s", m->response);
+ if(m->fax == 0 || (p = strchr(m->response, ':')) == 0)
+ return Rrerror;
+ m->fet = strtol(p+1, 0, 10);
+ m->valid |= Vfet;
+ return Rcontinue;
+}
+
+int
+fhng(Modem *m)
+{
+ char *p;
+
+ verbose("fhng: %s", m->response);
+ if(m->fax == 0 || (p = strchr(m->response, ':')) == 0)
+ return Rrerror;
+ m->fhng = strtol(p+1, 0, 10);
+ m->valid |= Vfhng;
+ return Rhangup;
+}
diff --git a/sys/src/cmd/fax/fax2receive.c b/sys/src/cmd/fax/fax2receive.c
new file mode 100755
index 000000000..e3c905a18
--- /dev/null
+++ b/sys/src/cmd/fax/fax2receive.c
@@ -0,0 +1,193 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+#include "modem.h"
+
+static char buf[102400];
+
+static int
+page(Modem *m, char *spool)
+{
+ int count, r;
+ char c;
+
+ /*
+ * Start data reception. We should receive CONNECT in response
+ * to +FDR, then data reception starts when we send DC2.
+ */
+ m->valid &= ~(Vfhng|Vfet|Vfpts);
+ if(command(m, "AT+FDR") != Eok)
+ return Esys;
+
+ switch(response(m, 30)){
+
+ case Rconnect:
+ m->phase = 'C';
+ if((r = createfaxfile(m, spool)) != Eok)
+ return r;
+ if((r = putmchar(m, "\022")) != Eok)
+ return r;
+ break;
+
+ case Rhangup:
+ return Eok;
+
+ default:
+ return seterror(m, Eattn);
+ }
+
+ /*
+ * Receive data.
+ */
+ verbose("starting page %d", m->pageno);
+ count = 0;
+ while((r = getmchar(m, &c, 6)) == Eok){
+ if(c == '\020'){
+ if((r = getmchar(m, &c, 3)) != Eok)
+ break;
+ if(c == '\003')
+ break;
+ if(c != '\020'){
+ verbose("B%2.2ux", c);
+ continue;
+ }
+ }
+ buf[count++] = c;
+ if(count >= sizeof(buf)){
+ if(write(m->pagefd, buf, count) < 0){
+ close(m->pagefd);
+ return seterror(m, Esys);
+ }
+ count = 0;
+ }
+ }
+ verbose("page %d done, count %d", m->pageno, count);
+ if(count && write(m->pagefd, buf, count) < 0){
+ close(m->pagefd);
+ return seterror(m, Esys);
+ }
+ if(r != Eok)
+ return r;
+
+ /*
+ * Wait for either OK or ERROR.
+ */
+ switch(r = response(m, 20)){
+
+ case Rok:
+ case Rrerror:
+ return Eok;
+
+ default:
+ verbose("page: response %d", r);
+ return Eproto;
+ }
+}
+
+static int
+receive(Modem *m, char *spool)
+{
+ int r;
+
+ loop:
+ switch(r = page(m, spool)){
+
+ case Eok:
+ /*
+ * Check we have a valid page reponse.
+ */
+ if((m->valid & Vfhng) == 0 && (m->valid & (Vfet|Vfpts)) != (Vfet|Vfpts)){
+ verbose("receive: invalid page reponse: #%4.4ux", m->valid);
+ return seterror(m, Eproto);
+ }
+
+ /*
+ * Was the page successfully received?
+ * If not, try again.
+ */
+ if((m->valid & Vfpts) && m->fpts[0] != 1)
+ goto loop;
+
+ /*
+ * Another page of the same document, a new document
+ * or no more pages.
+ * If no more pages we still have to get the FHNG, so
+ * the code is just the same as if there was another
+ * page.
+ */
+ if(m->valid & Vfet){
+ switch(m->fet){
+
+ case 0: /* another page */
+ case 2: /* no more pages */
+ m->pageno++;
+ goto loop;
+
+ case 1: /* new document */
+ /*
+ * Bug: currently no way to run the
+ * fax-received process for this, so it
+ * just stays queued.
+ */
+ faxrlog(m, Eok);
+ m->pageno = 1;
+ m->time = time(0);
+ m->pid = getpid();
+ goto loop;
+ }
+
+ verbose("receive: invalid FET: %d", m->fet);
+ return seterror(m, Eproto);
+ }
+
+ /*
+ * All done or hangup error.
+ * On error remove all pages in the current document.
+ * Yik.
+ */
+ if(m->valid & Vfhng){
+ if(m->fhng == 0)
+ return Eok;
+ verbose("receive: FHNG: %d", m->fhng);
+ /*
+ for(r = 1; r <= m->pageno; r++){
+ char pageid[128];
+
+ setpageid(pageid, spool, m->time, m->pid, r);
+ remove(pageid);
+ }
+ */
+ return seterror(m, Eattn);
+ }
+ /*FALLTHROUGH*/
+
+ default:
+ return r;
+ }
+}
+
+int
+faxreceive(Modem *m, char *spool)
+{
+ int r;
+
+ verbose("faxdaemon");
+ if((r = initfaxmodem(m)) != Eok)
+ return r;
+
+ /*
+ * assume that the phone has been answered and
+ * we have received +FCON
+ */
+ m->pageno = 1;
+ m->time = time(0);
+ m->pid = getpid();
+ fcon(m);
+
+ /*
+ * I wish I knew how to set the default parameters on the
+ * MT1432 modem (+FIP in Class 2.0).
+ */
+ return receive(m, spool);
+}
diff --git a/sys/src/cmd/fax/fax2send.c b/sys/src/cmd/fax/fax2send.c
new file mode 100755
index 000000000..af97f33a3
--- /dev/null
+++ b/sys/src/cmd/fax/fax2send.c
@@ -0,0 +1,171 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+#include "modem.h"
+
+int
+faxsend(Modem *m, int argc, char *argv[])
+{
+ int c, count, r, flow;
+ char buf[128];
+
+ verbose("faxsend");
+ if((r = initfaxmodem(m)) != Eok)
+ return r;
+
+ /* telco just does the dialing */
+ r = response(m, 120);
+ switch(r){
+ case Rok:
+ break;
+ default:
+ r = seterror(m, Enoanswer);
+ return r;
+
+ }
+
+ xonoff(m, 1);
+ verbose("sending");
+ m->pageno = 1;
+ while(argc--){
+ if(m->pageno != 1)
+ sleep(1000); /* let the paper catch up */
+
+ m->valid &= ~(Vfhng|Vfet|Vfpts|Vftsi|Vfdcs);
+ if((r = openfaxfile(m, *argv)) != Eok)
+ return r;
+
+ verbose("sending geometry");
+ sprint(buf, "AT+FDT=%ld,%ld,%ld,%ld", m->df, m->vr, m->wd, m->ln);
+ if(command(m, buf) != Eok)
+ goto buggery;
+ if(response(m, 20) != Rconnect){
+ r = seterror(m, Eincompatible);
+ goto buggery;
+ }
+
+ /*
+ * Write the data, stuffing DLE's.
+ * After each bufferfull check if the remote
+ * sent us anything, e.g. CAN to abort.
+ * This also flushes out the ^S/^Q characters
+ * which the driver insists on sending us.
+ * (Could fix the driver, of course...).
+ */
+ verbose("sending data");
+ for(;;){
+ flow = 0;
+ count = 0;
+ c = 0;
+ while(count < sizeof(buf)-1){
+ if((c = Bgetc(m->bp)) < 0)
+ break;
+ buf[count++] = c;
+ if(c == '\020')
+ buf[count++] = c;
+ }
+ verbose("sending %d bytes", count);
+ if(count && write(m->fd, buf, count) < 0){
+ verbose("write failed: %r");
+ r = seterror(m, Esys);
+ goto buggery;
+ }
+ /*
+ * this does really rough flow control since the
+ * avanstar is even worse
+ */
+ verbose("flow control");
+ while((r = rawmchar(m, buf)) == Eok || flow){
+ if(r != Eok){
+ if(flow-- == 0)
+ break;
+ sleep(250);
+ continue;
+ }
+ switch(buf[0]){
+ case '\030':
+ verbose("%c", buf[0]);
+ if(write(m->fd, "\020\003", 2) < 0){
+ r = seterror(m, Esys);
+ goto buggery;
+ }
+ goto okexit;
+ case '\021':
+ flow = 0;
+ break;
+ case '\023':
+ flow = 4;
+ break;
+ case '\n':
+ break;
+ default:
+ verbose("%c", buf[0]);
+ r = seterror(m, Eproto);
+ goto buggery;
+
+ }
+ }
+ if(c < 0)
+ break;
+ }
+
+
+ /*
+ * End of page, send DLE+ETX,
+ * get OK in response.
+ */
+ verbose("sending end of page");
+ if(write(m->fd, "\020\003", 2) < 0){
+ r = seterror(m, Esys);
+ goto buggery;
+ }
+ verbose("waiting for OK");
+ if(response(m, 120) != Rok){
+ r = seterror(m, Enoresponse);
+ goto buggery;
+ }
+
+ /*
+ * Did you hear me? - IT'S THE END OF THE PAGE.
+ * Argument is 0 if more pages to follow.
+ * Should get back an FPTS with an indication
+ * as to whether the page was successfully
+ * transmitted or not.
+ */
+ sprint(buf, "AT+FET=%d", argc == 0? 2: 0);
+ if(command(m, buf) != Eok)
+ goto buggery;
+ switch(response(m, 20)){
+ case Rok:
+ break;
+ case Rhangup:
+ if(m->fhng == 0 && argc == 0)
+ break;
+ r = seterror(m, Eproto);
+ goto buggery;
+ default:
+ r = seterror(m, Enoresponse);
+ goto buggery;
+ }
+
+ if((m->valid & Vfpts) == 0 || m->fpts[0] != 1){
+ r = seterror(m, Eproto);
+ goto buggery;
+ }
+
+ Bterm(m->bp);
+ m->pageno++;
+ argv++;
+ }
+okexit:
+ xonoff(m, 0);
+ return Eok;
+
+buggery:
+ xonoff(m, 0);
+ Bterm(m->bp);
+ command(m, "AT+FK");
+ response(m, 5);
+ return r;
+}
diff --git a/sys/src/cmd/fax/file.c b/sys/src/cmd/fax/file.c
new file mode 100755
index 000000000..3da7c7a96
--- /dev/null
+++ b/sys/src/cmd/fax/file.c
@@ -0,0 +1,164 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+#include "modem.h"
+
+static long wd[5] = {
+ 1728, 2048, 2432, 1216, 864
+};
+
+void
+setpageid(char *pageid, char *spool, long time, int pid, int pageno)
+{
+ sprint(pageid, "%s/%lud.%d.%3.3d", spool, time, pid, pageno);
+}
+
+int
+createfaxfile(Modem *m, char *spool)
+{
+ setpageid(m->pageid, spool, m->time, m->pid, m->pageno);
+ verbose("openfaxfile: %s", m->pageid);
+ if((m->pagefd = create(m->pageid, OTRUNC|OWRITE, 0666)) < 0)
+ return seterror(m, Esys);
+
+ fprint(m->pagefd, "TYPE=ccitt-g31\n");
+ fprint(m->pagefd, "WINDOW=0 0 %ld -1\n", wd[m->fdcs[2]]);
+ if(m->valid & Vftsi)
+ fprint(m->pagefd, "FTSI=%s\n", m->ftsi);
+ fprint(m->pagefd, "FDCS=%lud,%lud,%lud,%lud,%lud,%lud,%lud,%lud\n",
+ m->fdcs[0], m->fdcs[1], m->fdcs[2], m->fdcs[3],
+ m->fdcs[4], m->fdcs[5], m->fdcs[6], m->fdcs[7]);
+ fprint(m->pagefd, "\n");
+
+ return Eok;
+}
+
+enum
+{
+ Gshdrsize= 0x40,
+};
+
+int
+gsopen(Modem *m)
+{
+ int n;
+ char bytes[Gshdrsize];
+
+ /*
+ * Is this gs output
+ */
+ n = Bread(m->bp, bytes, Gshdrsize);
+ if(n != Gshdrsize)
+ return seterror(m, Esys);
+ if(bytes[0]!='\0' || strcmp(bytes+1, "PC Research, Inc")!=0){
+ Bseek(m->bp, 0, 0);
+ return seterror(m, Esys);
+ }
+
+ m->valid |= Vtype;
+ if(bytes[0x1d])
+ m->vr = 1;
+ else
+ m->vr = 0;
+ m->wd = 0;
+ m->ln = 2;
+ m->df = 0;
+ return Eok;
+}
+
+int
+picopen(Modem *m)
+{
+ char *p, *q;
+ int i, x;
+
+ /*
+ * Look at the header. Every page should have a valid type.
+ * The first page should have WINDOW.
+ */
+ while(p = Brdline(m->bp, '\n')){
+ if(Blinelen(m->bp) == 1)
+ break;
+ p[Blinelen(m->bp)-1] = 0;
+
+ verbose("openfaxfile: %s", p);
+ if(strcmp("TYPE=ccitt-g31", p) == 0)
+ m->valid |= Vtype;
+ /*
+ else if(m->pageno == 1 && strncmp("PHONE=", p, 6) == 0){
+ strcpy(m->number, p+6);
+ m->valid |= Vphone;
+ }
+ */
+ else if(m->pageno == 1 && strncmp("WINDOW=", p, 7) == 0){
+ p += 7;
+ verbose("openfaxfile: WINDOW: %s", p);
+ for(i = 0; i < 4; i++){
+ x = strtol(p, &q, 10);
+ if(i == 2)
+ m->wd = x;
+ if((p = q) == 0){
+ Bterm(m->bp);
+ return seterror(m, Eproto);
+ }
+ }
+ for(i = 0; i < 5; i++){
+ if(m->wd == wd[i]){
+ m->wd = i;
+ m->valid |= Vwd;
+ break;
+ }
+ }
+ if((m->valid & Vwd) == 0){
+ Bterm(m->bp);
+ return seterror(m, Eproto);
+ }
+ }
+ else if(m->pageno == 1 && strncmp("FDCS=", p, 5) == 0){
+ p += 5;
+ m->df = m->vr = m->wd = 0;
+ m->ln = 2;
+ for(i = 0; i < 5; i++){
+ x = strtol(p, &q, 10);
+ switch(i){
+ case 0:
+ m->vr = x;
+ break;
+ case 3:
+ m->ln = x;
+ break;
+ case 4:
+ m->df = x;
+ break;
+ }
+ if((p = q) == 0){
+ Bterm(m->bp);
+ return seterror(m, Eproto);
+ }
+ if(*p++ != ',')
+ break;
+ }
+ }
+ }
+
+ verbose("openfaxfile: valid #%4.4ux", m->valid);
+ if((m->valid & (Vtype|Vwd)) != (Vtype|Vwd)){
+ Bterm(m->bp);
+ return seterror(m, Eproto);
+ }
+
+ return Eok;
+}
+
+int
+openfaxfile(Modem *m, char *file)
+{
+ if((m->bp = Bopen(file, OREAD)) == 0)
+ return seterror(m, Esys);
+ m->valid &= ~(Vtype);
+
+ if(gsopen(m) == Eok)
+ return Eok;
+ return picopen(m);
+}
diff --git a/sys/src/cmd/fax/mkfile b/sys/src/cmd/fax/mkfile
new file mode 100755
index 000000000..050228627
--- /dev/null
+++ b/sys/src/cmd/fax/mkfile
@@ -0,0 +1,45 @@
+</$objtype/mkfile
+
+TARG = faxreceive\
+ faxsend\
+
+OFILES=\
+ fax2modem.$O\
+ file.$O\
+ modem.$O\
+ subr.$O\
+
+RECEIVE=\
+ $OFILES\
+ receive.$O\
+ fax2receive.$O\
+
+SEND=\
+ $OFILES\
+ send.$O\
+ fax2send.$O\
+
+BIN=/$objtype/bin/aux
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ receive.c\
+ send.c\
+ fax2receive.c\
+ fax2send.c\
+ ${TARG:%=%.c}\
+ ${TARG:%=/386/bin/%}\
+
+</sys/src/cmd/mkmany
+
+$O.faxreceive: $RECEIVE
+ $LD -o $target $prereq
+
+$O.faxsend: $SEND
+ $LD -o $target $prereq
+
+install.rc:V:
+ mkdir /sys/lib/fax
+ cp receiverc /sys/lib/fax
diff --git a/sys/src/cmd/fax/modem.c b/sys/src/cmd/fax/modem.c
new file mode 100755
index 000000000..d0decc3e7
--- /dev/null
+++ b/sys/src/cmd/fax/modem.c
@@ -0,0 +1,220 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+#include "modem.h"
+
+typedef struct {
+ char *terse;
+ char *verbose;
+ int result;
+ int (*f)(Modem*);
+} ResultCode;
+
+static ResultCode results[] = {
+ { "0", "OK", Rok, 0, },
+ { "1", "CONNECT", Rconnect, 0, },
+ { "2", "RING", Rring, 0, },
+ { "3", "NO CARRIER", Rfailure, 0, },
+ { "4", "ERROR", Rrerror, 0, },
+ { "5", "CONNECT 1200", Rconnect, 0, },
+ { "6", "NO DIALTONE", Rfailure, 0, },
+ { "7", "BUSY", Rfailure, 0, },
+ { "8", "NO ANSWER", Rfailure, 0, },
+ { "9", "CONNECT 2400", Rconnect, 0, }, /* MT1432BA */
+ { "10", "CONNECT 2400", Rconnect, 0, }, /* Hayes */
+ { "11", "CONNECT 4800", Rconnect, 0, },
+ { "12", "CONNECT 9600", Rconnect, 0, },
+ { "13", "CONNECT 14400",Rconnect, 0, },
+ { "23", "CONNECT 1275", Rconnect, 0, }, /* MT1432BA */
+
+ { "-1", "+FCON", Rcontinue, fcon, },
+ { "-1", "+FTSI", Rcontinue, ftsi, },
+ { "-1", "+FDCS", Rcontinue, fdcs, },
+ { "-1", "+FCFR", Rcontinue, fcfr, },
+ { "-1", "+FPTS", Rcontinue, fpts, },
+ { "-1", "+FET", Rcontinue, fet, },
+ { "-1", "+FHNG", Rcontinue, fhng, },
+
+ { 0 },
+};
+
+void
+initmodem(Modem *m, int fd, int cfd, char *type, char *id)
+{
+ m->fd = fd;
+ m->cfd = cfd;
+ if(id == 0)
+ id = "Plan 9";
+ m->id = id;
+ m->t = type;
+}
+
+int
+rawmchar(Modem *m, char *p)
+{
+ Dir *d;
+ int n;
+
+ if(m->icount == 0)
+ m->iptr = m->ibuf;
+
+ if(m->icount){
+ *p = *m->iptr++;
+ m->icount--;
+ return Eok;
+ }
+
+ m->iptr = m->ibuf;
+
+ if((d = dirfstat(m->fd)) == nil){
+ verbose("rawmchar: dirfstat: %r");
+ return seterror(m, Esys);
+ }
+ n = d->length;
+ free(d);
+ if(n == 0)
+ return Enoresponse;
+
+ if(n > sizeof(m->ibuf)-1)
+ n = sizeof(m->ibuf)-1;
+ if((m->icount = read(m->fd, m->ibuf, n)) <= 0){
+ verbose("rawmchar: read: %r");
+ m->icount = 0;
+ return seterror(m, Esys);
+ }
+ *p = *m->iptr++;
+ m->icount--;
+
+ return Eok;
+}
+
+int
+getmchar(Modem *m, char *buf, long timeout)
+{
+ int r, t;
+
+ timeout += time(0);
+ while((t = time(0)) <= timeout){
+ switch(r = rawmchar(m, buf)){
+
+ case Eok:
+ return Eok;
+
+ case Enoresponse:
+ sleep(100);
+ continue;
+
+ default:
+ return r;
+ }
+ }
+ verbose("getmchar: time %ud, timeout %ud", t, timeout);
+
+ return seterror(m, Enoresponse);
+}
+
+int
+putmchar(Modem *m, char *p)
+{
+ if(write(m->fd, p, 1) < 0)
+ return seterror(m, Esys);
+ return Eok;
+}
+
+/*
+ * lines terminate with cr-lf
+ */
+static int
+getmline(Modem *m, char *buf, int len, long timeout)
+{
+ int r, t;
+ char *e = buf+len-1;
+ char last = 0;
+
+ timeout += time(0);
+ while((t = time(0)) <= timeout){
+ switch(r = rawmchar(m, buf)){
+
+ case Eok:
+ /* ignore ^s ^q which are used for flow */
+ if(*buf == '\021' || *buf == '\023')
+ continue;
+ if(*buf == '\n'){
+ /* ignore nl if its not with a cr */
+ if(last == '\r'){
+ *buf = 0;
+ return Eok;
+ }
+ continue;
+ }
+ last = *buf;
+ if(*buf == '\r')
+ continue;
+ buf++;
+ if(buf == e){
+ *buf = 0;
+ return Eok;
+ }
+ continue;
+
+ case Enoresponse:
+ sleep(100);
+ continue;
+
+ default:
+ return r;
+ }
+ }
+ verbose("getmline: time %ud, timeout %ud", t, timeout);
+
+ return seterror(m, Enoresponse);
+}
+
+int
+command(Modem *m, char *s)
+{
+ verbose("m->: %s", s);
+ if(fprint(m->fd, "%s\r", s) < 0)
+ return seterror(m, Esys);
+ return Eok;
+}
+
+/*
+ * Read till we see a message or we time out.
+ * BUG: line lengths not checked;
+ * newlines
+ */
+int
+response(Modem *m, int timeout)
+{
+ int r;
+ ResultCode *rp;
+
+ while(getmline(m, m->response, sizeof(m->response), timeout) == Eok){
+ if(m->response[0] == 0)
+ continue;
+ verbose("<-m: %s", m->response);
+ for(rp = results; rp->terse; rp++){
+ if(strncmp(rp->verbose, m->response, strlen(rp->verbose)))
+ continue;
+ r = rp->result;
+ if(rp->f && (r = (*rp->f)(m)) == Rcontinue)
+ break;
+ return r;
+ }
+ }
+
+ m->response[0] = 0;
+ return Rnoise;
+}
+
+void
+xonoff(Modem *m, int i)
+{
+ char buf[8];
+
+ sprint(buf, "x%d", i);
+ i = strlen(buf);
+ write(m->cfd, buf, i);
+}
diff --git a/sys/src/cmd/fax/modem.h b/sys/src/cmd/fax/modem.h
new file mode 100755
index 000000000..2b5ca17ff
--- /dev/null
+++ b/sys/src/cmd/fax/modem.h
@@ -0,0 +1,107 @@
+typedef struct {
+ char *t;
+ int fd;
+ int cfd;
+ char *id;
+ char response[128];
+ char error[128];
+
+ int fax;
+ char phase;
+ char ftsi[128]; /* remote ID */
+ long fdcs[8]; /* frame information */
+ long fpts[8]; /* page reception response */
+ long fet; /* post page message */
+ long fhng; /* call termination status */
+ int pageno; /* current page number */
+ char pageid[128]; /* current page file */
+ int pagefd; /* current page fd */
+ int valid; /* valid page responses */
+ long time; /* timestamp */
+ int pid;
+
+ char ibuf[1024]; /* modem input buffering */
+ char *iptr;
+ long icount;
+
+ Biobuf *bp; /* file input buffering */
+
+ /* FDCS parameters */
+ long wd; /* width */
+ long vr; /* resolution */
+ long ln; /* page size (length) */
+ long df; /* huffman encoding */
+} Modem;
+
+enum { /* ResultCodes */
+ Rok = 0,
+ Rconnect,
+ Rring,
+ Rfailure,
+ Rrerror,
+ Rcontinue,
+ Rhangup,
+ Rnoise,
+};
+
+enum { /* ErrorCodes */
+ Eok = 0, /* no error */
+ Eattn, /* can't get modem's attention */
+ Enoresponse, /* no response from modem */
+ Enoanswer, /* no answer from other side */
+ Enofax, /* other side isn't a fax machine */
+ Eincompatible, /* transmission incompatible with receiver */
+ Esys, /* system call error */
+ Eproto, /* fax protocol botch */
+};
+
+enum { /* things that are valid */
+ Vfdcs = 0x0001, /* page responses */
+ Vftsi = 0x0002,
+ Vfpts = 0x0004,
+ Vfet = 0x0008,
+ Vfhng = 0x0010,
+
+ Vwd = 0x4000,
+ Vtype = 0x8000,
+};
+
+/* fax2modem.c */
+extern int initfaxmodem(Modem*);
+extern int fcon(Modem*);
+extern int ftsi(Modem*);
+extern int fdcs(Modem*);
+extern int fcfr(Modem*);
+extern int fpts(Modem*);
+extern int fet(Modem*);
+extern int fhng(Modem*);
+
+/* fax2receive.c */
+extern int faxreceive(Modem*, char*);
+
+/* fax2send.c */
+extern int faxsend(Modem*, int, char*[]);
+
+/* modem.c */
+extern int setflow(Modem*, int);
+extern int setspeed(Modem*, int);
+extern int rawmchar(Modem*, char*);
+extern int getmchar(Modem*, char*, long);
+extern int putmchar(Modem*, char*);
+extern int command(Modem*, char*);
+extern int response(Modem*, int);
+extern void initmodem(Modem*, int, int, char*, char*);
+extern void xonoff(Modem*, int);
+
+/* spool.c */
+extern void setpageid(char*, char*, long, int, int);
+extern int createfaxfile(Modem*, char*);
+extern int openfaxfile(Modem*, char*);
+
+/* subr.c */
+extern void verbose(char*, ...);
+extern void error(char*, ...);
+extern int seterror(Modem*, int);
+extern void faxrlog(Modem*, int);
+extern void faxxlog(Modem*, int);
+extern int vflag;
diff --git a/sys/src/cmd/fax/receive.c b/sys/src/cmd/fax/receive.c
new file mode 100755
index 000000000..ece399f9f
--- /dev/null
+++ b/sys/src/cmd/fax/receive.c
@@ -0,0 +1,73 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+#include "modem.h"
+
+static Modem modems[1];
+
+static char *spool = "/mail/faxqueue";
+static char *type = "default";
+static char *receiverc = "/sys/lib/fax/receiverc";
+
+static void
+receivedone(Modem *m, int ok)
+{
+ char *argv[10], *p, time[16], pages[16];
+ int argc;
+
+ faxrlog(m, ok);
+ if(ok != Eok)
+ return;
+
+ argc = 0;
+ if(p = strrchr(receiverc, '/'))
+ argv[argc++] = p+1;
+ else
+ argv[argc++] = receiverc;
+ sprint(time, "%lud.%d", m->time, m->pid);
+ argv[argc++] = time;
+ argv[argc++] = "Y";
+ sprint(pages, "%d", m->pageno-1);
+ argv[argc++] = pages;
+ if(m->valid & Vftsi)
+ argv[argc++] = m->ftsi;
+ argv[argc] = 0;
+ exec(receiverc, argv);
+ exits("can't exec");
+}
+
+static void
+usage(void)
+{
+ fprint(2, "%s: usage: %s [-v] [-s dir]\n", argv0, argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ Modem *m;
+
+ m = &modems[0];
+
+ ARGBEGIN{
+ case 'v':
+ vflag = 1;
+ break;
+
+ case 's':
+ spool = ARGF();
+ break;
+
+ default:
+ usage();
+ break;
+
+ }ARGEND
+
+ initmodem(m, 0, -1, type, 0);
+ receivedone(m, faxreceive(m, spool));
+
+ exits(0);
+}
diff --git a/sys/src/cmd/fax/receiverc b/sys/src/cmd/fax/receiverc
new file mode 100755
index 000000000..6cc56a3cc
--- /dev/null
+++ b/sys/src/cmd/fax/receiverc
@@ -0,0 +1,58 @@
+#!/bin/rc
+spool=/mail/faxqueue
+recipients=/mail/faxqueue/faxrecipients
+
+# run mail as if we're on fs
+rm /srv/fs
+9fs fs
+bind -c /n/fs/mail/faxqueue /mail/faxqueue
+
+#
+# Arguments should be
+# time Y|N pages [ftsi]
+#
+switch($#*){
+
+case 4
+ #
+ # Check for the NYT. It's 9 pages from 'Via Fax '.
+ #
+ nyt=false
+ if(~ $2 Y && {~ $4 'Via Fax '}){
+ switch(`{date|sed 's/ .*//'}){
+ case Mon Tue Wed Thu Fri
+ hour=`{date|sed 's/.* ([0-9][0-9]):.*/\1/'}
+ if(test $3 -gt 7 -a '(' $hour -lt 7 -o $hour -ge 21 ')')
+ nyt=true
+ case *
+ if(test $3 -gt 7)
+ nyt=true
+ }
+ }
+ switch($nyt){
+
+ case true
+ to=`{seq 0 1 $3}
+ for(i in `{seq 2 1 $3}){
+ switch($i){
+
+ case ?
+ ext=00$i
+ case ??
+ ext=0$i
+ case ???
+ ext=$i
+ }
+ cp $spool/$1.$ext /n/fs/lib/nyt/nyt.$to($i)
+ }
+ cp $spool/$1.1 /n/fs/lib/nyt/nyt.$3
+ rm -f $spool/$1.*
+ case *
+ {echo $*; echo FAX: page -w $spool/$1.'*'} | mail `{cat $recipients}
+ }
+case 3
+ {echo $*; echo FAX: page -w $spool/$1.'*'} | mail `{cat $recipients}
+case *
+ {echo $*; echo FAX: page -w $spool/$1.'*'} | mail postmaster
+}
+exit 0
diff --git a/sys/src/cmd/fax/send.c b/sys/src/cmd/fax/send.c
new file mode 100755
index 000000000..9fa7342f6
--- /dev/null
+++ b/sys/src/cmd/fax/send.c
@@ -0,0 +1,55 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+#include "modem.h"
+
+static Modem modems[1];
+
+static void
+usage(void)
+{
+ fprint(2, "%s: usage: %s [-v] number pages\n", argv0, argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ int fd, cfd, r;
+ Modem *m;
+ char *addr;
+
+ m = &modems[0];
+
+ ARGBEGIN{
+ case 'v':
+ vflag = 1;
+ break;
+ default:
+ usage();
+ break;
+
+ }ARGEND
+
+ if(argc <= 1)
+ usage();
+ verbose("send: %s %s...", argv[0], argv[1]);
+
+ addr = netmkaddr(*argv, "telco", "fax!9600");
+ fd = dial(addr, 0, 0, &cfd);
+ if(fd < 0){
+ fprint(2, "faxsend: can't dial %s: %r\n", addr);
+ exits("Retry, can't dial");
+ }
+ initmodem(m, fd, cfd, 0, 0);
+ argc--; argv++;
+ r = faxsend(m, argc, argv);
+ if(r != Eok){
+ fprint(2, "faxsend: %s\n", m->error);
+ syslog(0, "fax", "failed %s %s: %s", argv[0], argv[1], m->error);
+ exits(m->error);
+ }
+ syslog(0, "fax", "success %s %s", argv[0], argv[1]);
+ exits(0);
+}
diff --git a/sys/src/cmd/fax/subr.c b/sys/src/cmd/fax/subr.c
new file mode 100755
index 000000000..20248d0a5
--- /dev/null
+++ b/sys/src/cmd/fax/subr.c
@@ -0,0 +1,71 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+#include "modem.h"
+
+int vflag;
+
+void
+verbose(char *fmt, ...)
+{
+ va_list arg;
+ char buf[512];
+
+ if(vflag){
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ syslog(0, "fax", buf);
+ }
+}
+
+void
+error(char *fmt, ...)
+{
+ va_list arg;
+ char buf[512];
+ int n;
+
+ n = sprint(buf, "%s: ", argv0);
+ va_start(arg, fmt);
+ vseprint(buf+n, buf+sizeof(buf)-n, fmt, arg);
+ va_end(arg);
+ fprint(2, buf);
+ if(vflag)
+ print(buf+n);
+ exits("error");
+}
+
+static char *errors[] = {
+ [Eok] "no error",
+ [Eattn] "can't get modem's attention",
+ [Enoanswer] "Retry, no answer or busy",
+ [Enoresponse] "Retry, no response from modem",
+ [Eincompatible] "Retry, incompatible",
+ [Esys] "Retry, system call error",
+ [Eproto] "Retry, fax protocol botch",
+};
+
+int
+seterror(Modem *m, int error)
+{
+ if(error == Esys)
+ sprint(m->error, "%s: %r", errors[Esys]);
+ else
+ strcpy(m->error, errors[error]);
+ verbose("seterror: %s", m->error);
+ return error;
+}
+
+void
+faxrlog(Modem *m, int ok)
+{
+ char buf[1024];
+ int n;
+
+ n = sprint(buf, "receive %lud %c %d", m->time, ok == Eok ? 'Y': 'N', m->pageno-1);
+ if(ok == Eok && (m->valid & Vftsi))
+ sprint(buf+n, " %s", m->ftsi);
+ syslog(0, "fax", buf);
+}