[net.micro.pc] XMODEM protocol for UN*X

coulter@hplabsc.UUCP (Michael Coulter) (05/20/86)

The following program compiles and works on an hp-ux system (more SYS V.2
than BSD).  I have used it to communicate with my Vectra (HP's AT clone)
running a communications program that speaks xmodem protocol.

-- Michael Coulter    ...hplabs!coulter

/*
 *  XM - a UNIX-to-CP/M file transfer shell
 *	by Richard Conn
 *
 *  XM is based on UC version 1.2, which in turn was based on UMODEM 3.5,
 *  originally written by Lauren Weinstein, and mutated by Richard Conn,
 *  Bennett Marks, Michael Rubenstein, Ben Goldfarb, David Hinnant, and
 *  Lauren Weinstein. XM differs from UC in that it offers only a basic
 *  UNIX-to-CP/M file transfer facility using the Christensen XMODEM protocol
 *  in checksum mode.
 */
#define versmaj 1	/* Major Version */
#define versmin 0	/* Minor Version */
/*  Basics  */
#define FALSE	0
#define TRUE	~FALSE
/*  ASCII Characters  */
#define SOH	001
#define STX	002
#define ETX	003
#define EOT	004
#define ENQ	005
#define ACK	006
#define LF	012
#define CR	015
#define NAK	025
#define SYN	026
#define CAN	030
#define CTRLZ	032
#define ESC	033
/*  XM Constants  */
#define TIMEOUT      -1 	     /* Timeout Flag */
#define ERRMAX	10		/* Max errors tolerated */
#define BLOCKSZ 128		/* Size of transmission block */
#define CREATE	0644		/* Mode for New Files */
#define DELAY	5		/* Basic delay for transmission */
/*  Library Utilities  */
#include	<stdio.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<sgtty.h>
#include	<signal.h>
#include	<ctype.h>
#include	<setjmp.h>
/*  Setjmp Environment */
jmp_buf rbenv;

main (argc, argv)
int argc;
char *argv[];
{
	int sendflg, recvflg, statflg;	/* major functions */
	char filetype;			/* tranfer type */
	FILE *fopen();			/* forward ref */
	int index;	/* index for arg parsing loop */
	char opt;	/* current option character */
	char *getenv(); /* getenv function defn */
	/* Print Banner */
	printf("XM Version %d.%d - Christensen XMODEM Protocol\n"
		,versmaj,versmin);
	printf("File Transfer Tool\n");
	/* Check for Help Request */
	if (argc == 1) {
		help();
		exit(0);
	}
	/* Init for Option Parsing */
	sendflg = FALSE;
	recvflg = FALSE;
	statflg = FALSE;
	/* Process Options */
	index = 0;
	while ((opt = argv[1][index++]) != '\0')
		switch (opt) {
		case '-' :              /* skip dash */
			break;
		case 'F' :
		case 'f' :
			statflg = TRUE; /* set file stat mode */
			break;
		case 'R' :
			recvflg = TRUE; /* set file recv mode */
			filetype = 'b';
			break;
		case 'r' :
			recvflg = TRUE; /* set file recv mode */
			filetype = 't';
			break;
		case 'S' :
			sendflg = TRUE; /* set file send mode */
			filetype = 'b';
			break;
		case 's' :
			sendflg = TRUE; /* set file send mode */
			filetype = 't';
			break;
		default :
			printf("Invalid Option %c\n", opt);
			break;
		}
	/* Select and Execute Major Mode */
	if (statflg) {		/* File Status Display */
		if (argc < 3) {
			printf("File Name NOT Given\n");
			exit(0);
		}
		fxstat(filetype,argv[2]);
		exit(0);
	}
	if (sendflg) {		/* Send File */
		if (argc < 3) {
			printf("File Name NOT Given\n");
			exit(0);
		}
		sendfile(filetype,argv[2]);
		exit(0);
	}
	if (recvflg) {		/* Receive File */
		if (argc < 3) {
			printf("File Name NOT Given\n");
			exit(0);
		}
		recv(filetype,argv[2]);
		exit(0);
	}
	printf("Major Mode NOT Selected\n");
	help();
	exit(0);
}
/* Print Help */
help()
{
	printf("Usage:  XM c filename\n");
	printf("\n");
	printf("where 'c' MUST be One of the Following Commands --\n");
	printf("\tR -- Receive Binary File\n");
	printf("\tr -- Receive Text File\n");
	printf("\tS -- Send Binary File\n");
	printf("\ts -- Send Text File\n");
	printf("\tf or F -- Show File Status\n");
	printf("\n");
	printf("Examples:\n");
	printf("\tXM S myfile <-- Send Binary File \"myfile\"\n");
	printf("\tXM s mytext <-- Send Text File \"mytext\"\n");
}
/* Send File */
sendfile(filetype,filename)
char filetype;
char *filename;
{
	FILE *fd, *fopen();
	int blocknum;			/* Current Block Number */
	int nlflg;			/* New Line for File Convert */
	int sending;			/* Xmit In-Progress Flag */
	int tries;			/* Attempt Count */
	int bufctr;			/* Counter for Buffer Build */
	int c;				/* Temp Char */
	int rcode;			/* Return Code */
	char buf[BLOCKSZ];		/* Buffer for Transfer */
	/* Print Banner */
	printf("XM Sending %s File: %s\n",
		(filetype == 't') ? "Text" : "Binary",
		filename);
	/* Open File for Input and Print Opening Messages */
	if ((fd = fopen(filename, "r")) == 0) {
		printf("Can`t Open File %s for Send\n", filename);
		return;
	}
	fxstat(filetype,filename);   /* Print File Status Info */
	printf("Ready to Send File\n");
	binary(TRUE,TRUE);	/* Open Binary Communications */
	/* Init Parameters */
	blocknum = 1;
	nlflg = FALSE;
	sending = TRUE;
	/* Synchronize */
	tries = 0;
	while (recvbyte(30) != NAK) {
		if (++tries > ERRMAX) {
			printf("Remote System Not Responding\n");
			return;
		}
	}
	/* Main Transmission Loop */
	while(sending) {
		/* Build Next Block into buf */
		for (bufctr = 0; bufctr < BLOCKSZ;) {
			if (nlflg) {	/* New Line */
				buf[bufctr++] = LF;	/* Store LF */
				nlflg = FALSE;
			}
			if (bufctr == BLOCKSZ) break;	/* Leave for Loop */
			c = getc(fd);	/* Get Next Byte from File */
			if (c == EOF) {
				sending = FALSE;	/* Done */
				if (!bufctr)	/* Avoid Extra Block */
					break;
				for(;bufctr < BLOCKSZ; bufctr++)
					buf[bufctr]= (filetype == 't')
						? CTRLZ : '\0' ;
				continue;	/* Exit for Loop */
			}
			if (c == LF && filetype == 't') {  /* NL? */
/*				buf[bufctr++] = CR;	/* Insert CR */
/*
   I commented out the above line, because the EOL on the Amiga is
   LF not CR/LF.  wpl
*/
				nlflg = TRUE;		/* New Line */
			}
			else buf[bufctr++] = c; 	/* Store Char */
		}
		/* Send Block */
		tries = 0;	/* Set Try Count */
		if (bufctr) do {
			putblock(filetype,buf,blocknum);     /* Send Block */
			rcode = recvbyte(10);	/* Get Response */
		}
		while (rcode != ACK && ++tries < ERRMAX);
		blocknum = (blocknum + 1) & 0xFF;
		if (tries == ERRMAX) sending = FALSE;  /* Error Abort */
	}
	/* Cleanup After Send */
	fclose(fd);	/* Close File */
	tries = 0;
	sendbyte(EOT);
	while (recvbyte(15) != ACK && ++tries < ERRMAX)
		sendbyte(EOT);
	binary(FALSE,TRUE);	/* Leave Binary Mode */
	sleep(3);
	printf("\n");
}
/* Send Buffer to Receiver */
putblock(filetype,buf,blocknum)
char filetype;
char *buf;
int blocknum;
{
	int i, j, checksum;
	sendbyte(SOH);		/* Send Start of Header */
	sendbyte(blocknum&0xff);     /* Send Block Number */
	sendbyte((-blocknum-1)&0xff);	     /* Send Block Complement */
	checksum = 0;
	for (i = 0; i < BLOCKSZ; i++) {
		sendbyte(*buf&0xff); /* Send Byte */
		checksum = (checksum + *buf++) & 0xff;
	}
	sendbyte(checksum&0xff);	     /* Checksum */
}
/* Receive File */
recv(filetype,filename)
char filetype;
char *filename;
{
	int fd; 		/* file descriptor */
	int blocknum;		/* next block to receive */
	int rbcnt;		/* total number of received blocks */
	int errorcnt;		/* number of errors on current block */
	int receiving;		/* continuation flag */
	int char1;		/* first char received in block */
	int rcode;		/* received block code */
	if (!access(filename,2)) {
		printf("File %s Exists -- Delete it? ", filename);
		if (!getyn()) {
			printf("Aborting\n");
			return;
		}
	}
	unlink(filename);	/* delete old file, if any */
	if ((fd = creat(filename, CREATE)) == -1) {	/* can't create */
		printf("Can't Create %s\n", filename);
		return;
	}
	/* We Have a GO */
	printf("XM Receiving %s File: %s\n",
		(filetype == 't') ? "Text" : "Binary",
		filename);
	printf("Ready to Receive\n");
	/* Init Counters et al */
	blocknum = 1;
	rbcnt = 0;
	errorcnt = 0;
	receiving = TRUE;
	/* Establish Binary Communications */
	binary(TRUE,TRUE);
	/* Synchronize with Sender */
	sendbyte(NAK);
	/* Receive Next Packet */
	while (receiving) {
		do {
			char1 = recvbyte(6);
		}
		while ((char1 != SOH) && (char1 != EOT) && (char1 != TIMEOUT));
		switch (char1) {
		case TIMEOUT :	     /* Timeout */
			if (++errorcnt == ERRMAX) {
				close(fd);	/* Close File */
				sleep(3);	/* Delay for Sender */
				binary(FALSE,TRUE);	/* Normal I/O */
				receiving = FALSE;
			}
			sendbyte(NAK);
			break;
		case EOT :	/* End of Transmission */
			sendbyte(ACK);
			while (recvbyte(3) != TIMEOUT);
			close(fd);	/* Close File */
			sleep(3);	/* Delay for Sender */
			binary(FALSE,TRUE);	/* Normal I/O */
			printf("\n");
			receiving = FALSE;
			break;
		case SOH :	/* New or Old Block */
			rcode = getblock(filetype,fd,blocknum);/* read block */
			switch (rcode) {
			case 0 :	/* OK */
				blocknum = ++blocknum & 0xff;
				rbcnt++;
			case 2 :	/* OK, but Duplicate Block */
				errorcnt = 0;
				sendbyte(ACK);
				break;
			case 1 :	/* Xmit Error, Non-Fatal */
				if (++errorcnt < ERRMAX) {
					sendbyte(NAK);
					break;
				}
			default :	/* Xmit Error, Fatal */
				close(fd);
				sendbyte(CAN);
				binary(FALSE,TRUE);
				while (recvbyte(3) != TIMEOUT);
				receiving = FALSE;
				break;
			}
			break;
		}
	}
}
/* Get Block from Sender */
getblock(filetype,fd,blocknum)
char filetype;
int fd, blocknum;
{
	int curblock, cmpblock;
	int recdone, checksum, inchecksum, byte, bufcnt, c;
	int startstx, endetx, endenq;
	int errflg, errchr;
	char buff[BLOCKSZ];
	int j;
	curblock = recvbyte(DELAY);
	if (curblock == TIMEOUT) return(1);
	cmpblock = recvbyte(DELAY);
	if (cmpblock == TIMEOUT) return(1);
	if ((curblock + cmpblock) != 0xff) {
		while (recvbyte(DELAY) != TIMEOUT);  /* Flush */
		return(1);
	}
	checksum = 0;		/* Init Checksum */
	byte = 0;		/* Init Buff Ptr */
	recdone = FALSE;	/* File Receive NOT Done */
	for (bufcnt=0; bufcnt<BLOCKSZ; bufcnt++) {
		c = recvbyte(DELAY);
		if (c == TIMEOUT) return(1);
		buff[byte] = c;
		checksum = (checksum + c) & 0xff;
		if (filetype != 't') {
			byte++; 	/* binary xfer, so advance */
			continue;
		}
		if (c == CR) continue;	/* skip CR */
		if (c == CTRLZ) {	/* done */
			recdone = TRUE;
			continue;
		}
		if (!recdone) byte++;		/* continue */
	}
	inchecksum = recvbyte(DELAY);
	if (inchecksum == TIMEOUT) return(1);
	errflg = FALSE;
	if (checksum != inchecksum) errflg = TRUE;
	if (errflg) return(1);
	if (curblock != blocknum) {
		if (curblock == ((blocknum+1)&0xff)) {
			return(99);
		}
		return(2);
	}
	if (write(fd,buff,byte) < 0) {
		return(99);
	}
	return(0);
}
/* File Status Display */
fxstat(filetype,filename)
char filetype;
char *filename;
{
	struct stat fsi;	/* file status info */
	if (stat (filename, &fsi) == -1) {	/* get file status info */
		printf("File %s Not Found\n", filename);
		return;
	}
	printf("File Size of %s is %ldK, %ld Blocks\n",
	filename,
	fsi.st_size%1024 ? (fsi.st_size/1024)+1 : fsi.st_size/1024,
	fsi.st_size%128 ? (fsi.st_size/128)+1 : fsi.st_size/128);
}
/*  SUPPORT ROUTINES  */
/* get yes or no response from user */
getyn()
	{
	int c;
	c = charx();	/* get char */
	if (c == 'y' || c == 'Y') {
		printf("Yes\n");
		return(TRUE);
	}
	else {
		printf("No\n");
		return(FALSE);
	}
}
/* get single char input */
charx()
{
	int binary();
	int c;
	binary(TRUE,FALSE);
	c = getchar();
	binary(FALSE,FALSE);
	return (c);
}
/* send byte to receiver */
sendbyte(data)
char data;
{
	write (1, &data, 1);	/* write the byte */
}
/* receive a byte from sender */
recvbyte(seconds)
unsigned seconds;
{
	char c;
	int alarmfunc();		/* forward declaration */
	signal(SIGALRM,alarmfunc);	/* catch alarms */
	if (setjmp(rbenv) < 0)		/* setup jump for alarm */
		return (TIMEOUT);
	alarm(seconds); 		/* set clock */
	read (0, &c, 1);	/* get char or timeout */
	alarm(0);			/* clear clock */
	return (c&0xff);
}
/* dummy alarm function */
alarmfunc()
{
	longjmp(rbenv,TIMEOUT); 	     /* jump to recvbyte */
	return;
}
/* set and clear binary mode */
binary(setflg,scope)
int setflg, scope;
{
	static struct sgttyb ttys, ttysold;
	if (setflg) {	/* set binary */
		if (gtty (0, &ttys) < 0) return(FALSE); /* failed */
		ttysold.sg_ispeed = ttys.sg_ispeed;	/* save old values */
		ttysold.sg_ospeed = ttys.sg_ospeed;
		ttysold.sg_erase = ttys.sg_erase;
		ttysold.sg_kill = ttys.sg_kill;
		ttysold.sg_flags = ttys.sg_flags;
		ttys.sg_flags |= RAW;		/* set for RAW Mode */
		ttys.sg_flags &= ~ECHO; 	/* set no ECHO */
		if (scope) {		/* cover all values? */
			ttys.sg_flags &= ~XTABS;	/* set no tab exp */
			ttys.sg_flags &= ~LCASE;	/* set no case xlate */
			ttys.sg_flags |= ANYP;		/* set any parity */
			ttys.sg_flags &= ~NL3;		/* no delays on nl */
			ttys.sg_flags &= ~TAB0; 	/* no tab delays */
			ttys.sg_flags &= ~TAB1;
			ttys.sg_flags &= ~CR3;		/* no CR delay */
			ttys.sg_flags &= ~FF1;		/* no FF delay */
			ttys.sg_flags &= ~BS1;		/* no BS delay */
		}
		if (stty (0, &ttys) < 0) return(FALSE); /* failed */
#ifdef	MESG
		if (scope) system("mesg n >/dev/null"); /* turn off messages */
#endif
		return(TRUE);
	}
	else {		/* clear binary */
		if (stty (0, &ttysold) < 0) return (FALSE);
#ifdef	MESG
		if (scope) system("mesg y >/dev/null"); /* turn on messages */
#endif
		return(TRUE);	/* OK */
	}
}