[comp.unix.xenix] Xmodem

ron@mlfarm.UUCP (Ronald Florence) (08/19/89)

There have been several inquiries recently about code to enable the
use of Xmodem with cu or other terminal programs.  The enclosed code
(written in 1986) can be compiled into either standalone programs
which enable a remote terminal to upload/download with Xmodem onto a
Unix/Xenix system, or if you have a source license, can be compiled
with cu to enable the use of Xmodem protocol within cu.

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	Readme
#	Makefile
#	xr.c
#	xmodem.c
# This archive created: Sat Aug 19 10:09:45 1989
# By:	Ronald Florence (Maple Lawn Farm, Stonington, CT)
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'Readme'" '(2008 characters)'
if test -f 'Readme'
then
	echo shar: "will not over-write existing file 'Readme'"
else
sed 's/^X//' << \SHAR_EOF > 'Readme'
XXmodem Under Xenix/Unix
Xcopyright 1986 Ronald Florence
X
XREMOTE XMODEM 
X
XWhen compiled with xr.c, xmodem.c provides standalone programs, xr
Xand xt (actually a link) which enable a remote computer logged onto
Xthe host (unix) machine to transmit to or receive from the unix machine
Xusing Xmodem protocol.  
X
XThe options are the same for both send and receive:
X
X	-c	force checksum (instead of auto-detect of crc/checksum)
X	-d fn	debug messages to fn
X	-t	text (CR-NL <-> NL conversions)
X
XTo transfer TO the unix host give the command
X
X	 xr [-ct] [-d errfile] filename
X
Xon the unix host, wait for xr to announce it is ready, then begin the
XXmodem send from the remote machine.  To transfer FROM the unix host,
Xgive the command
X
X	xt [-ct] [-d errfile] filename
X
Xon the unix host, wait for xt to announce it is ready, then begin the
XXmodem receive on the remote machine.  xt will adjust to the sync 
Xcharacter of the Xmodem receiver (match crc or checksum).
X
XBoth xt and xr ignore signals, and should be terminated by sending a
XCtrl-X <CAN>, which is the signal Xmodem programs normally send to
Xcancel a transfer.  If the remote machine screws up, the Ctrl-X can be
Xsent manually.
X
XThe byte counts of text files transferred from ms-dos machines with the
X"-t" option will be shorter on the unix machine because the <CR>s have
Xbeen dropped.
X
X
XXMODEM WITH OTHER TERMINAL PROGRAMS
X
XIf you have a unix/xenix source license, xmodem.c can be compiled with
Xcu.c to provide a version of cu that can receive and transmit to
Xbulletin boards, Compuserve, etc., using Xmodem (Modem 7) protocol in
Xeither crc or checksum mode.  
X
XThe xmodem.c module can also be compiled with other terminal programs,
Xsuch as kermit, to provide xmodem transfer capabilities.  One easy way 
Xto use the module with a program like cu is to kill the receive process 
Xin cu, condition the line to 
X
X	~(ISTRIP | IXON | IXOFF | PARENB) , CS8
X
Xthen fork to start xget() or xput().  When the child is finished,
Xdo a longjmp to restart the receive process.
SHAR_EOF
if test 2008 -ne "`wc -c < 'Readme'`"
then
	echo shar: "error transmitting 'Readme'" '(should have been 2008 characters)'
