[net.sources] TMODEM: UNIX-compatible Christensen MODEM

ian@utcsstat.UUCP (08/15/83)

# /bin/sh
echo x - tmodem.README
cat > tmodem.README << "//SYSIN DD SYSOUT=BD"
TMODEM -- a UNIX-style implementation of Christensen's MODEM

The recent comment on USENET about huge assembly-language MODEM
programs prompts this posting:

This MODEM program was written by Andrew Scott Beals at MIT.
I liked it, so I'm posting it to the net (with Beals' permission).
Called TMODEM, it is in the UNIX/software tools style of programming, that is,
it doesn't log on to remote systems, have 365 different options,
or scratch your nose for you. It has just two modes (send and receive)
and concentrates on sending and receiving files. Normal usage would
be to sign on from a CP/M system (using the TO option of CP/M 
MODEM, and login to UNIX, then invoke TMODEM to transmit
or receive a file. Note that `transmit' and `receive' are locally
defined, so the CP/M side should be instructed to transmit when the TMODEM
side is in receive, or vice versa.

There are two source files, the one distributed by Beals and one I cleaned up
a little. They both work; mine has one bug fix (clearing to the
end of the buffer at end of file on receive). Use whichever you like,
for whatever you like. This software is not to be sold; it may be
freely distributed provided the author is credited.

program written by Andrew Scott Beals (SJOBRG.ANDY%MIT-OZ@MIT-MC)
distributed to USENET by Ian Darwin (utcsstat!ian).
//SYSIN DD SYSOUT=BD
echo x - tmodem.1
cat > tmodem.1 << "//SYSIN DD SYSOUT=BD"
.TH TMODEM 1
.SH NAME
tmodem \- Christensen modem protocol (MODEM7, MODEM9, XMODEM, etc) under UNIX.
.SH SYNOPSIS
.I tmodem
[
.B \-s
] / [
.B \-t
] file ...
.SH DESCRIPTION
.I Tmodem
implements Ward Christensen's MODEM protocol.
This protocol is normally associated with CP/M systems,
and is provided under UNIX to provide for bidirectional
file transfer between CP/M and UNIX.
.PP
It is assumed that the user knows how to use MODEM on his/her
CP/M system.
For use of
.I tmodem
you must have a version of the Christensen MODEM program
on your CP/M system.
To transfer a file, you must have one program in
.I send
mode and one in 
.I receive 
mode.
You use `s' for MODEM and `-s' for 
.I tmodem
to send a file;
`r' and `-r' to receive a file.
.SH EXAMPLE
This example shows use of MODEM and 
.I tmodem
to load a file from the CP/M micro to the UNIX system.
.nf
A>modem to.300		; dial in to UNIX, login
login: joeuser		; in terminal mode
password: censored
Last login Wed Aug  3 11:30:43 EDT 1983 on tty513
You have mail.
There is news.
$ tmodem -r sample.data	; start UNIX copy of tmodem to receive file
^E			; switch CP/M MODEM to command mode
COMMAND? so.300 sample.dat ; start cp/m copy of MODEM to send file
SENDING # 01		; message appears for each sector.
----			; and so on.
COMMAND? to.300		; reconnect to UNIX
<nl>			; send null line
Transmission complete	; final message from tmodem.
$			; can now log off UNIX or do other work.
.SH SEE ALSO
cu(1), uucp(1), stty(1), cat(1), tounix(1), tocpm(1).
.SH AUTHOR
Andrew Scott Beals, M.I.T.
.sp
This document mostly by Ian Darwin, Toronto.
.SH BUGS
CP/M is a trademark of Digital Research, Inc.
.PP
I should write in more intelligent time-out, but I don't think it's
really necessary.
//SYSIN DD SYSOUT=BD
echo x - tmodem.c
cat > tmodem.c << "//SYSIN DD SYSOUT=BD"
/*
 * a version of Ward Christensen's MODEM program for
 * UNIX v7, 4.1bsd
 *
 * by Andrew Scott Beals
 * sjobrg.andy%mit-oz@mit-mc
 * last update->4 june 1982
 * code reorganized, cleaned up - ian darwin, utcsstat!ian, 83-05-02
 *
 */

#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <sgtty.h>

#define uchar	unsigned char

#define CPMEOF	26		/* control/z */
#define OVERWRITE 1		/* define for normal overwrite */
#define MAXERRORS 10		/* max number of times to retry one block */
#define OVRWRITIM 10		/* time to pause (none if OVERWRITE defined) */
#define SECSIZE	128		/* cpm sector, transmission block */
#define SENTIMOUT 80		/* timeout time in send */
#define	SLEEP	30		/* timeout time in recv */

/* Protocol characters used */

#define	SOH	1	/* Start Of Header */
#define	EOT	4	/* End Of Transmission */
#define	ACK	6	/* ACKnowlege */
#define	NAK	0x15	/* Negative AcKnowlege */

short ttyhold;
struct sgttyb ttymode;

main(argc, argv)
int argc;
char **argv;
{

	if (argc != 3) 
		usage();
	gtty(0, &ttymode);
	ttyhold = ttymode.sg_flags;
	ttymode.sg_flags |= RAW;
	ttymode.sg_flags &= ~ECHO;

	if (argv[1][0] != '-')
		usage();
	switch (argv[1][1]){
	case 'r': 
		rec(argv[2]); 
		break;
	case 's': 
		sen(argv[2]); 
		break;
	default: 
		usage();
	}
	die(0);
}

/********** send a file to the remote **********/
sen(tfile)
char	*tfile;
{

	register uchar checksum, index, blocknumber, errorcount;
	uchar sector[SECSIZE];
	int foo, nbytes, timeout();

	stty(0, &ttymode);
	if ((foo = open(tfile, 0)) == -1) {
		fprintf(stderr, "can't open %s for send!\r\n", tfile);
		die(1);
	}
	fprintf(stderr, "file open, ready to send\r\n");
	fflush(stderr);
	fflush(stdout);
	errorcount = 0;
	blocknumber = 1;
	signal(SIGALRM, timeout);
	alarm(SENTIMOUT);

	while ((getchar() != NAK) && (errorcount < MAXERRORS))
		++errorcount;
	alarm(0);
#ifdef DEBUG
	fprintf(stderr, "transmission beginning\r\n");
	fflush(stderr);
#endif
	if (errorcount == MAXERRORS) {
		error();
	}
	while (nbytes=read(foo, sector, sizeof sector)) {
		if (nbytes<SECSIZE)
			sector[nbytes]=CPMEOF;
		errorcount = 0;
		while (errorcount < MAXERRORS) {
#ifdef DEBUG
			fprintf(stderr, "{%d} ", blocknumber);
			fflush(stderr);
#endif
			putchar(SOH);	/* here is our header */
			putchar(blocknumber);	/* the block number */
			putchar(~blocknumber);	/* & its complement */
			checksum = 0;
			for (index = 0; index < SECSIZE; index++) {
				putchar(sector[index]);
				checksum += sector[index];
			}
			putchar(checksum);	/* tell our checksum */
			fflush(stdout);
			if (getchar() != ACK)
				++errorcount;
			else
				break;
		}
		if (errorcount == MAXERRORS)
			error();
		++blocknumber;
	}
	index = 1;
	while (index) {
		putchar(EOT);
		fflush(stdout);
		index = getchar() == ACK;
	}
	fprintf(stderr, "Transmission complete.\r\n");
	fflush(stderr);
}

/********** receive a file **********/
rec(tfile)
char	*tfile;
{
	register uchar checksum, index, blocknumber, errorcount, character;
	uchar sector[SECSIZE];
	int foo;

#ifndef OVERWRITE
	if ((foo = open(tfile, 0)) != -1) {
		close(foo);
		fprintf(stderr, "%s exists; you have %d seconds to abort\r\n",
		OVRWRITIM,tfile);
		fflush(stderr);
		sleep(OVRWRITIM);
		fprintf(stderr, "Too late!\r\n");
		fflush(stderr);
	}
#endif

	stty(0, &ttymode);

	if ((foo = creat(tfile, 0666)) == -1) {
		perror(tfile);
		die(1);
	}
	printf("you have %d seconds...",SLEEP);
	fflush(stdout);
	sleep(SLEEP);	/* wait for the user to get his act together */
#ifdef DEBUG
	fprintf(stderr, "Starting...\r\n");
	fflush(stderr);
#endif
	putchar(NAK);
	fflush(stdout);
	errorcount = 0;
	blocknumber = 1;
	while ((character = getchar()) != EOT) {
		register uchar not_ch;
		if (character != SOH) {
#ifdef DEBUG
			fprintf(stderr, "Not SOH\r\n");
			fflush(stderr);
#endif
			if (++errorcount < MAXERRORS)
				goto nakit;
			else
				error();
		}
		character = getchar();
		not_ch = ~getchar();
#ifdef DEBUG
		fprintf(stderr, "[%d] ", character);
		fflush(stderr);
#endif
		if (character != not_ch) {
#ifdef DEBUG
			fprintf(stderr, "Blockcounts not ~\r\n");
			fflush(stderr);
#endif
			++errorcount;
			goto nakit;
		}
		if (character != blocknumber) {
#ifdef DEBUG
			fprintf(stderr, "Wrong blocknumber\r\n");
			fflush(stderr);
#endif
			++errorcount;
			goto nakit;
		}
		checksum = 0;
		for (index = 0; index < SECSIZE; index++) {
			sector[index] = getchar();
			checksum += sector[index];
		}
		if (checksum != getchar()) {
#ifdef DEBUG
			fprintf(stderr, "Bad checksum\r\n");
			fflush(stderr);
#endif
			errorcount++;
			goto nakit;
		}
		putchar(ACK);
		fflush(stdout);
		blocknumber++;
		write(foo, sector, sizeof sector);
		if (!errorcount)
			continue;
nakit:
		putchar(NAK);
		fflush(stdout);
	}
	close(foo);

	putchar(ACK);	/* tell the other end we accepted his EOT 	*/
	putchar(ACK);
	putchar(ACK);
	fflush(stdout);

	fprintf(stderr, "Completed.\r\n");
	fflush(stderr);
}


/* give message that we timed out, and then die */
timeout()
{
	fprintf(stderr, "Timed out waiting for NAK from remote system\r\n");
	die(1);
}

/* give user minimal usage message */
usage()
{
	fprintf(stderr,"usage: modem option file\n");
	fprintf(stderr,"       option is `-s' for send, or `-r' for recieve''\n");
	exit(1);
}

error()
{
	fprintf(stderr, "too many errors...aborting\r\n");
	die(1);
}

die(how)
register int how;
{
	ttymode.sg_flags = ttyhold;
	stty(0, &ttymode);
	exit(how);
}
//SYSIN DD SYSOUT=BD
echo x - tmodem.dist.c
cat > tmodem.dist.c << "//SYSIN DD SYSOUT=BD"
/*
 * a version of Ward Christensen's MODEM program for
 * UNIX v7, 4.1bsd
 *
 * by Andrew Scott Beals
 * sjobrg.andy%mit-oz@mit-mc
 * last update->4 june 1982
 *
 */

#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <sgtty.h>

#define uchar	unsigned char

#define	SLEEP	30

	/* Protocol characters used */

#define	SOH	1	/* Start Of Header */
#define	EOT	4	/* End Of Transmission */
#define	ACK	6	/* ACKnowlege */
#define	NAK	0x15	/* Negative AcKnowlege */

short		ttyhold;
struct sgttyb	ttymode;

main(argc,argv)
int	argc;
char	**argv;
{
	register uchar	checksum,index,blocknumber,errorcount,
			character;
	uchar		sector[128];
	int		foo,timeout();

	if(argc!=3)
	{
usage:		fprintf(stderr,"usage:\tmodem -<option> <file>\n");
		fprintf(stderr,"\twhere <option> is ``s'' for send, or ``r'' for recieve''\n");
		exit(1);
	}
	gtty(0,&ttymode);
	ttyhold=ttymode.sg_flags;
	ttymode.sg_flags|=RAW;
	ttymode.sg_flags&=~ECHO;

	if(argv[1][0]!='-')goto usage;
	if(argv[1][1]=='r')goto rec;
	if(argv[1][1]!='s')goto usage;

	/* send a file to the remote */

	stty(0,&ttymode);
	if((foo=open(argv[2],0))==-1)
	{
		fprintf(stderr,"can't open %s for send!\7\n",argv[2]);
		die(1);
	}
	fprintf(stderr,"file open, ready to send\r\n");
	fflush(stderr);
	fflush(stdout);
	errorcount=0;
	blocknumber=1;
	signal(SIGALRM,timeout);
	alarm(80);

	while((getchar()!=NAK)&&(errorcount<10))++errorcount;
	alarm(0);
#ifdef DEBUG
	fprintf(stderr,"transmission beginning\r\n");
	fflush(stderr);
#endif
	if(errorcount==10)
	{
error:		fprintf(stderr,"too many errors...aborting\7\7\7\r\n");
		die(1);
	}
	while(read(foo,sector,sizeof sector))
	{
		errorcount=0;
		while(errorcount<10)
		{
#ifdef DEBUG
			fprintf(stderr,"{%d} ",blocknumber);
			fflush(stderr);
#endif
			putchar(SOH); /* here is our header */
			putchar(blocknumber);	/* the block number */
			putchar(~blocknumber);	/* & its complement */
			checksum=0;
			for(index=0;index<128;index++)
			{
				putchar(sector[index]);
				checksum+=sector[index];
			}
			putchar(checksum); /* tell our checksum */
			fflush(stdout);
			if(getchar()!=ACK)++errorcount;
			else break;
		}
		if(errorcount==10)goto error;
		++blocknumber;
	}
	index=1;
	while(index)
	{
		putchar(EOT);
		fflush(stdout);
		index=getchar()==ACK;
	}
	fprintf(stderr,"Transmission complete.\r\n");
	fflush(stderr);
	die(0);

rec:	/* recieve a file */

	if((foo=open(argv[2],0))!=-1) {
		close(foo);
		fprintf(stderr,"%s exists; you have 10 seconds to abort\r\n",argv[2]);
		fflush(stderr);
		sleep(10);
		fprintf(stderr,"Too late!\r\n");
		fflush(stderr);
	}

	stty(0,&ttymode);

	if((foo=creat(argv[2],0666))==-1) {
		perror(argv[2]);
		die(1);
	}
	printf("you have 30 seconds...");
	fflush(stdout);
	sleep(SLEEP);	/* wait for the user to get his act together */
#ifdef DEBUG
	fprintf(stderr,"Starting...\r\n");
	fflush(stderr);
#endif
	putchar(NAK);
	fflush(stdout);
	errorcount=0;
	blocknumber=1;
	while((character=getchar())!=EOT)
	{
		register uchar not_ch;
		if(character!=SOH)
		{
#ifdef DEBUG
			fprintf(stderr,"Not SOH\r\n");
			fflush(stderr);
#endif
			if(++errorcount<10)goto nakit;
			else goto error;
		}
		character=getchar();
		not_ch=~getchar();
#ifdef DEBUG
		fprintf(stderr,"[%d] ",character);
		fflush(stderr);
#endif
		if(character!=not_ch)
		{
#ifdef DEBUG
			fprintf(stderr,"Blockcounts not ~\r\n");
			fflush(stderr);
#endif
			++errorcount;
			goto nakit;
		}
		if(character!=blocknumber)
		{
#ifdef DEBUG
			fprintf(stderr,"Wrong blocknumber\r\n");
			fflush(stderr);
#endif
			++errorcount;
			goto nakit;
		}
		checksum=0;
		for(index=0;index<128;index++)
		{
			sector[index]=getchar();
			checksum+=sector[index];
		}
		if(checksum!=getchar())
		{
#ifdef DEBUG
			fprintf(stderr,"Bad checksum\r\n");
			fflush(stderr);
#endif
			errorcount++;
			goto nakit;
		}
		putchar(ACK);
		fflush(stdout);
		blocknumber++;
		write(foo,sector,sizeof sector);
		if(!errorcount)continue;
nakit:
		putchar(NAK);
		fflush(stdout);
	}
	close(foo);

	putchar(ACK); /* tell the modem on the other end we accepted his EOT */
	putchar(ACK);
	putchar(ACK);
	fflush(stdout);

	fprintf(stderr,"Completed.\r\n");
	fflush(stderr);
	die(0);
}

timeout()
{
	fprintf(stderr,
	"Timed out waiting for NAK from remote system\7\7\7\r\n");
	die(1);
}

die(how)
register int how;
{
	ttymode.sg_flags=ttyhold;
	stty(0,&ttymode);
	exit(how);
}
//SYSIN DD SYSOUT=BD