/* * @(#)gio.c 1.4 87/08/14 - gio.c Copyright 1987 by John Gilmore. * * Copying and use of this program are controlled by the terms of the Free * Software Foundation's GNU Emacs General Public License. * * Derived from: * i[$]uuslave.c 1.7 08/12/85 14:04:20 * which came from the ACGNJ BBS system at +1 201 753 9758. Original * author unknown. * * Ported to Amiga by William Loftus * Amiga Changes Copyright 1988 by William Loftus. All rights reserved. */ #ifndef lint char gio_version[] = "@(#)gio.c Amiga-0.10 BETA"; #endif #include "includes.h" /* All the system header files */ /* * Declarations of gnuucp's system-dependent routines, which * are in the file sysdep.c (a link to one of many system dependent * implementations). */ #include "uucp.h" /* Returns a temp filename, in a static buffer, to use while * receiving the file whose name is passed as argument. */ char * temp_filename(); /* Returns a filename, in a static buffer, which should be used * on the local system to access the file named in a uucp control * file (the argument). E.g. it might turn D.foobarX1234 into * foobar\X1234.D on MSDOS, or to D./D.foobarX1234 on Unix. */ char * munge_filename(); /* * Basement level I/O routines * * xwrite() writes a character string to the serial port * xgetc() returns a character from the serial port, or an EOF for timeout. * sigint() restores the state of the serial port on exit. */ int xwrite(); /* filedesc, buffer, count */ int xgetc(); /* No arg */ void sigint(); /* No arg */ void openline(); /* ttyname */ /* * UUCP "g" procotol definitions */ #define MAGIC 0125252 /* checksum is subtracted from this */ /* * What is sent from one machine to the other is a control byte and * sometimes a data block following. * A packet is a just this, with a frame around the control byte and the * data, if any, after the frame. The frame is 6 bytes long, and looks like: * DLE K C0 C1 C X * where: * DLE is a literal ASCII DLE (Data Link Escape) character * K is binary, 9 for a control packet, or the packet size log 2, minus 4 * e.g. K = 2 means 64 byte packet since (K+4) is 6 or 64. * 2 2 * C0 and C1 are low, high order checksums for the entire data section, or * are low, high order of (MAGIC minus the control byte). * C is the control byte for the message. * X is the xor of K, C0, C1, and C. * If a packet does not satisfy all of the above checks, it is invalid. */ #define DLE 0x10 /* Start of packet indicator */ #define LENBYTE 1 /* Byte offset from DLE to length */ #define CBYTE 4 /* Byte offset from DLE to control */ #define FRAMEBYTE 5 /* Byte offset from DLE to end of frame */ #define KCONTROL 9 /* K value for control packets */ /* * A control byte is split into three bit fields: type, x, and y. * TT XXX YYY * Here are the types: */ #define CONTROL 0 /* Control message */ #define ALTCHN 1 /* Alternate channel message, unused in UUCP */ #define LONGDATA 2 /* Long data block -- full size spec'd by K */ #define SHORTDATA 3 /* Short data block -- first byte or two is count. Full K-size packet is sent, even though the useful data is shorter. */ char *tt_pr[] = {"CONTROL", "ALTCHN", "LONGDATA", "SHORTDATA"}; /* If TT == CONTROL (also K will == KCONTROL) then the x field is: and the Y field means: */ #define CLOSE 1 /* End of communication */ #define RJ 2 /* Reject packet last good packet # seen */ #define SRJ 3 /* Selective reject seq # of bad packet, resend SRJ is not used by UUCP. */ #define RR 4 /* Receiver Ready last good packet # seen */ #define INITC 5 /* Init phase C window size to hand sender */ #define INITB 6 /* Init phase B max data segment size (K val) to hand sender */ #define INITA 7 /* Init phase A window size to hand sender */ char *ctrl_pr[] = {"*ZERO*", "CLOSE", "RJ", "SRJ", "RR", "INITC", "INITB", "INITA"}; /* * If TT == LONGDATA or SHORTDATA then x field is the sequence # of this packet * and y field is the last good packet # seen. * * In both data and RJ/RR packets, the "last good packet # seen" starts off * as zero. */ #define MAX_PACKET 4096 /*#define SLOP 10 /* Frame header, ctrl, slop */ #define MAX_FLAGS 40 #ifndef LOG #define logit(one, two) /* Nothing */ #endif extern int errno; unsigned char msgi[MAX_PACKET+SLOP], /* Incoming packet */ msgo[MAX_PACKET+SLOP]; /* Outgoing packet */ int firstslave, /* First packet of slave's session */ msgsize, /* Size of data part of msg */ tt, xxx, yyy, /* Fields from control byte */ rseq, /* Last good packet # we received */ his_rseq, /* Last good packet # HE received */ wseq; /* Next packet # we will send */ int last_op; /* Last data op: OP_READ or OP_WRITE */ #define OP_READ 0 #define OP_WRITE 1 int reject; /* Packet # to reject or NOREJECT */ #define NOREJECT -1 /* Segments are encoded as (log2 length) - 3, except in INITB packet */ int wndsiz = 1; /* Ask for window of 1 messages flying */ int segsiz = 2; /* Ask for 64 byte messages */ int sendseg = 2; /* Size segments other guy wants to see */ int sendwin = 1; /* Size window other guy wants to see */ int sendbytes; /* sendseg, in bytes */ int segbytes[10] = { /* K value (encoded segment size) */ -1, /* 0 */ 32, /* 1 */ 64, /* 2 */ 128, /* 3 */ 256, /* 4 */ 512, /* 5 */ 1024, /* 6 */ 2048, /* 7 */ 4096, /* 8 */ 0, /* 9 = KCONTROL */ }; /* * Low level output routines. These send packets without checking * whether they got properly received. * * writeframe(): * * Finish off an outgoing frame in msgo and queue it up to be written * to the serial port. * * This routine is called to send each and every packet. */ int writeframe(cksm) int cksm; { msgo[0] = (unsigned char)DLE; msgo[2] = (unsigned char)cksm; msgo[3] = (unsigned char)(cksm >> 8); msgo[5] = msgo[1] ^ msgo[2] ^ msgo[3] ^ msgo[4]; if (debug > 8) { int t, x, y, i, maxlen; printf("T "); maxlen = segbytes[msgo[LENBYTE]] + 6; for (i = 0; i < maxlen; i++) printf("%02x ",msgo[i] & 0xFF); printf("\n"); t = msgo[CBYTE] >> 6; x = (msgo[CBYTE] >> 3) & 7; y = msgo[CBYTE] & 7; if (t == CONTROL) printf("Sent: CONTROL %s %d\n", ctrl_pr[x], y); else printf("Sent: %s %d %d\n", tt_pr[t], x, y); } /* * In our window=1 implementation, we just queue the packet * up for transmission here (by leaving it in msgo[]). It * will be written next time we go through inpkt(). */ last_op = OP_WRITE; /* Remember to avoid overwriting the packet */ return 0; /* Never aborts */ } /* Send an ack */ int ackmsg() { msgo[1] = (unsigned char)KCONTROL; if (reject != NOREJECT) msgo[4] = (unsigned char)((CONTROL << 6) | (RJ << 3) | reject); else msgo[4] = (unsigned char)((CONTROL << 6) | (RR << 3) | rseq); reject = NOREJECT; return writeframe((int)(MAGIC - msgo[4])); } /* Send a control message other than an ack */ int ctlmsg(byte) char byte; { msgo[1] = (unsigned char)KCONTROL; msgo[4] = (unsigned char)((CONTROL << 6) | byte); return writeframe((int)(MAGIC - msgo[4])); } /* * Medium level output routine. This sends a short or long data packet * and figures out when to retransmit and/or insert acknowledgements as * needed. */ sendpacket(s, n, sorl) char *s; int n; int sorl; /* SHORTDATA or LONGDATA */ { int cksm, offset, difflen; if (last_op == OP_WRITE) { /* Better get the first one sent and ack'd first */ /* FIXME, this will change for window > 1 */ if (inpkt()) return 1; } bzero((char *)(msgo+6), sendbytes); msgo[1] = (unsigned char)sendseg; msgo[4] = (unsigned char)((sorl << 6) + (wseq << 3) + rseq); switch(sorl) { case LONGDATA: if (n > sendbytes) abort(); offset = 6; break; case SHORTDATA: difflen = sendbytes - n; if (difflen < 1) abort(); offset = 7; if (difflen <= 127) { msgo[6] = (unsigned char)difflen; /* One byte count */ } else { msgo[6] = (unsigned char)(128 | difflen); /* low byte, with 0x80 on */ msgo[7] = (unsigned char)(difflen >> 7); /* High byte */ offset = 8; } } bcopy(s, msgo+offset, n); /* Move the data */ cksm = MAGIC - (chksum(&msgo[6], sendbytes) ^ (0377 & msgo[4])); wseq = (wseq + 1) & 7; /* Bump sent pkt sequence # */ return writeframe(cksm); } /* * Medium level input routine. * * Write a packet to the serial port, then read a packet from the serial port. * Return 1 if other side went away, 0 if good packet received. * * With window size of 1, we send a packet and then receive one. * FIXME, when we implement a larger window size, this routine will become * more complicated and callers will not be able to depend on msgo[] * being sent and acknowledged when it returns. */ int inpkt() { int data,count,need; int i; unsigned short pktsum, oursum; int status; /* * Next vars are for re-queueing received chars to rescan them * for a valid packet after an error. */ int queued = -1; /* <0: off, 0: just finished, >0: # chars pending */ unsigned char *qp = '\0'; unsigned char qbuf[sizeof msgi]; /* This can be static if 4K on the stack is too much */ # define bad(str) { if (debug > 0 ) printf str; goto oops; } if (firstslave) { firstslave = 0; goto again; } xmit: i = segbytes[msgo[LENBYTE]] + 6; status = xwrite(msgo,i); if (status != i) { #ifdef DEBUG printf("xmit %d bytes failed, status %d, errno %d", i, status, errno); #endif return 1; /* Write failed */ } again: count = 0; #ifdef DEBUG if (debug > 8) printf("R "); #endif while (1) { if (queued >= 0) { /* * Process some stuff from a string. * If we just finished the last char queued, and * we are still scanning for a DLE, re-xmit our * last packet before we continue reading. * On the other hand, if we have a valid packet * header accumulating, just keep reading the serial * port. */ if (--queued < 0) { if (count == 0) { #ifdef DEBUG printf("End queue. Re-xmit.\n"); #endif goto xmit; /* No packet comin' in */ } else { #ifdef DEBUG printf("End queue. Keep reading\n"); #endif goto readser; /* Seems to be sumpin' */ } } data = (int)(*qp++) & 0xFF; /* Just grab from queue */ } else { readser: data = xgetc(); if (data == EOF) break; } if (debug > 8) { printf("%02x%c ",data & 0xFF, isprint(data)? data: ' '); } switch (count) { case 0: /* Look for DLE */ if (data == DLE) msgi[count++] = (unsigned char)DLE; break; case LENBYTE: /* Check length byte */ if (data > KCONTROL || data == 0) bad(("packet size")); if (segbytes[data] > MAX_PACKET) { bad(("packet too long for buffer")); oops: printf(" bad in above packet\n"); /* FIXME, decode packet header here, if enough of it has come in. */ /* See if any DLEs in the bad packet */ /* Skip 0, we know that's a DLE */ for (i = 1; i < count; i++) { if (msgi[i] == (unsigned char)DLE) { /* Reprocess from the DLE. * if queued, back up the q. * if not, make one. */ if (queued >= 0) { queued += count - i; qp -= count - i; DEBUG(6,"Backup and rescan queue\n", 0); } else { bcopy(msgi+i, qbuf, count - i); qp = qbuf; queued = count - i; DEBUG(6,"Queue and rescan input\n", 0); } goto again; } } if (queued >= 0) { DEBUG(6, "Continue scan\n", 0); goto again; } else { DEBUG(6, "Re-xmit previous packet\n",0); goto xmit; /* Xmit then rcv */ } } msgi[count++] = (unsigned char)data; /* Save it */ msgsize = segbytes[data]; /* Save Packet size */ need = 6 + msgsize; break; case CBYTE: /* Break up control byte as well as storing it */ tt = ((unsigned char)data >> 6) & 3; xxx = ((unsigned char)data >> 3) & 7; yyy = (unsigned char)data & 7; msgi[count++] = (unsigned char)data; /* save it */ /* Now check it a bit */ switch (tt) { /* Switch on msg type */ case CONTROL: /* Control msg must have KCONTROL size */ if (msgsize != 0) bad(("K versus Control")); /* We don't implement SRJ, nor does Unix */ switch (xxx) { case SRJ: bad(("SRJ received")); case RJ: case RR: if (yyy != (7 & (wseq - 1))) bad(("didn't ack our pkt 2")); } break; case ALTCHN: printf(" K=%02x X=%02x Y=%02x C=%02x EZ=%02x\n", msgi[1],msgi[2],msgi[3],msgi[4], msgi[1]^msgi[2]^msgi[3]^msgi[4]); printf("Real Z=%02x\n",xgetc()); bad(("ALTCHN received")); /* Unsupported */ case SHORTDATA: case LONGDATA: if (msgsize == 0) bad (("KCONTROL with data")); if (((xxx - rseq) & 7) > wndsiz) { /* Atari ST cpp has problems with * macro args broken across lines? * That's why funny indent here */ bad (("data out of window, xxx=%d rseq=%d", xxx, rseq)); } /* FIXME, below enforces window size == 1 */ /* Note that this is also how we guarantee that msgo has been received OK by the time we exit inpkt() too. Don't change it unless you know what you are doing. */ if (yyy != (7 & (wseq - 1))) bad(("didn't ack our pkt 1")); break; } break; case FRAMEBYTE: /* See whole frame, check it a bit. */ msgi[count++] = (unsigned char)data; if ((unsigned char)data != (msgi[1] ^ msgi[2] ^ msgi[3] ^ msgi[4])) bad(("frame checksum")); pktsum = msgi[2] + (msgi[3] << 8); if (tt == CONTROL) { /* Check checksums for control packets */ oursum = MAGIC - msgi[4]; if (pktsum != oursum) bad(("control checksum")); /* * We have a full control packet. * Update received seq number for the ones * that carry one. */ switch (xxx) { case RJ: case RR: if (((wseq - yyy) & 7) > sendwin) { bad (("RJ/RR out of window, yyy=%d wseq=%d", yyy, wseq)); } his_rseq = yyy; } goto done; } else { /* * Received frame of data packet. * * Now that the checksum has been verified, * we can believe the acknowledgement (if * any) in it. */ if (((wseq - yyy) & 7) > sendwin) { bad (("data ack out of window, yyy=%d wseq=%d", yyy, wseq)); } his_rseq = yyy; } break; default: msgi[count++] = (unsigned char)data; if (count >= need) { /* We have received a full data packet */ oursum = MAGIC - (chksum(&msgi[6], sendbytes) ^ (0377 & msgi[4])); if (pktsum != oursum) { /* Send a reject on this pkt */ reject = xxx - 1; bad(("\ndata checksum in packet %x, ours=%x", pktsum, oursum)); } /* FIXME, this may change for window>1 */ if (xxx != (rseq+1)%8 ) { bad(("Not next packet xxx=%d rseq=%d", xxx, rseq)); } rseq = xxx; /* We saw this pkt OK */ done: #ifdef DEBUG if (debug > 2) { printf("\n"); if (tt == CONTROL) printf("Rcvd: CONTROL %s %d\n", ctrl_pr[xxx], yyy); else printf("Rcvd: %s %d %d\n", tt_pr[tt], xxx, yyy); } #endif last_op = OP_READ; return(0); } break; } } DEBUG(6, " EOF\n", 0); return(1); } int chksum(s,n) unsigned char *s; int n; { short sum; unsigned short t; short x; sum = -1; x = 0; do { if (sum < 0) { sum <<= 1; sum++; } else sum <<= 1; t = sum; sum += *s++ & 0377; x += sum ^ n; if ((unsigned short)sum <= t) sum ^= x; } while (--n > 0); return((int)sum); } /* * Medium level packet driver input routine. * * Read a data packet from the other side. If called twice in succession, * we send an ack of the previous packet. Otherwise we tend to piggyback * the acks on data packets. * * Result is 0 if we got a data packet, 1 if we got some other kind, or * a hangup timeout. */ int indata() { while (1) { if (last_op == OP_READ) { ackmsg(); /* Send an ack */ } if (inpkt()) return 1; switch (tt) { case ALTCHN: return 1; /* Unsupported - yet */ case LONGDATA: case SHORTDATA: /* * We got a data packet. That's what we want, * so return. */ return 0; /* We are done. */ case CONTROL: switch (xxx) { default: return 1; /* Bad packet type */ case RJ: /* Reject prev pkt */ case RR: /* OK but no data */ break; /* Ack and try again */ } } } return 1; } /* * Open a conversation in the g protocol. Medium level routine. * Returns 0 for success, 1 for failure. */ int gturnon(mastermode) int mastermode; { int tries = 0; int expect = 0; static int which[] = {INITA, INITB, INITC}; /* initialize protocol globals, e.g. packet sequence numbers */ rseq = 0; /* Last good packet # we have seen from him */ wseq = 1; /* Next packet # we will send */ his_rseq = 0; /* Last good Packet # he has seen from us */ reject = NOREJECT; /* Don't reject first packet */ firstslave = mastermode? 0: 1; /* About to do first slave packet? */ if (mastermode) goto master_start; while (++tries <= 5) { /* Receive an initialization packet and handle it */ if (inpkt() == 0 && tt == CONTROL && xxx == which[expect]) { /* Remember we've seen it, grab value */ switch (xxx) { case INITA: case INITC: sendwin = yyy; break; case INITB: /* * Get preferred packet size for other guy, * but don't overrun our buffer space. * The encoded segment size is off-by-1 from * the one used in the K field in each packet. */ do { sendseg = yyy+1; sendbytes = segbytes[sendseg]; } while (sendbytes > MAX_PACKET && --yyy); break; } } else { expect = -1; } /* FIXME -- check this, does it belong here?? */ if (mastermode) if (++expect > 2) return 0; master_start: /* * Transmit an initialization packet. * * Send whichever packet we expected, if we got it. * If we didn't, send INITA. */ switch (expect) { case -1: case 0: ctlmsg((INITA << 3) | wndsiz); break; case 1: ctlmsg((INITB << 3) | (segsiz - 1)); break; case 2: ctlmsg((INITC << 3) | wndsiz); break; } if (!mastermode) if (++expect > 2) return 0; /* We are done */ } return 1; /* Failure */ } /* * Turn off conversation in the G protocol. Medium level routine. */ int gturnoff() { /* In windowed protocol, we have to check if prev one's been ack'd */ if (last_op == OP_WRITE) { if (inpkt()) return 1; } do { ctlmsg(CLOSE << 3); if (inpkt()) return 1; } while (tt != CONTROL && xxx != CLOSE); return 0; } /* * Read a message from the other side. * * Messages are always contained in LONGDATA packets. If a message is * longer than a single packet, only the last packet will contain a null * character. Keep catenating them until you see the null. * * Return SUCCESS or FAIL. If the received message is longer than our * buffer, we eat the whole message, but return FAIL once it ends. */ int grdmsg(str) char *str; /* Buffer to put it in, sized MAXMSGLEN */ { unsigned msglen; unsigned totlen = 0; unsigned oldlen; str[0] = '\0'; /* No command yet */ do { if (indata() || tt != LONGDATA) return FAIL; msgi[6+msgsize] = '\0'; /* Null terminate packet */ msglen = strlen(msgi+6); oldlen = totlen; totlen += msglen; if (totlen < MAXMSGLEN) { strcat(str+oldlen,&msgi[6]); /* Tack on to command */ } } while (msglen == msgsize); /* Loop if no null in pkt */ return (totlen < MAXMSGLEN)? SUCCESS: FAIL; } /* * Write a message to the other side. * * We write the first (or only) packet from our local bufr[], the * rest comes straight out of the caller's string. */ int gwrmsg(type, str) char type; char *str; { char bufr[MAX_PACKET]; char *ptr; int thislen, totlen; bufr[0] = type; totlen = strlen(str) + 1; if (totlen > MAXMSGLEN) abort(); strncpy(&bufr[1], str, sendbytes-1); ptr = bufr; for (;;) { thislen = totlen; if (thislen > sendbytes) thislen = sendbytes; if (sendpacket(ptr, thislen, LONGDATA)) return FAIL; totlen -= sendbytes; if (totlen < 0) break; if (ptr == bufr) ptr = str + (sendbytes - 1); else ptr += sendbytes; } return SUCCESS; } /* * Write a file to the other side. */ int gwrdata(file) FILE *file; /* File to be sent */ { char dskbuf[MAX_PACKET]; /* Disk I/O buffer */ int count; do { count = fread(dskbuf, sizeof (char), sendbytes, file); if (count < 0) return FAIL; /* FIXME, should send EOF */ if (sendpacket(dskbuf, count, (count == sendbytes)? LONGDATA: SHORTDATA)) return FAIL; } while (count); return SUCCESS; } /* * Read a file from the other side. */ int grddata(file) FILE *file; /* stdio file ptr of file to read into */ { int offset; int status; int error = 0; do { /* Read a packet, handle the data in it */ if (indata()) return FAIL; switch (tt) { case LONGDATA: /* FIXME, check this write */ offset = 6; goto writeit; case SHORTDATA: if (msgi[6] & 0x80) { msgsize -= (msgi[7] << 7) | (127&msgi[6]); offset = 8; } else { msgsize -= msgi[6]; offset = 7; } writeit: /* FIXME: * Consider skipping further writing if error != 0 */ if (msgsize != 0) { status = fwrite(&msgi[offset], sizeof (char), msgsize, file); if (status != msgsize) error++; } break; } } while (msgsize != 0); return error? FAIL: SUCCESS; }