fi
fi
echo shar: "extracting 'Makefile'" '(423 characters)'
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
sed 's/^X//' << \SHAR_EOF > 'Makefile'
X# cu, xr/xt
X
XCFLAGS= -O -s
X
XLDFLAGS= -s
X
Xwhat:
X	@echo "make what?"
X
Xdebugcu: cu.c cuxmodem.o login.o
X	cc -Dddt $(CFLAGS) -s login.o cu.c cuxmodem.o -o cu
X
Xcu: cu.o cuxmodem.o login.o
X	cc login.o cu.o cuxmodem.o $(LDFLAGS) -o cu
X
Xxr: xr.o xmodem.o
X	cc xr.o xmodem.o -o xr
X	ln xr xt
X
Xcuxmodem.o: xmodem.c 
X	cc $(CFLAGS) -DCU -c xmodem.c 
X	mv xmodem.o cuxmodem.o
X
Xdial: dial24.c
X	cc $(CFLAGS) -o dial dial24.c
X
Xclean:
X	rm *.o
SHAR_EOF
if test 423 -ne "`wc -c < 'Makefile'`"
then
	echo shar: "error transmitting 'Makefile'" '(should have been 423 characters)'
fi
fi
echo shar: "extracting 'xr.c'" '(2252 characters)'
if test -f 'xr.c'
then
	echo shar: "will not over-write existing file 'xr.c'"
else
sed 's/^X//' << \SHAR_EOF > 'xr.c'
X/*
X * xr.c - remote xmodem functions for xenix/unix
X * copyright 1986 Ronald Florence
X *
X * usage: xr|xt [-tc] [-d errfile] file
X *	-t	text mode (CR-NL <-> NL)
X *      -c	force checksum 
X *
X * To avoid overwriting existing files, 
X * a received file with the same name 
X * as an existing file is stored as fname~.
X */
X
X#include <signal.h>
X#include <stdio.h>
X#include <fcntl.h>
X#include <sys/ioctl.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <termio.h>
X
X#define	DEBUG		01
X#define	LF		02
X#define CHECKSUM	04
X#define	BSIZE		128
X#define errx(m,f)	printf("%s: ", pname), \
X			printf(m, f), \
X			printf("\n"), \
X			exit(1)
X
XFILE	*errf;
X
Xstruct	termio	old,
X		new;
X
Xhangup()
X{
X	resetline();
X	exit(1);
X}
X
X
Xmain(ac, av)
Xint	ac;
Xchar	**av;
X{
X	char	*pname, 
X		trans = (av[0][strlen(av[0]) -1] == 't'),
X		*fname;
X	int	c, opts = 0; 
X	struct	stat	stbuf;
X	extern	int	optind;
X	extern	char	*optarg;
X	FILE	*fp;
X
X	pname = *av;
X	while ((c = getopt(ac, av, "ctd:?")) != EOF)
X		switch (c)  {
X		case 't' :
X			opts |= LF;
X			break;
X	        case 'c' :
X		  	opts |= CHECKSUM;
X			break;
X		case 'd' :
X			opts |= DEBUG;
X			if (!(errf = fopen(optarg, "w")))
X				errx("can't open %s", optarg);
X			setbuf(errf, NULL);
X			break;
X		case '?' :
X			printf("usage: %s [-tc] [-d errfile] file\n", pname);
X			exit(1);
X		}
X	if (ac == 1 || ac == optind) 
X		errx("need file name", NULL);
X	fname = av[optind];
X	if (trans && !(fp = fopen(fname, "r"))) 
X		errx("can't open %s", fname);
X	if (!trans) {
X		if (!access(fname, 0))
X			strcat(fname, "~");
X		if (!(fp = fopen(fname, "w")))
X			errx("can't write %s", fname);
X	}
X	printf("Ready to %s %s\n", (trans) ? "send" : "receive", fname);
X	if (trans) {
X		stat(fname, &stbuf);
X		printf("%d blocks (128 bytes/block)\n",stbuf.st_size/BSIZE+1);
X	}
X	printf("Ctrl-X to abort transfer\n");
X
X	signal(SIGINT, SIG_IGN);
X	signal(SIGQUIT, SIG_IGN);
X	signal(SIGHUP, hangup);
X
X        ioctl(1, TCGETA, &old);
X        ioctl(1, TCGETA, &new);
X	fflush(stdin);
X        new.c_iflag = IGNBRK|IGNPAR;
X        new.c_oflag = 0;
X        new.c_lflag = 0;
X        new.c_cc[4] = 1;
X        new.c_cflag &= ~PARENB;
X        new.c_cflag |= CS8;
X        ioctl(1, TCSETAW, &new);
X
X	(trans) ? xput(fp, opts) : xget(fp, opts);
X}
X
X
Xresetline()
X{
X	ioctl(1, TCSETA, &old);
X}
X
SHAR_EOF
if test 2252 -ne "`wc -c < 'xr.c'`"
then
	echo shar: "error transmitting 'xr.c'" '(should have been 2252 characters)'
