Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP
Path: utzoo!mnetor!seismo!brl-adm!rutgers!sri-unix!hplabs!hao!noao!grandi
From: grandi@noao.UUCP (Steve Grandi)
Newsgroups: net.sources
Subject: XMODEM/YMODEM program for 4.2/4.3BSD (part 2/2)
Message-ID: <540@noao.UUCP>
Date: Wed, 7-Jan-87 10:48:48 EST
Article-I.D.: noao.540
Posted: Wed Jan  7 10:48:48 1987
Date-Received: Thu, 8-Jan-87 18:43:26 EST
Organization: National Optical Astronomy Observatories, Tucson, AZ
Lines: 1102


: 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
-- 
Steve Grandi, National Optical Astronomy Observatories, Tucson, AZ, 602-325-9228
UUCP: {decvax,hao,ihnp4,seismo}!noao!grandi           Internet: grandi@noao.arpa