Subject: v07i093: Full-featured XMODEM, Part02/02 Newsgroups: mod.sources Approved: mirror!rs Submitted by: seismo!noao!grandi (Steve Grandi) Mod.sources: Volume 7, Issue 93 Archive-name: xmodem/Part02 [ I'll be at USENIX, if you want to come look for me with bats and clubs. --r$ ] : This is a shar archive. Extract with sh, not csh. echo x - batch.c sed -e 's/^X//' > batch.c << '!Funky!Stuff!' X/* X * Various routines for batch tranfer X */ X X#include "xmodem.h" X X/* make sure filename sent or received in YMODEM batch is canonical */ X/* Turn Unix '/' into CP/M ':' and translate to all lower case */ X Xunixify (name) Xchar *name; X { X char *ptr; X for (ptr=name; *ptr; ++ptr) X { X if (*ptr == '/') X *ptr = ':'; X if (isupper (*ptr)) X *ptr |= 040; X } X } X Xcpmify (name) Xchar *name; X { X char *ptr; X for (ptr=name; *ptr; ++ptr) X { X if (*ptr == ':') X *ptr = '/'; X if (isupper (*ptr)) X *ptr |= 040; X } X } X X X/* convert a CP/M file name received in a MODEM7 batch transfer X * into a unix file name mapping '/' into ':', converting to all X * upper case and adding dot in proper place. X * Use "filename" to hold name. X * Code stolen from D. Thompson's (IRTF) xmodem.c X */ X Xchar *cpm_unix (string) Xunsigned char *string; X{ X register int i; X unsigned char *iptr, temp; X register char *optr; X X if (*string == '\0') X error("Null file name in MODEM7 batch receive", TRUE); X X for (iptr=string; (temp = *iptr) ; ) { X temp &= 0177; /* strips bit 7 */ X if (isupper(temp)) X temp |= 040; /* set bit 5 for lower case */ X if (temp == '/') X temp=':'; /* map / into : */ X *iptr++ = temp; X } X X /* put in main part of name */ X iptr=string; X optr=filename; X for (i=0; i<8; i++) { X if (*iptr != ' ') X *optr++ = *iptr++; X } X X /* add dot */ X *optr++ = '.'; X X /* put in extension */ X iptr = &string[8]; X for (i=0; i<3; i++) { X if (*iptr != ' ') X *optr++ = *iptr++; X } X X *optr++ = '\000'; X return (filename); X} X X/* Send 11 character CP/M filename for MODEM7 batch transmission X * Returns -1 for a protocol error; 0 if successful X * NOTE: we tromp a little on the argument string! X * code stolen from D. Thompson's (IRTF) xmodem.c X */ X Xsend_name(name) Xchar *name; X{ X register int cksum; X register char *ptr; X X xmdebug("send_name"); X X /* append cp/m EOF */ X name[NAMSIZ] = CTRLZ; X name[NAMSIZ+1] = '\000'; X X /* create checksum */ X ptr = name; X cksum = 0; X while (*ptr) X cksum += *ptr++; X cksum &= 0x00FF; X X /* send filename */ X X sendbyte(ACK); X ptr = name; X sendbyte(*ptr++); X X while (*ptr) { X Xchecknak: switch (readbyte(6)) { X X case CAN: { X if (readbyte(3) == CAN) X error("Program Canceled by User",TRUE); X else X goto checknak; X } X X case ACK: break; X X case TIMEOUT: { X logit("Timeout while sending filename\n"); X sendbyte(BAD_NAME); X return (-1); X } X X default: { X logit("Error while sending filename\n"); X sendbyte(BAD_NAME); X return (-1); X } X } X X sendbyte (*ptr++); X } X X /* Check checksum returned by other side against my value */ X if (readbyte(10) != cksum) { X logit("Bad checksum while sending filename\n"); X sendbyte(BAD_NAME); X return (-1); X } X X sendbyte(ACK); X return (0); X} X X/* Convert Unix filename to 11 character CP/M file name (8 char name, X * 3 char extension, dot in between is not included). X * map ':' into '/'; Use filename to hold name. X * code stolen from D. Thompson's (IRTF) xmodem.c X */ X Xchar *unix_cpm(string) Xchar *string; X{ X register char *iptr, *optr, temp; X int i; X X char *rindex(); X char *strcpy(); X X /* blank 11 character name */ X (void) strcpy (filename," "); X X /* strip off any path name */ X if ((iptr = rindex(string,'/'))) X iptr++; X else X iptr=string; X X /* skip leading '.'s */ X while (*iptr == '.') X iptr++; X X /* copy main part of name */ X optr = filename; X i = 8; X while ((i--) && (*iptr) && (*iptr != '.')) X *optr++ = *iptr++; X X /* advance to unix extension, or end of unix name */ X while ((*iptr != '.') && (*iptr)) X iptr++; X X /* skip over the '.' */ X while (*iptr == '.') X iptr++; X X /* copy extension */ X optr = &filename[8]; X i=3; X while ((i--) && (*iptr) && (*iptr != '.')) X *optr++ = *iptr++; X X filename[NAMSIZ] = '\000'; X X /* Fuss with name */ X for (iptr=filename; (temp = *iptr) ;) { X temp &= 0177; /* strip bit 7 (parity bit) */ X if (islower(temp)) X temp &= ~040; /* make upper case */ X if (temp == ':') X temp ='/'; /* map ':' into '/' */ X *iptr++ = temp; X } X X if (DEBUG) X fprintf (LOGFP, "DEBUG: File %s sent as %s\n", string, filename); X X return(filename); X} !Funky!Stuff! echo x - misc.c sed -e 's/^X//' > misc.c << '!Funky!Stuff!' X#include "xmodem.h" X X/* Print Help Message */ Xhelp() X { X printf("\nUsage: \n\txmodem "); X printf("-[rb!rt!sb!st][options] filename\n"); X printf("\nMajor Commands --"); X printf("\n\trb <-- Receive Binary"); X printf("\n\trt <-- Receive Text"); X printf("\n\tsb <-- Send Binary"); X printf("\n\tst <-- Send Text"); X printf("\nOptions --"); X printf("\n\tY <-- Use YMODEM Batch Mode on transmit"); X printf("\n\tB <-- Use MODEM7 Batch Mode on transmit"); X printf("\n\tK <-- Use 1K packets on transmit"); X printf("\n\tc <-- Select CRC mode on receive"); X printf("\n\td <-- Delete xmodem.log file before starting"); X printf("\n\tl <-- (ell) Turn OFF Log File Entries"); X printf("\n\tx <-- Include copious debugging information in log file"); X printf("\n"); X } X X/* get type of transmission requested (text or binary) */ Xgettype(ichar) Xchar ichar; X { X if (ichar == 't') return(ichar); X if (ichar == 'b') return(ichar); X error("Invalid Send/Receive Parameter - not t or b", FALSE); X return(0); X } X X/* print error message and exit; if mode == TRUE, restore normal tty modes */ Xerror(msg, mode) Xchar *msg; Xint mode; X { X if (mode) X restoremodes(TRUE); /* put back normal tty modes */ X printf("\r\n%s\n", msg); X if ((LOGFLAG || DEBUG) && (LOGFP != NULL)) X { X fprintf(LOGFP, "XMODEM Fatal Error: %s\n", msg); X fclose(LOGFP); X } X exit(-1); X } X X X/* Construct a proper (i.e. pretty) sector count for messages */ X Xchar X*sectdisp(recvsectcnt, bufsize, plus1) Xlong recvsectcnt; Xint bufsize, plus1; X { X static char string[20]; X if (plus1) X recvsectcnt += (bufsize == 128) ? 1 : 8; X if (bufsize == 128 || recvsectcnt == 0) X sprintf (string, "%d", recvsectcnt); X else X sprintf (string, "%d-%d", recvsectcnt-7, recvsectcnt); X return(string); X } X X/* type out debugging info */ Xxmdebug(str) Xchar *str; X { X if (DEBUG && (LOGFP != NULL)) X fprintf(LOGFP,"DEBUG: '%s'\n",str); X } X X/* print elapsed time and rate of transfer in logfile */ X Xint quant[] = { 60, 60, 24}; Xchar *sep[] = { "second", "minute", "hour" }; X Xprtime (numsect, seconds) Xlong numsect; Xtime_t seconds; X X{ X register int i; X register int Seconds; X int nums[3]; X int rate = 0; X X if (!LOGFLAG) X return(0); X X Seconds = (int)seconds; X X if (Seconds != 0) X rate = 128 * numsect/Seconds; X X for (i=0; i<3; i++) { X nums[i] = (Seconds % quant[i]); X Seconds /= quant[i]; X } X X fprintf (LOGFP, "%ld CP/M Sectors Transfered in ", numsect); X while (--i >= 0) X if (nums[i]) X fprintf (LOGFP, "%d %s%c ", nums[i], sep[i], X nums[i] == 1 ? '\0' : 's'); X fprintf (LOGFP, "\n"); X X if (rate != 0) X fprintf (LOGFP, "Transfer Rate = %d Characters per Second\n", rate); X X return(0); X} X X/* Print elapsed time estimate */ X Xprojtime (numsect, fd) Xlong numsect; XFILE *fd; X { X register int i; X register int seconds; X int nums[3]; X X if (numsect == 0) X return (0); X X/* constant below should really be 1280; reduced to 90% to account for time lost in overhead */ X X seconds = 1422 * numsect / ttyspeed + 1; X X for (i=0; i<3; i++) { X nums[i] = (seconds % quant[i]); X seconds /= quant[i]; X } X X fprintf (fd, "Estimated transmission time "); X while (--i >= 0) X if (nums[i]) X fprintf (fd, "%d %s%c ", nums[i], sep[i], X nums[i] == 1 ? '\0' : 's'); X fprintf (fd, "\n"); X return (0); X } !Funky!Stuff! echo x - receive.c sed -e 's/^X//' > receive.c << '!Funky!Stuff!' X#include "xmodem.h" X X/** receive a file **/ X X/* returns TRUE if in the midst of a batch transfer */ X/* returns FALSE if no more files are coming */ X X/* This routine is one HUGE do-while loop with far to many indented levels. X * I chose this route to facilitate error processing and to avoid GOTOs. X * Given the troubles I've had keeping the nested IF statements straight, X * I was probably mistaken... X */ X Xrfile(name) Xchar *name; X { X X char *sectdisp(); X char *cpm_unix(); X char *strcpy(); X time_t time(); X X int fd, /* file descriptor for created file */ X checksum, /* packet checksum */ X firstchar, /* first character of a packet */ X sectnum, /* number of last received packet (modulo 128) */ X sectcurr, /* second byte of packet--should be packet number (mod 128) */ X sectcomp, /* third byte of packet--should be complement of sectcurr */ X tmode, /* text mode if true, binary mode if false */ X errors, /* running count of errors (reset when 1st packet starts */ X errorflag, /* set true when packet (or first char of putative packet) is invalid */ X fatalerror, /* set within main "read-packet" Do-While when bad error found */ X inchecksum, /* incoming checksum or CRC */ X expsect, /* expected number of sectors (YMODEM batch) */ X bufsize; /* packet size (128 or 1024) */ X long recvsectcnt; /* running sector count (128 byte sectors) */ X int bufctr; /* number of real chars in read packet */ X unsigned char *nameptr; /* ptr in filename for MODEM7 protocol */ X time_t start; /* starting time of transfer */ X int openflag = FALSE; /* is file open for writing? */ X X logit("----\nXMODEM File Receive Function\n"); X if (CRCMODE) X logit("CRC mode requested\n"); X X BATCH = FALSE; /* don't know if really are in batch mode ! */ X fatalerror = FALSE; X sectnum = errors = recvsectcnt = 0; X bufsize = 128; X X tmode = (XMITTYPE == 't') ? TRUE : FALSE; X X sleep(1); /* wait a second for other side to get ready */ X if (CRCMODE) /* start up transfer */ X sendbyte(CRCCHR); X else X sendbyte(NAK); X X X do /* start of MAIN Do-While loop to read packets */ X { X errorflag = FALSE; X do /* start by reading first byte in packet */ X { X firstchar = readbyte(6); X } X while ((firstchar != SOH) X && (firstchar != STX) X && (firstchar != EOT) X && (firstchar != ACK || recvsectcnt > 0) X && (firstchar != TIMEOUT) X && (firstchar != CAN)); X X if (firstchar == EOT) /* check for REAL EOT */ X { X if (readbyte(1) != TIMEOUT) X { X firstchar = TIMEOUT; X errorflag = TRUE; X logit ("EOT followed by characters; ignored\n"); X } X } X X if (firstchar == TIMEOUT) /* timeout? */ X { X logitarg("Timeout on Sector %s\n", sectdisp(recvsectcnt,bufsize,1)); X errorflag = TRUE; X } X X if (firstchar == CAN) /* bailing out? */ X { X if ((readbyte(3) & 0x7f) == CAN) X error("Reception canceled at user's request",TRUE); X else X { X errorflag = TRUE; X logit("Received single CAN character\n"); X } X } X X if (firstchar == ACK) /* MODEM7 batch? */ X { X int i,c; X X logit("MODEM7 Batch Protocol\n"); X nameptr = buff; X checksum = 0; X X for (i=0; i send.c << '!Funky!Stuff!' X/** send a file **/ X X/* X * Operation of this routine depends on on MDM7BAT and YMDMBAT flags. X * X * If "name" is NULL; close out the BATCH send. X */ X X#include "xmodem.h" X Xsfile(name) Xchar *name; X { X X char *sectdisp(); X time_t time(); X char *strcpy(); X char *unix_cpm(); X X extern unsigned short crctab[1< NAKMAX) X error("Remote System Not Responding", TRUE); X X if ((firstchar & 0x7f) == CAN) X if (readbyte(3) == CAN) X error("Send cancelled at user's request",TRUE); X X if (firstchar == CRCCHR) X { X CRCMODE = TRUE; X if (!closeout) X logit("CRC mode requested\n"); X } X } X while (firstchar != NAK && firstchar != CRCCHR); X X if (closeout && MDM7BAT) /* close out MODEM7 batch */ X { X sendbyte(ACK); sendbyte (EOT); X readbyte(2); /* flush junk */ X return; X } X X if (MDM7BAT) /* send MODEM7 file name and resync for data packets */ X { X if (send_name(unix_cpm(name)) == -1) /* should do better job here!! */ X error("MODEM7-batch filename transfer botch", TRUE); X X firstchar = readbyte(5); X if (firstchar != CRCCHR && firstchar != NAK) /* Should do some better error handling!!! */ X error("MODEM7 protocol botch, NAK/C expected", TRUE); X X CRCMODE = FALSE; X if (firstchar == CRCCHR) X { X CRCMODE = TRUE; X logit("CRC mode requested for MODEM7 batch transfer\n"); X } X } X X sectnum = 1; X X if (YMDMBAT) /* Fudge for YMODEM transfer (to send name packet) */ X { X sectnum = 0; X bufsize = 128; X } X X attempts = errors = sentsect = extrachar = 0; X start = time((time_t *) 0); X X do /* outer packet building/sending loop; loop till whole file is sent */ X { X X if (closeout && YMDMBAT && sectnum == 1) /* close out YMODEM */ X return; X X if (YMDMBAT && sectnum == 1) /* get set to send YMODEM data packets */ X { X bufsize = LONGPACK ? 1024 : 128; X do X { X while (((firstchar=readbyte(3)) != CRCCHR) && (firstchar != CAN)) X if (++attempts > NAKMAX) X error("YMODEM protocol botch, C expected", TRUE); X if ((firstchar&0x7f) == CAN) X if (readbyte(3) == CAN) X error("Send cancelled at User's request", TRUE); X } X while (firstchar != CRCCHR); X attempts = 0; X } X X if (extrachar >= 128) /* update expected sector count */ X { X extrachar = 0; X expsect++; X } X X if ((bufsize == 1024) && (errors > KSWMAX)) X { X logit("Reducing packet size to 128 due to excessive errors\n"); X bufsize = 128; X } X X if ((bufsize == 1024) && ((expsect - sentsect) < 8)) X { X logit("Reducing packet size to 128 for tail end of file\n"); X bufsize = 128; X } X X if (sectnum > 0) /* data packet */ X { X for (bufctr=0; bufctr < bufsize;) X { X if (nlflag) X { X buff[bufctr++] = LF; /* leftover newline */ X nlflag = FALSE; X } X if (getbyte(fd, &c) == EOF) X { X sendfin = TRUE; /* this is the last sector */ X if (!bufctr) /* if EOF on sector boundary */ X break; /* avoid sending extra sector */ X buff[bufctr++] = CTRLZ; /* Control-Z for CP/M EOF (even do it for binary file) */ X continue; X } X X if (tmode && c == LF) /* text mode & Unix newline? */ X { X extrachar++; X buff[bufctr++] = CR; /* insert carriage return */ X if (bufctr < bufsize) X buff[bufctr++] = LF; /* insert LF */ X else X nlflag = TRUE; /* insert on next sector */ X } X else X buff[bufctr++] = c; /* copy the char without change */ X } X X if (!bufctr) /* if EOF on sector boundary */ X break; /* avoid sending empty sector */ X } X X else /* YMODEM filename packet */ X { X for (bufctr=0; bufctr> 8) & 0xff); X } X } X X bbufcnt = 0; /* start building block to be sent */ X blockbuf[bbufcnt++] = (bufsize == 1024) ? STX : SOH; /* start of packet char */ X blockbuf[bbufcnt++] = sectnum; /* current sector # */ X blockbuf[bbufcnt++] = ~sectnum; /* and its complement */ X X checksum = 0; /* initialize checksum */ X for (bufctr=0; bufctr < bufsize; bufctr++) X { X blockbuf[bbufcnt++] = buff[bufctr]; X X if (CRCMODE) X checksum = (checksum<>(W-B)) ^ buff[bufctr]]; X X else X checksum = ((checksum+buff[bufctr]) & 0xff); X } X X if (CRCMODE) /* put in CRC */ X { X checksum &= 0xffff; X blockbuf[bbufcnt++] = ((checksum >> 8) & 0xff); X blockbuf[bbufcnt++] = (checksum & 0xff); X } X else /* put in checksum */ X blockbuf[bbufcnt++] = checksum; X X attempts = 0; X X do /* inner packet loop */ X { X X writebuf(blockbuf, bbufcnt); /* write the block */ X X if (DEBUG) X fprintf (LOGFP, "DEBUG: %d byte Packet %02xh (%02xh) sent, checksum %02xh %02xh\n", X bbufcnt, blockbuf[1]&0xff, blockbuf[2]&0xff, blockbuf[bufsize+3]&0xff, blockbuf[bufsize+4]&0xff); X X attempts++; X sendresp = readbyte(10); /* get response from remote */ X X if (sendresp != ACK) X { X errors++; X X if ((sendresp & 0x7f) == CAN) X if ((readbyte(3) & 0x7f) == CAN) X error("Send cancelled at user's request\n",TRUE); X X if (sendresp == TIMEOUT) X { X logitarg("Timeout on sector %s\n",sectdisp(sentsect,bufsize,1)); X } X else X { X logitarg("Non-ACK on sector %s\n",sectdisp(sentsect,bufsize,1)); X } X } X } X while((sendresp != ACK) && (attempts < RETRYMAX) && (errors < ERRORMAX)); /* close of inner loop */ X X sectnum++; /* increment to next sector number */ X sentsect += (bufsize == 128) ? 1 : 8; X } X while (!sendfin && (attempts < RETRYMAX) && ( errors < ERRORMAX)); /* end of outer loop */ X X if (attempts >= RETRYMAX) X { X sendbyte(CAN); sendbyte(CAN); sendbyte(CAN); X error("Remote System Not Responding", TRUE); X } X X if (attempts > ERRORMAX) X { X sendbyte(CAN); sendbyte(CAN); sendbyte(CAN); X error ("Too many errors in transmission", TRUE); X } X X attempts = 0; X sendbyte(EOT); /* send 1st EOT to close down transfer */ X X while ((readbyte(15) != ACK) && (attempts++ < RETRYMAX)) /* wait for ACK of EOT */ X { X logit("EOT not ACKed\n"); X sendbyte(EOT); X } X X if (attempts >= RETRYMAX) X error("Remote System Not Responding on Completion", TRUE); X X close(fd); X X logit("Send Complete\n"); X prtime(sentsect, time((time_t *) 0) - start); X } !Funky!Stuff! exit