fi
fi
echo shar: "extracting 'xmodem.c'" '(6588 characters)'
if test -f 'xmodem.c'
then
	echo shar: "will not over-write existing file 'xmodem.c'"
else
sed 's/^X//' << \SHAR_EOF > 'xmodem.c'
X/*
X * xmodem.c - xmodem functions for Xenix or Unix
X * copyright 1986, 1988 Ronald Florence
X * 
X * modified 3/88 to do crc by packet instead of by byte
X * modified 5/89 to make auto-match of CRC/checksum the default
X * modified 5/89 to filter out trailing null bytes from packets sent
X *   with -t option from brain-damaged xmodems.
X */
X
X#include <signal.h>
X#include <stdio.h>
X
X#ifdef CU
X
Xextern int	rlfd;				/* the open line in cu */
X#define	WFD	rlfd
X#define RFD	rlfd
X#define	errf	stderr
X
X#else
X
Xextern	FILE	*errf;				/* error file for remote */
X#define	WFD	1				/* stdout */
X#define RFD	0				/* stdin */
X
X#endif
X
X#define	BSIZE		128
X#define	DEBUG		01
X#define LF		02
X#define CHECKSUM	04
X#define NOREAD(x, c)	(rchar(x, &c) == -1)
X#define TX(c)		write(WFD, &c, 1)	
X#define CRC_TRIES	2
X#define ever		(;;)
X
Xstatic	char	soh = 0x01,
X		eot = 0x04,
X		ack = 0x06,
X		nak = 0x15,
X		can = 0x18,
X		crcinit = 'C';
X
Xstatic	int	debug,
X		crc;
X
Xunsigned short	do_crc();
X
Xint	kleenex(),
X	onalarm(); 
X
Xxget(fp, opts)
XFILE	*fp;
Xint	opts;
X{
X	char 	buf[BSIZE]; 
X	unsigned char	inch, b= 1, crchi;
X	int	iput = BSIZE, i, tries = 0; 
X
X	debug = (opts & DEBUG);
X	crc = (opts & CHECKSUM) ? 0 : 1;
X	signal(SIGALRM, onalarm);
X#ifdef CU
X	signal(SIGINT, kleenex);
X#else
X	sleep(10);
X#endif
X	(crc) ? TX(crcinit) : TX(nak);
X	for ever {
X		if NOREAD(6, inch) {
X			err("Timeout during SOH");
X			if (tries++ >= CRC_TRIES)
X			  crc = 0;
X			cksend(crc ? crcinit : nak);
X			continue;
X		}
X		if (inch == eot) 
X			break;
X		if (inch == can) {
X			err("CAN block %u", b);
X			kleenex(-1);
X		}
X		if (inch != soh) {
X			err("Bad SOH block %u: %#x", b, inch);
X			cksend(nak);
X			continue;
X		}
X		if NOREAD(2, inch) {
X			err("Timeout block %u during blocknum", b);
X			cksend(nak);
X			continue;
X		}
X		if (inch != b) {
X			err("Expected blocknum %u, got %u", b, inch);
X			cksend(nak);
X			continue;
X		}
X		if NOREAD(2, inch) {
X			err("Timeout block %u during ~blocknum", b);
X			cksend(nak);
X			continue;
X		}
X		if (inch != ~b) {
X			err("Expected ~blocknum %u, got %u", ~b, inch);
X			cksend(nak);
X			continue;
X		}
X			/* Read in the block of 128 bytes without
X			 * taking time for checksums or crc.
X			 */
X		for (i = 0; i < BSIZE; i++)
X			if NOREAD(2, buf[i]) 
X				break;
X		if (i < BSIZE) {
X			err("Timeout data recv, char #%d", i);
X			cksend(nak);
X			continue;
X		}
X                if (crc && NOREAD(2, crchi)) {
X			err("Timeout crc hibyte");
X			cksend(nak);
X			continue;
X		}
X                if NOREAD(2, inch) {
X			err("Timeout %s", (crc) ? "crc lobyte" : "checksum");
X			cksend(nak);
X			continue;
X		}
X			/* Now, when we have the whole packet,
X			 * do the checksum or crc.
X			 */
X		if (crc) {
X			unsigned short	crcsum;
X
X			crcsum = do_crc(buf);
X			if (inch + (crchi << 8) != crcsum) {
X				err("Expected crc %u, got %u", 
X					crcsum, inch + (crchi << 8));
X				cksend(nak);
X				continue;
X			}
X		}
X		else {
X			unsigned char	cksum;
X
X			for (cksum = 0, i = 0; i < BSIZE; i++) 
X				cksum += buf[i];
X			cksum %= 256;
X			if (cksum != inch) {
X				err("Expected checksum %u, got %u", cksum,inch);
X				cksend(nak);
X				continue;
X			}
X		}
X		TX(ack);
X#ifdef CU
X		putc('.', stderr);
X#endif
X		if (opts & LF) 
X			for (i=0, iput=0; i < BSIZE; i++) {
X				if (buf[i] == 0x1a)	/* old ms-dos eof */
X					break;
X				if (buf[i] != '\r' && buf[i] != '\0')
X					buf[iput++] = buf[i];
X			}
X		fwrite(buf, iput, 1, fp);
X		b++;
X		b %= 256;
X	}
X	TX(ack);
X	kleenex(0);
X}
X
X
Xxput(fp, opts)
XFILE	*fp;
Xint	opts;
X{
X	char	buf[BSIZE];
X	unsigned char	b = 1, cb, inch;
X	int 	cread, i;
X
X
X#ifdef CU
X	signal(SIGINT, kleenex);
X#endif
X	signal(SIGALRM, onalarm);
X	debug = (opts & DEBUG);
X	rchar(60, &cb);
X	if (cb == crcinit)
X		crc = 1;
X	else if (cb == nak)
X		crc = 0;
X	else  {
X		err("No startup %s", (crc) ? "'C'" : "NAK");
X		kleenex(-1);
X	}
X	cread = fillbuf(fp, buf, (opts & LF));
X	while (cread) {
X		for (i = cread; i < BSIZE; i++) 
X			buf[i] = 0;
X		TX(soh);
X		TX(b);
X		cb = ~b;
X		TX(cb);
X		write(WFD, buf, BSIZE);
X		if (crc) {
X			unsigned short	crcsum;
X			unsigned char	crclo, crchi;
X
X			crcsum = do_crc(buf);
X			crclo = (crcsum & 0xff);
X			crchi = (crcsum >> 8);
X			TX(crchi);
X			TX(crclo);
X		}
X		else {
X			unsigned char	cksum;
X
X			for (cksum = 0, i = 0; i < BSIZE; i++) 
X				cksum += buf[i];
X			cksum %= 256;
X			TX(cksum);
X		}
X		if NOREAD(15, inch) {
X			err("Timeout after block %u", b);
X			continue;
X		}
X		if (inch == can) {
X			err("CAN after block %u", b);
X			kleenex(-1);
X		}
X		if (inch != ack) {
X			err("Non-ACK after block %u: %#x", b, inch);
X			continue;
X		}
X#ifdef CU
X		putc('.', stderr);
X#else
X		if (debug) 
X			fprintf(errf, "Validated block %u\n", b);
X#endif
X		cread = fillbuf(fp, buf, (opts & LF));
X		b++;
X		b %= 256;
X	}
X	for ever {
X		TX(eot);
X		if NOREAD(15, inch) {
X			err("Timeout during EOT");
X			continue;
X		}
X		if (inch == can) {
X			err("CAN during EOT");
X			kleenex(-1);
X		}
X		if (inch != ack) {
X			err("Non-ACK during EOT: %#x", inch);
X			continue;
X		}
X		break;
X	}
X	kleenex(0);
X}
X
X
Xfillbuf(fp, buf, lf)
XFILE	*fp;
Xchar	*buf;
Xint	lf;
X{
X	int	i = 0, c; 
X	static	int	cr_held; 
X
X	if (cr_held) {
X		buf[i] = '\n';
X		i++;
X		cr_held--;
X	}
X	for (; i < BSIZE; i++) {
X		if ((c = getc(fp)) == EOF)
X			break;
X		if (c == '\n' && lf) {
X			buf[i] = '\r';
X			if (i == 127) {
X				cr_held++;
X				return BSIZE;
X			}
X			buf[i+1] = '\n';
X			i++;
X		}
X		else
X			buf[i] = c;
X	}
X	return i;
X}
X
X
Xunsigned short do_crc(b)
Xchar	*b;
X{
X	unsigned short	shift, flag, crcsum;
X	char	c;
X	int	i;
X
X	for (i = 0, crcsum = 0; i < (BSIZE + 2); i++) {
X		c = (i < BSIZE) ? b[i] : 0;
X		for (shift = 0x80; shift; shift >>= 1)  {
X			flag = (crcsum & 0x8000);
X			crcsum <<= 1;
X			crcsum |= ((shift & c) ? 1 : 0);
X			if (flag)
X				crcsum ^= 0x1021;
X		}
X	}
X	return (crcsum);
X}
X
X
X/* The timeout in rchar() works by deliberately 
X * interrupting the read() system call.  We know 
X * errno=EINTR, so there is no reason for a 
X * perror() autopsy. 
X */
Xrchar(timeout, cp)
Xunsigned	timeout;
Xchar	*cp;
X{
X	int	c; 
X
X	alarm(timeout);
X	if ((c = read(RFD, cp, 1)) == -1)
X		return -1;
X	alarm(0);
X	return c;
X}
X
X
Xonalarm()
X{
X	signal(SIGALRM, onalarm);
X}
X
X
Xkleenex(sig)
Xint sig;
X{
X#ifdef CU
X	if (sig > 0)
X		cksend(can);
X	else 
X		fprintf(stderr, "\r\nFile transfer %s.",
X			(sig) ? "cancelled" : "complete");
X	fprintf(stderr, "\r\n");
X#else
X	printf("File transfer %s.\r\n", (sig) ? "cancelled" : "complete");
X	resetline();
X#endif
X	exit(sig);
X}
X
X
Xcksend(ch)
Xchar	ch;
X{
X	int	j;
X	char	cp;
X
X	do {
X		j = rchar(2, &cp);
X	} while (j != -1);
X	TX(ch);
X}
X
X
X/* VARARGS1 */
Xerr(s, i, j)
Xchar	*s;
Xint	i, j;
X{
X	if (debug) {
X		fprintf(errf, s, i, j);
X#ifndef CU
X		fprintf(errf, "\n");
X	}
X#else
X		fprintf(errf, "\r\n");
X	}
X	else
X		putc('%', stderr);
X#endif
X}
SHAR_EOF
if test 6588 -ne "`wc -c < 'xmodem.c'`"
then
	echo shar: "error transmitting 'xmodem.c'" '(should have been 6588 characters)'
fi
fi
exit 0
#	End of shell archive

-- 

Ronald Florence			...{hsi!aati,rayssd}!mlfarm!ron