summaryrefslogtreecommitdiff
path: root/sys/src/cmd/con
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/con
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/con')
-rwxr-xr-xsys/src/cmd/con/con.c683
-rwxr-xr-xsys/src/cmd/con/hayes.c240
-rwxr-xr-xsys/src/cmd/con/mkfile18
-rwxr-xr-xsys/src/cmd/con/xmr.c182
-rwxr-xr-xsys/src/cmd/con/xms.c215
5 files changed, 1338 insertions, 0 deletions
diff --git a/sys/src/cmd/con/con.c b/sys/src/cmd/con/con.c
new file mode 100755
index 000000000..a8b697700
--- /dev/null
+++ b/sys/src/cmd/con/con.c
@@ -0,0 +1,683 @@
+#include <u.h>
+#include <libc.h>
+
+int debug; /* true if debugging */
+int ctl = -1; /* control fd (for break's) */
+int raw; /* true if raw is on */
+int consctl = -1; /* control fd for cons */
+int ttypid; /* pid's if the 2 processes (used to kill them) */
+int outfd = 1; /* local output file descriptor */
+int cooked; /* non-zero forces cooked mode */
+int returns; /* non-zero forces carriage returns not to be filtered out */
+int crtonl; /* non-zero forces carriage returns to be converted to nls coming from net */
+int strip; /* strip off parity bits */
+char firsterr[2*ERRMAX];
+char transerr[2*ERRMAX];
+int limited;
+char *remuser; /* for BSD rlogin authentication */
+int verbose;
+int baud;
+int notkbd;
+int nltocr; /* translate kbd nl to cr and vice versa */
+
+static char *srv;
+
+#define MAXMSG (2*8192)
+
+int dodial(char*, char*, char*);
+void fromkbd(int);
+void fromnet(int);
+long iread(int, void*, int);
+long iwrite(int, void*, int);
+int menu(int);
+void notifyf(void*, char*);
+void pass(int, int, int);
+void rawoff(void);
+void rawon(void);
+void stdcon(int);
+char* system(int, char*);
+void dosystem(int, char*);
+int wasintr(void);
+void punt(char*);
+char* syserr(void);
+void seterr(char*);
+
+/* protocols */
+void device(char*, char*);
+void rlogin(char*, char*);
+void simple(char*, char*);
+
+void
+usage(void)
+{
+ punt("usage: con [-CdnrRsTv] [-b baud] [-l [user]] [-c cmd] [-S svc] "
+ "net!host[!service]");
+}
+
+void
+main(int argc, char *argv[])
+{
+ char *dest;
+ char *cmd = 0;
+
+ returns = 1;
+ ARGBEGIN{
+ case 'b':
+ baud = atoi(EARGF(usage()));
+ break;
+ case 'C':
+ cooked = 1;
+ break;
+ case 'c':
+ cmd = EARGF(usage());
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'l':
+ limited = 1;
+ if(argv[1] != nil && argv[1][0] != '-')
+ remuser = EARGF(usage());
+ break;
+ case 'n':
+ notkbd = 1;
+ break;
+ case 'r':
+ returns = 0;
+ break;
+ case 's':
+ strip = 1;
+ break;
+ case 'S':
+ srv = EARGF(usage());
+ break;
+ case 'R':
+ nltocr = 1;
+ break;
+ case 'T':
+ crtonl = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1){
+ if(remuser == 0)
+ usage();
+ dest = remuser;
+ remuser = 0;
+ } else
+ dest = argv[0];
+ if(*dest == '/' && strchr(dest, '!') == 0)
+ device(dest, cmd);
+ else if(limited){
+ simple(dest, cmd); /* doesn't return if dialout succeeds */
+ rlogin(dest, cmd); /* doesn't return if dialout succeeds */
+ } else {
+ rlogin(dest, cmd); /* doesn't return if dialout succeeds */
+ simple(dest, cmd); /* doesn't return if dialout succeeds */
+ }
+ punt(firsterr);
+}
+
+/*
+ * just dial and use as a byte stream with remote echo
+ */
+void
+simple(char *dest, char *cmd)
+{
+ int net;
+
+ net = dodial(dest, 0, 0);
+ if(net < 0)
+ return;
+
+ if(cmd)
+ dosystem(net, cmd);
+
+ if(!cooked)
+ rawon();
+ stdcon(net);
+ exits(0);
+}
+
+/*
+ * dial, do UCB authentication, use as a byte stream with local echo
+ *
+ * return if dial failed
+ */
+void
+rlogin(char *dest, char *cmd)
+{
+ int net;
+ char buf[128];
+ char *p;
+ char *localuser;
+
+ /* only useful on TCP */
+ if(strchr(dest, '!')
+ && (strncmp(dest, "tcp!", 4)!=0 && strncmp(dest, "net!", 4)!=0))
+ return;
+
+ net = dodial(dest, "tcp", "login");
+ if(net < 0)
+ return;
+
+ /*
+ * do UCB rlogin authentication
+ */
+ localuser = getuser();
+ if(remuser == 0){
+ if(limited)
+ remuser = ":";
+ else
+ remuser = localuser;
+ }
+ p = getenv("TERM");
+ if(p == 0)
+ p = "p9";
+ if(write(net, "", 1)<0
+ || write(net, localuser, strlen(localuser)+1)<0
+ || write(net, remuser, strlen(remuser)+1)<0
+ || write(net, p, strlen(p)+1)<0){
+ close(net);
+ punt("BSD authentication failed");
+ }
+ if(read(net, buf, 1) != 1)
+ punt("BSD authentication failed1");
+ if(buf[0] != 0){
+ fprint(2, "con: remote error: ");
+ while(read(net, buf, 1) == 1){
+ write(2, buf, 1);
+ if(buf[0] == '\n')
+ break;
+ }
+ exits("read");
+ }
+
+ if(cmd)
+ dosystem(net, cmd);
+
+ if(!cooked)
+ rawon();
+ nltocr = 1;
+ stdcon(net);
+ exits(0);
+}
+
+/*
+ * just open a device and use it as a connection
+ */
+void
+device(char *dest, char *cmd)
+{
+ int net;
+ char cname[128];
+
+ net = open(dest, ORDWR);
+ if(net < 0) {
+ fprint(2, "con: cannot open %s: %r\n", dest);
+ exits("open");
+ }
+ snprint(cname, sizeof cname, "%sctl", dest);
+ ctl = open(cname, ORDWR);
+ if (baud > 0) {
+ if(ctl >= 0){
+ /* set speed and use fifos if available */
+ fprint(ctl, "b%d i1", baud);
+ }
+ else
+ fprint(2, "con: cannot open %s: %r\n", cname);
+ }
+
+ if(cmd)
+ dosystem(net, cmd);
+
+ if(!cooked)
+ rawon();
+ stdcon(net);
+ exits(0);
+}
+
+/*
+ * ignore interrupts
+ */
+void
+notifyf(void *a, char *msg)
+{
+ USED(a);
+
+ if(strstr(msg, "yankee"))
+ noted(NDFLT);
+ if(strstr(msg, "closed pipe")
+ || strcmp(msg, "interrupt") == 0
+ || strcmp(msg, "hangup") == 0)
+ noted(NCONT);
+ noted(NDFLT);
+}
+
+/*
+ * turn keyboard raw mode on
+ */
+void
+rawon(void)
+{
+ if(debug)
+ fprint(2, "rawon\n");
+ if(raw)
+ return;
+ if(consctl < 0)
+ consctl = open("/dev/consctl", OWRITE);
+ if(consctl < 0){
+// fprint(2, "can't open consctl\n");
+ return;
+ }
+ write(consctl, "rawon", 5);
+ raw = 1;
+}
+
+/*
+ * turn keyboard raw mode off
+ */
+void
+rawoff(void)
+{
+ if(debug)
+ fprint(2, "rawoff\n");
+ if(raw == 0)
+ return;
+ if(consctl < 0)
+ consctl = open("/dev/consctl", OWRITE);
+ if(consctl < 0){
+// fprint(2, "can't open consctl\n");
+ return;
+ }
+ write(consctl, "rawoff", 6);
+ raw = 0;
+}
+
+/*
+ * control menu
+ */
+#define STDHELP "\t(b)reak, (q)uit, (i)nterrupt, toggle printing (r)eturns, (.)continue, (!cmd)\n"
+
+int
+menu(int net)
+{
+ char buf[MAXMSG];
+ long n;
+ int done;
+ int wasraw = raw;
+
+ if(wasraw)
+ rawoff();
+
+ fprint(2, ">>> ");
+ for(done = 0; !done; ){
+ n = read(0, buf, sizeof(buf)-1);
+ if(n <= 0)
+ return -1;
+ buf[n] = 0;
+ switch(buf[0]){
+ case '!':
+ print(buf);
+ system(net, buf+1);
+ print("!\n");
+ done = 1;
+ break;
+ case '.':
+ done = 1;
+ break;
+ case 'q':
+ return -1;
+ case 'i':
+ buf[0] = 0x1c;
+ write(net, buf, 1);
+ done = 1;
+ break;
+ case 'b':
+ if(ctl >= 0)
+ write(ctl, "k", 1);
+ done = 1;
+ break;
+ case 'r':
+ returns = 1-returns;
+ done = 1;
+ break;
+ default:
+ fprint(2, STDHELP);
+ break;
+ }
+ if(!done)
+ fprint(2, ">>> ");
+ }
+
+ if(wasraw)
+ rawon();
+ else
+ rawoff();
+ return 0;
+}
+
+void
+post(char *srv, int fd)
+{
+ int f;
+ char buf[32];
+
+ f = create(srv, OWRITE /* |ORCLOSE */ , 0666);
+ if(f < 0)
+ sysfatal("create %s: %r", srv);
+ snprint(buf, sizeof buf, "%d", fd);
+ if(write(f, buf, strlen(buf)) != strlen(buf))
+ sysfatal("write %s: %r", srv);
+ close(f);
+}
+
+/*
+ * the real work. two processes pass bytes back and forth between the
+ * terminal and the network.
+ */
+void
+stdcon(int net)
+{
+ int netpid;
+ int p[2];
+ char *svc;
+
+ svc = nil;
+ if (srv) {
+ if(pipe(p) < 0)
+ sysfatal("pipe: %r");
+ if (srv[0] != '/')
+ svc = smprint("/srv/%s", srv);
+ else
+ svc = srv;
+ post(svc, p[0]);
+ close(p[0]);
+ dup(p[1], 0);
+ dup(p[1], 1);
+ /* pipe is now std in & out */
+ }
+ ttypid = getpid();
+ switch(netpid = rfork(RFMEM|RFPROC)){
+ case -1:
+ perror("con");
+ exits("fork");
+ case 0:
+ notify(notifyf);
+ fromnet(net);
+ if (svc)
+ remove(svc);
+ postnote(PNPROC, ttypid, "die yankee dog");
+ exits(0);
+ default:
+ notify(notifyf);
+ fromkbd(net);
+ if (svc)
+ remove(svc);
+ if(notkbd)
+ for(;;)
+ sleep(0);
+ postnote(PNPROC, netpid, "die yankee dog");
+ exits(0);
+ }
+}
+
+/*
+ * Read the keyboard and write it to the network. '^\' gets us into
+ * the menu.
+ */
+void
+fromkbd(int net)
+{
+ long n;
+ char buf[MAXMSG];
+ char *p, *ep;
+ int eofs;
+
+ eofs = 0;
+ for(;;){
+ n = read(0, buf, sizeof(buf));
+ if(n < 0){
+ if(wasintr()){
+ if(!raw){
+ buf[0] = 0x7f;
+ n = 1;
+ } else
+ continue;
+ } else
+ return;
+ }
+ if(n == 0){
+ if(++eofs > 32)
+ return;
+ } else
+ eofs = 0;
+ if(n && memchr(buf, 0x1c, n)){
+ if(menu(net) < 0)
+ return;
+ }else{
+ if(!raw && n==0){
+ buf[0] = 0x4;
+ n = 1;
+ }
+ if(nltocr){
+ ep = buf+n;
+ for(p = buf; p < ep; p++)
+ switch(*p){
+ case '\r':
+ *p = '\n';
+ break;
+ case '\n':
+ *p = '\r';
+ break;
+ }
+ }
+ if(iwrite(net, buf, n) != n)
+ return;
+ }
+ }
+}
+
+/*
+ * Read from the network and write to the screen.
+ * Filter out spurious carriage returns.
+ */
+void
+fromnet(int net)
+{
+ long n;
+ char buf[MAXMSG];
+ char *cp, *ep;
+
+ for(;;){
+ n = iread(net, buf, sizeof(buf));
+ if(n < 0)
+ return;
+ if(n == 0)
+ continue;
+
+ if (strip)
+ for (cp=buf; cp<buf+n; cp++)
+ *cp &= 0177;
+
+ if(crtonl) {
+ /* convert cr's to nl's */
+ for (cp = buf; cp < buf + n; cp++)
+ if (*cp == '\r')
+ *cp = '\n';
+ }
+ else if(!returns){
+ /* convert cr's to null's */
+ cp = buf;
+ ep = buf + n;
+ while(cp < ep && (cp = memchr(cp, '\r', ep-cp))){
+ memmove(cp, cp+1, ep-cp-1);
+ ep--;
+ n--;
+ }
+ }
+
+ if(n > 0 && iwrite(outfd, buf, n) != n){
+ if(outfd == 1)
+ return;
+ outfd = 1;
+ if(iwrite(1, buf, n) != n)
+ return;
+ }
+ }
+}
+
+/*
+ * dial and return a data connection
+ */
+int
+dodial(char *dest, char *net, char *service)
+{
+ char name[128];
+ char devdir[128];
+ int data;
+
+ devdir[0] = 0;
+ strcpy(name, netmkaddr(dest, net, service));
+ data = dial(name, 0, devdir, &ctl);
+ if(data < 0){
+ seterr(name);
+ return -1;
+ }
+ fprint(2, "connected to %s on %s\n", name, devdir);
+ return data;
+}
+
+void
+dosystem(int fd, char *cmd)
+{
+ char *p;
+
+ p = system(fd, cmd);
+ if(p){
+ print("con: %s terminated with %s\n", cmd, p);
+ exits(p);
+ }
+}
+
+/*
+ * run a command with the network connection as standard IO
+ */
+char *
+system(int fd, char *cmd)
+{
+ int pid;
+ int p;
+ static Waitmsg msg;
+ int pfd[2];
+ int n;
+ char buf[4096];
+
+ if(pipe(pfd) < 0){
+ perror("pipe");
+ return "pipe failed";
+ }
+ outfd = pfd[1];
+
+ close(consctl);
+ consctl = -1;
+ switch(pid = fork()){
+ case -1:
+ perror("con");
+ return "fork failed";
+ case 0:
+ close(pfd[1]);
+ dup(pfd[0], 0);
+ dup(fd, 1);
+ close(ctl);
+ close(fd);
+ close(pfd[0]);
+ if(*cmd)
+ execl("/bin/rc", "rc", "-c", cmd, nil);
+ else
+ execl("/bin/rc", "rc", nil);
+ perror("con");
+ exits("exec");
+ break;
+ default:
+ close(pfd[0]);
+ while((n = read(pfd[1], buf, sizeof(buf))) > 0)
+ if(write(fd, buf, n) != n)
+ break;
+ p = waitpid();
+ outfd = 1;
+ close(pfd[1]);
+ if(p < 0 || p != pid)
+ return "lost child";
+ break;
+ }
+ return msg.msg;
+}
+
+int
+wasintr(void)
+{
+ return strcmp(syserr(), "interrupted") == 0;
+}
+
+void
+punt(char *msg)
+{
+ if(*msg == 0)
+ msg = transerr;
+ fprint(2, "con: %s\n", msg);
+ exits(msg);
+}
+
+char*
+syserr(void)
+{
+ static char err[ERRMAX];
+ errstr(err, sizeof err);
+ return err;
+}
+
+void
+seterr(char *addr)
+{
+ char *se = syserr();
+
+ if(verbose)
+ fprint(2, "'%s' calling %s\n", se, addr);
+ if(firsterr[0] && (strstr(se, "translate") ||
+ strstr(se, "file does not exist") ||
+ strstr(se, "unknown address") ||
+ strstr(se, "directory entry not found")))
+ return;
+ strcpy(firsterr, se);
+}
+
+
+long
+iread(int f, void *a, int n)
+{
+ long m;
+
+ for(;;){
+ m = read(f, a, n);
+ if(m >= 0 || !wasintr())
+ break;
+ }
+ return m;
+}
+
+long
+iwrite(int f, void *a, int n)
+{
+ long m;
+
+ m = write(f, a, n);
+ if(m < 0 && wasintr())
+ return n;
+ return m;
+}
diff --git a/sys/src/cmd/con/hayes.c b/sys/src/cmd/con/hayes.c
new file mode 100755
index 000000000..6ade8be86
--- /dev/null
+++ b/sys/src/cmd/con/hayes.c
@@ -0,0 +1,240 @@
+#include <u.h>
+#include <libc.h>
+
+void setspeed(int, int);
+int getspeed(char*, int);
+void godial(int, int, char*);
+int readmsg(int, int);
+void punt(char*, ...);
+
+int pulsed;
+int verbose;
+char msgbuf[128]; /* last message read */
+
+enum
+{
+ Ok,
+ Success,
+ Failure,
+ Noise,
+};
+
+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 },
+};
+
+void
+usage(void)
+{
+ punt("usage: hayes [-p] telno [device]");
+}
+
+void
+main(int argc, char **argv)
+{
+ int data = -1;
+ int ctl = -1;
+ char *cname;
+
+ ARGBEGIN{
+ case 'p':
+ pulsed = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ switch(argc){
+ case 1:
+ data = 1;
+ break;
+ case 2:
+ data = open(argv[1], ORDWR);
+ if(data < 0){
+ fprint(2, "hayes: %r opening %s\n", argv[1]);
+ exits("hayes");
+ }
+ cname = malloc(strlen(argv[1])+4);
+ sprint(cname, "%sctl", argv[1]);
+ ctl = open(cname, ORDWR);
+ free(cname);
+ break;
+ default:
+ usage();
+ }
+ godial(data, ctl, argv[0]);
+ exits(0);
+}
+
+int
+send(int fd, char *x)
+{
+ return write(fd, x, strlen(x));
+}
+
+void
+godial(int data, int ctl, char *number)
+{
+ char *dialstr;
+ int m;
+ int baud;
+
+ /* get the modem's attention */
+ if(send(data, "\r+++\r") < 0)
+ punt("failed write");
+ readmsg(data, 2);
+ sleep(1000);
+
+ /* initialize */
+ if(send(data, "ATZ\r") < 0)
+ punt("failed write");
+ m = readmsg(data, 2);
+ if(m < 0)
+ punt("can't get modem's attention");
+
+ /*
+ * Q0 = report result codes
+ * V1 = full word result codes
+ * W1 = negotiation progress codes enabled
+ * E1 = echo commands
+ * M1 = speaker on until on-line
+ */
+ if(send(data, "ATQ0V1E1M1\r") < 0)
+ punt("failed write");
+ m = readmsg(data, 2);
+ if(m != Ok)
+ punt("can't get modem's attention");
+ if(send(data, "ATW1\r") < 0)
+ punt("failed write");
+ readmsg(data, 2);
+ sleep(1000);
+
+ /* godial */
+ dialstr = malloc(6+strlen(number));
+ sprint(dialstr, "ATD%c%s\r", pulsed ? 'P' : 'T', number);
+ if(send(data, dialstr) < 0) {
+ free(dialstr);
+ punt("failed write");
+ }
+ free(dialstr);
+ m = readmsg(data, 60);
+ if(m != Success)
+ punt("dial failed: %s", msgbuf);
+ baud = getspeed(msgbuf, 9600);
+ setspeed(ctl, baud);
+ fprint(2, "hayes: connected at %d baud\n", baud);
+}
+
+/*
+ * read until we see a message or we time out
+ */
+int
+readmsg(int f, int secs)
+{
+ ulong start;
+ char *p;
+ int len;
+ Dir *d;
+ Msg *pp;
+
+ p = msgbuf;
+ len = sizeof(msgbuf) - 1;
+ for(start = time(0); time(0) <= start+secs;){
+ if((d = dirfstat(f)) == nil)
+ punt("failed read");
+ if(d->length == 0){
+ free(d);
+ sleep(100);
+ continue;
+ }
+ free(d);
+ if(read(f, p, 1) <= 0)
+ punt("failed read");
+ if(*p == '\n' || *p == '\r' || len == 0){
+ *p = 0;
+ if(verbose && p != msgbuf)
+ fprint(2, "%s\n", msgbuf);
+ for(pp = msgs; pp->text; pp++)
+ if(strncmp(pp->text, msgbuf, strlen(pp->text))==0)
+ return pp->type;
+ start = time(0);
+ p = msgbuf;
+ len = sizeof(msgbuf) - 1;
+ continue;
+ }
+ len--;
+ p++;
+ }
+ strcpy(msgbuf, "No response from modem");
+ return 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(int ctl, int baud)
+{
+ char buf[32];
+
+ if(ctl < 0)
+ return;
+ sprint(buf, "b%d", baud);
+ write(ctl, buf, strlen(buf));
+ write(ctl, "m1", 2);
+}
+
+
+void
+punt(char *fmt, ...)
+{
+ char buf[256];
+ va_list arg;
+ int n;
+
+ strcpy(buf, "hayes: ");
+ va_start(arg, fmt);
+ n = vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg) - buf;
+ va_end(arg);
+ buf[n] = '\n';
+ write(2, buf, n+1);
+ exits("hayes");
+}
diff --git a/sys/src/cmd/con/mkfile b/sys/src/cmd/con/mkfile
new file mode 100755
index 000000000..2f04039f4
--- /dev/null
+++ b/sys/src/cmd/con/mkfile
@@ -0,0 +1,18 @@
+</$objtype/mkfile
+
+TARG=con\
+ xms\
+ xmr\
+ hayes\
+
+BIN=/$objtype/bin
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${TARG:%=%.c}\
+ /sys/man/1/con\
+
+</sys/src/cmd/mkmany
+
+
diff --git a/sys/src/cmd/con/xmr.c b/sys/src/cmd/con/xmr.c
new file mode 100755
index 000000000..3f57610ef
--- /dev/null
+++ b/sys/src/cmd/con/xmr.c
@@ -0,0 +1,182 @@
+#include <u.h>
+#include <libc.h>
+
+enum {
+ Soh= 0x1,
+ Eot= 0x4,
+ Ack= 0x6,
+ Nak= 0x15,
+ Cancel= 0x18,
+};
+
+int notifyf(void*, char*);
+int readupto(uchar*, int);
+int receive(int, uchar);
+void send(int);
+
+int debug, dfd;
+
+void
+main(int argc, char **argv)
+{
+ int fd;
+ uchar seqno;
+ ulong bytes;
+
+ ARGBEGIN {
+ case 'd':
+ dfd = 2;
+ debug = 1;
+ break;
+ } ARGEND
+
+ if(argc != 1){
+ fprint(2, "usage: xmr filename\n");
+ exits("usage");
+ }
+ fd = open("/dev/consctl", OWRITE);
+ if(fd >= 0)
+ write(fd, "rawon", 5);
+ fd = create(argv[0], ORDWR, 0666);
+ if(fd < 0){
+ perror("xmr: create");
+ exits("create");
+ }
+
+ atnotify(notifyf, 1);
+ send(Nak);
+
+ /*
+ * keep receiving till the other side gives up
+ */
+ bytes = 0;
+ for(seqno = 1; ; seqno++){
+ if(receive(fd, seqno) == -1)
+ break;
+ bytes += 128;
+ }
+ fprint(2, "xmr: received %ld bytes\n", bytes);
+ exits(0);
+}
+
+void
+send(int byte)
+{
+ uchar c;
+
+ c = byte;
+ if(write(1, &c, 1) != 1){
+ fprint(2, "xmr: hungup\n");
+ exits("hangup");
+ }
+}
+
+int
+readupto(uchar *a, int len)
+{
+ int n;
+ int sofar;
+
+ for(sofar = 0; sofar < len; sofar += n){
+ n = read(0, a, len-sofar);
+ if(n <= 0){
+ send(Nak);
+ return sofar;
+ }
+ if(*a == Eot || *a == Cancel)
+ return sofar + n;
+ a += n;
+ }
+ return sofar;
+
+}
+
+int
+receive(int fd, uchar seqno)
+{
+ uchar buf[128+4];
+ uchar sum;
+ uchar *p;
+ int n;
+ int tries;
+ int have;
+
+ for(have = 0, tries = 0;; tries++){
+ if(debug)
+ fprint(dfd, "have == %d\n", have);
+ if(tries > 10){
+ fprint(2, "xmr: timed out\n");
+ if(debug)
+ close(dfd);
+ exits("timeout");
+ }
+
+ /* try to gather up a block */
+ alarm(15*1000);
+ n = readupto(&buf[have], 132-have);
+ alarm(0);
+ have += n;
+ if(have){
+ switch(buf[0]){
+ case Eot:
+ send(Ack);
+ return -1;
+ case Cancel:
+ fprint(2, "xmr: transfer aborted by sender\n");
+ exits("cancel");
+ }
+ }
+ if(have != 132)
+ continue;
+
+ /* checksum */
+ for(p = buf, sum = 0; p < &buf[128+3]; p++)
+ sum += *p;
+
+ /* If invalid block, resynchronize */
+ if(buf[0] != Soh || buf[2] != (255-buf[1]) || sum != buf[131]){
+ if(debug){
+ fprint(dfd, "resync %2.2ux %d %d %ux %ux\n", buf[0],
+ buf[1], buf[2], sum, buf[131]);
+ write(dfd, (char*)buf+3, 128);
+ fprint(dfd, "\n");
+ }
+ p = memchr(buf+1, Soh, 131);
+ if(p){
+ have = 132-(p-buf);
+ memmove(buf, p, have);
+ } else
+ have = 0;
+ continue;
+ }
+
+ /* it's probably a real block, so dump it if there's an error */
+ have = 0;
+
+ /* if this is the last block, ack */
+ if(buf[1] == seqno-1){
+ tries = 0;
+ send(Ack);
+ }else if(buf[1] == seqno){
+ if(debug)
+ fprint(dfd, "Ack\n");
+ send(Ack);
+ if(write(fd, buf+3, 128) != 128){
+ fprint(2, "xmr: abort, error writing file\n");
+ exits("write");
+ }
+ return 0;
+ } else {
+ send(Nak);
+ }
+ }
+}
+
+int
+notifyf(void *a, char *msg)
+{
+ USED(a);
+ if(strcmp(msg, "alarm") == 0)
+ return 1;
+ return 0;
+}
diff --git a/sys/src/cmd/con/xms.c b/sys/src/cmd/con/xms.c
new file mode 100755
index 000000000..2d46f845a
--- /dev/null
+++ b/sys/src/cmd/con/xms.c
@@ -0,0 +1,215 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+enum {
+ Soh= 0x1,
+ Stx= 0x2,
+ Eot= 0x4,
+ Ack= 0x6,
+ Nak= 0x15,
+ Cancel= 0x18,
+};
+
+int send(uchar*, int);
+int notifyf(void*, char*);
+
+int debug, progress, onek;
+
+void
+errorout(int ctl, int bytes)
+{
+ uchar buf[2];
+
+ buf[0] = Cancel;
+ write(1, buf, 1);
+ fprint(2, "\nxms: gave up after %d bytes\n", bytes);
+ write(ctl, "rawoff", 6);
+ exits("cancel");
+}
+
+ushort
+updcrc(int c, ushort crc)
+{
+ int count;
+
+ for (count=8; --count>=0;) {
+ if (crc & 0x8000) {
+ crc <<= 1;
+ crc += (((c<<=1) & 0400) != 0);
+ crc ^= 0x1021;
+ }
+ else {
+ crc <<= 1;
+ crc += (((c<<=1) & 0400) != 0);
+ }
+ }
+ return crc;
+}
+
+void
+main(int argc, char **argv)
+{
+ uchar c;
+ uchar buf[1024+5];
+ uchar seqno;
+ int fd, ctl;
+ long n;
+ int sum;
+ uchar *p;
+ int bytes;
+ int crcmode;
+
+ ARGBEGIN{
+ case 'd':
+ debug = 1;
+ break;
+ case 'p':
+ progress = 1;
+ break;
+ case '1':
+ onek = 1;
+ break;
+ }ARGEND
+
+ if(argc != 1){
+ fprint(2, "usage: xms filename\n");
+ exits("usage");
+ }
+ fd = open(argv[0], OREAD);
+ if(fd < 0){
+ perror("xms");
+ exits("open");
+ }
+
+ ctl = open("/dev/consctl", OWRITE);
+ if(ctl < 0){
+ perror("xms");
+ exits("consctl");
+ }
+ write(ctl, "rawon", 5);
+
+ /* give the other side a 30 seconds to signal ready */
+ atnotify(notifyf, 1);
+ alarm(30*1000);
+ crcmode = 0;
+ for(;;){
+ if(read(0, &c, 1) != 1){
+ fprint(2, "xms: timeout\n");
+ exits("timeout");
+ }
+ c = c & 0x7f;
+ if(c == Nak)
+ break;
+ if(c == 'C') {
+ if (debug)
+ fprint(2, "crc mode engaged\n");
+ crcmode = 1;
+ break;
+ }
+ }
+ alarm(0);
+
+ /* send the file in 128/1024 byte chunks */
+ for(bytes = 0, seqno = 1; ; bytes += n, seqno++){
+ n = read(fd, buf+3, onek ? 1024 : 128);
+ if(n < 0)
+ exits("read");
+ if(n == 0)
+ break;
+ if(n < (onek ? 1024 : 128))
+ memset(&buf[n+3], 0, (onek ? 1024 : 128)-n);
+ buf[0] = onek ? Stx : Soh;
+ buf[1] = seqno;
+ buf[2] = 255 - seqno;
+
+ /* calculate checksum and stuff into last byte */
+ if (crcmode) {
+ unsigned short crc;
+ crc = 0;
+ for(p = buf + 3; p < &buf[(onek ? 1024 : 128)+3]; p++)
+ crc = updcrc(*p, crc);
+ crc = updcrc(0, crc);
+ crc = updcrc(0, crc);
+ buf[(onek ? 1024 : 128) + 3] = crc >> 8;
+ buf[(onek ? 1024 : 128) + 4] = crc;
+ }
+ else {
+ sum = 0;
+ for(p = buf + 3; p < &buf[(onek ? 1024 : 128)+3]; p++)
+ sum += *p;
+ buf[(onek ? 1024 : 128) + 3] = sum;
+ }
+
+ if(send(buf, (onek ? 1024 : 128) + 4 + crcmode) < 0)
+ errorout(ctl, bytes);
+ if (progress && bytes % 10240 == 0)
+ fprint(2, "%dK\n", bytes / 1024);
+ }
+
+ /* tell other side we're done */
+ buf[0] = Eot;
+ if(send(buf, 1) < 0)
+ errorout(ctl, bytes);
+
+ fprint(2, "xms: %d bytes transmitted\n", bytes);
+ write(ctl, "rawoff", 6);
+ exits(0);
+}
+
+/*
+ * send a message till it's acked or we give up
+ */
+int
+send(uchar *buf, int len)
+{
+ int tries;
+ int n;
+ uchar c;
+
+ for(tries = 0;; tries++, sleep(2*1000)){
+ if(tries == 10)
+ return -1;
+ if(write(1, buf, len) != len)
+ return -1;
+
+ alarm(30*1000);
+ n = read(0, &c, 1);
+ alarm(0);
+ if(debug) switch(c){
+ case Soh:
+ fprint(2, " Soh");
+ break;
+ case Eot:
+ fprint(2, " Eot");
+ break;
+ case Ack:
+ fprint(2, " Ack");
+ break;
+ case Nak:
+ fprint(2, " Nak");
+ break;
+ case Cancel:
+ fprint(2, "\nremote Cancel\n");
+ return -1;
+ default:
+ if(isprint(c))
+ fprint(2, "%c", c);
+ else
+ fprint(2, " \\0%o", c);
+ }
+ c = c & 0x7f;
+ if(n == 1 && c == Ack)
+ break;
+ }
+ return 0;
+}
+
+int
+notifyf(void *a, char *msg)
+{
+ USED(a);
+ if(strcmp(msg, "alarm") == 0)
+ return 1;
+ return 0;
+}