[net.sources] Additions to 4.2BSD tip for xmodem file transfer

grandi@noao.UUCP (Steve Grandi) (02/19/85)

The following stuff will enable the 4.2BSD tip program to send and
receive files using the Christensen xmodem protocol for packetized file 
up/downloading.  I used the xmodem program written by Brian Kantor 
(distributed on the net in March 1984) and sliced it and diced it to fit 
into tip.  Note that this code runs ONLY on 4.2BSD since it uses the 'select' 
system call.

Four new '~' commands are added to tip:
	~{	Receive xmodem text file
	~}	Send xmodem text file
	~(	Receive xmodem binary file
	~)	Send xmodem binary file
Each of these commands will ask for a local file name to send/receive.

The following shar archive contains two files.  diffs.cmdtab.c is a
context diff file which should be applied to /usr/src/usr.bin/tip/cmdtab.c
(use patch if you have it).  add.cmds.c is a file which should be appended 
in its entirety to the end of /usr/src/usr.bin/tip/cmds.c.

Anyone willing to jam the new unix kermit into tip?
--
Steve Grandi, National Optical Astronomy Observatories, Tucson, AZ  USA
UUCP:	{allegra,arizona,astrovax,decvax,hao,ihnp4,lbl-csam,seismo} !noao!grandi
Arpa:	noao!grandi@lbl-csam
--------------------------------------------------------------------------------
: This is a shar archive.  Extract with sh, not csh.
echo x - diffs.cmdtab.c
sed -e 's/^X//' > diffs.cmdtab.c << '!Funky!Stuff!'
X*** cmdtab.c.ORIG	Sat Jun 25 01:19:08 1983
X--- cmdtab.c	Thu Sep  6 08:30:29 1984
X***************
X*** 7,12
X  extern	int shell(), getfl(), sendfile(), chdirectory();
X  extern	int finish(), help(), pipefile(), consh(), variable();
X  extern	int cu_take(), cu_put(), dollar(), genbrk(), suspend();
X  
X  esctable_t etable[] = {
X  	{ '!',	NORM,	"shell",			 shell },
X
X--- 7,13 -----
X  extern	int shell(), getfl(), sendfile(), chdirectory();
X  extern	int finish(), help(), pipefile(), consh(), variable();
X  extern	int cu_take(), cu_put(), dollar(), genbrk(), suspend();
X+ extern	int rtfile(), rbfile(), stfile(), sbfile();
X  
X  esctable_t etable[] = {
X  	{ '!',	NORM,	"shell",			 shell },
X***************
X*** 25,29
X  	{ 's',	NORM,	"set variable",			 variable },
X  	{ '?',	NORM,	"get this summary",		 help },
X  	{ '#',	NORM,	"send break",			 genbrk },
X  	{ 0, 0, 0 }
X  };
X
X--- 26,34 -----
X  	{ 's',	NORM,	"set variable",			 variable },
X  	{ '?',	NORM,	"get this summary",		 help },
X  	{ '#',	NORM,	"send break",			 genbrk },
X+ 	{ '{',	NORM,	"receive xmodem text file", 	 rtfile },
X+ 	{ '}',	NORM,	"send xmodem text file", 	 stfile },
X+ 	{ '(',	NORM,	"receive xmodem binary file",	 rbfile },
X+ 	{ ')',	NORM,	"send xmodem binary file", 	 sbfile },
X  	{ 0, 0, 0 }
X  };
!Funky!Stuff!
echo x - add.cmds.c
sed -e 's/^X//' > add.cmds.c << '!Funky!Stuff!'
X/*  Place this code at the end of cmds.c */
X
X/*  XMODEM stuff; sag sept 1984	- Feb 1985
X *	Taken from Brian Kantor's (sdccsu3!brian) xmodem version 1.0
X *	Note that this code will work on version 4.2 ONLY (uses select)
X */
X
X#include <sys/stat.h>
X#include <sys/time.h>
X
X/*  ASCII Constants  */
X#define      SOH  	001 
X#define	     STX	002
X#define	     ETX	003
X#define      EOT	004
X#define	     ENQ	005
X#define      ACK  	006
X#define	     LF		012   /* Unix LF/NL */
X#define	     CR		015  
X#define      NAK  	025
X#define	     SYN	026
X#define	     CAN	030
X#define	     ESC	033
X#define	     CTRLZ	032   /* CP/M EOF for text (usually!) */
X
X/*  XMODEM Constants  */
X#define      TIMEOUT  	-1
X#define      ERRORMAX  	10    /* maximum errors tolerated */
X#define      RETRYMAX  	10    /* maximum retries to be made */
X#define	     BBUFSIZ	128   /* buffer size -- do not change! */
X#define      DEBUG	0     /* 1 for debugging output */
X#define      GETERR	-10   /* error code for getbyte routine */
X
Xchar buff[BBUFSIZ];
X
Xint nbchr;	/* number of chars read so far for buffered read */
X
X/* Receive a text file */
Xrtfile(c)
Xchar c;
X{
X	putchar (c);
X	rfile ('t');
X}
X
X/* Receive a binary file */
Xrbfile(c)
Xchar c;
X{
X	putchar (c);
X	rfile ('b');
X}
X
X/* Send a text file */
Xstfile(c)
Xchar c;
X{
X	putchar (c);
X	sfile ('t');
X}
X
X/* Send a binary file */
Xsbfile(c)
Xchar c;
X{
X	putchar (c);
X	sfile ('b');
X}
X
X/* print error message and cleanup for exit */
Xerror(msg)
Xchar *msg;
X	{
X	printf("\r\nXMODEM:  %s\n", msg);
X	ioctl (0, TIOCSETC, &tchars);
X	write (fildes[1], (char *)&ccc, 1);
X	signal (SIGINT, SIG_DFL);
X	}
X
X/*
X *
X *	Get a byte from the specified file.  Buffer the read so we don't
X *	have to use a system call for each character.
X *
X */
Xgetbyte(fildes, ch)				/* Buffered disk read */
Xint fildes;
Xchar *ch;
X
X	{
X	static char buf[BUFSIZ];	/* Remember buffer */
X	static char *bufp = buf;	/* Remember where we are in buffer */
X	
X	if (nbchr == 0)			/* Buffer exausted; read some more */
X		{
X		if ((nbchr = read(fildes, buf, BUFSIZ)) < 0)
X			{
X			error("File Read Error");
X			return (GETERR);
X		 	}
X		bufp = buf;		/* Set pointer to start of array */
X		}
X	if (--nbchr >= 0)
X		{
X		*ch = *bufp++;
X		return(0);
X		}
X	else
X		return(EOF);
X	}
X
X/**  receive a file  **/
Xrfile(mode)
Xchar mode;
X	{
X	register int bufctr, checksum;
X	register int c;
X	int j, firstchar, sectnum, sectcurr, tmode;
X	int sectcomp, errors, errorflag, recfin;
X	int fatalerror, inchecksum;
X	long recvsectcnt;
X
X	int (*f) ();
X	char *cp, *expand();
X	time_t start;
X
X	if (prompt (" Receive local file name? ", copyname))
X		return;
X	cp = expand (copyname);
X	if ((sfd = creat (cp, 0666)) < 0)
X		{
X		printf ("\r\n%s: Cannot creat\r\n", copyname);
X		return;
X		}
X
X	kill (pid, SIGIOT);
X	read (repdes[0], (char *)&ccc, 1); /* wait until read process stops */
X	ioctl (0, TIOCSETC, &defchars);	   /* set tty modes */
X	f = signal (SIGINT, intcopy);	   /* intercept control-c */
X
X	start = time(0);
X	quit = 0;
X	(void) setjmp(intbuf);		   /* set control-c catcher */
X
X	if (quit)
X		{
X		error("Control-C; Reception canceled");
X		close (sfd);
X		return;
X		}
X
X	printf("XMODEM:  Ready to RECEIVE File %s", cp);
X	puts("\r\nControl-C to cancel.\n");
X
X	recfin = FALSE;
X	sectnum = errors = 0;
X	fatalerror = FALSE;  /* NO fatal errors */
X	recvsectcnt = 0;  /* number of received sectors */
X
X	if (mode == 't')
X		tmode = TRUE;
X	else
X		tmode = FALSE;
X
X	sendbyte(NAK);  /* Start up the sender's first block */
X
X        do
X        	{   
X		errorflag = FALSE;
X            	do 
X			{
X                  	firstchar = readbyte(6);
X            		} 
X			while ((firstchar != SOH) 
X				&& (firstchar != EOT) 
X				&& (firstchar != TIMEOUT) 
X				&& ((firstchar & 0x7f) != CAN));
X
X            	if (firstchar == TIMEOUT)
X	    		{  
X			printf("\r\nTimeout on Sector %d\n", sectnum+1);
X               		errorflag = TRUE;
X	    		}
X
X            	if ((firstchar & 0x7f) == CAN)
X	    		{  
X			error("Reception canceled at user's request");
X			close (sfd);
X			return;
X	    		}
X
X            	if (firstchar == SOH)
X	   		{
X               		sectcurr = readbyte(3);	   /* get sector numbers */
X               		sectcomp = readbyte(3);
X               		if ((sectcurr + sectcomp) == 0xff)
X               			{  
X				if (sectcurr == ((sectnum+1) & 0xff))
X		 			{  
X					checksum = 0;
X		     			for (j = bufctr = 0; j < BBUFSIZ; j++)
X	      	     				{  
X						buff[bufctr] = c = readbyte(3);
X		        			checksum = ((checksum+c) & 0xff);
X						if (!tmode)  /* binary mode */
X							{  
X							bufctr++;
X		           				continue;
X		        				}
X						if (c == CR)
X			   				continue;  /* skip CR's */
X						if (c == CTRLZ)  /* CP/M EOF char */
X							{  
X							recfin = TRUE;  /* flag EOF */
X		           				continue;
X		        				}
X		        			if (!recfin)
X			   				bufctr++;
X		     				}
X		     			inchecksum = readbyte(3);  /* get checksum */
X		    			if (checksum == inchecksum)  /* good checksum */
X		     				{  
X						errors = 0;
X						recvsectcnt++;
X		        			sectnum = sectcurr; 
X						if (DEBUG) printf ("\n");
X						printf ("\rreceived sector %-4d", recvsectcnt);
X						if (write(sfd, buff, bufctr) < 0) 
X							{
X							sendbyte(CAN);
X			   				error("File Write Error");
X							close (sfd);
X							return;
X							}
X		        			else 
X			   				sendbyte(ACK);
X
X		     				}
X		     			else
X		     				{  
X						printf("\r\nChecksum Error on Sector %d\n", sectnum+1);
X		        			errorflag = TRUE;
X		     				}
X                  			}
X                  		else
X                  			{ 
X					if (sectcurr == sectnum)
X                    				{  
X						while(readbyte(3) != TIMEOUT)
X							;
X            	       				sendbyte(ACK);
X                    				}
X                    			else
X		    				{  
X						printf("\r\nPhase Error - Received Sector is ");
X			  			printf("%d while Expected Sector is %d\n",
X			   				sectcurr, ((sectnum+1) & 0xff));
X						errorflag = TRUE;
X						fatalerror = TRUE;
X						sendbyte(CAN);
X		    				}
X	          			}
X           			}
X			else
X	   			{  
X				printf("\r\nHeader Sector Number Error on Sector %d\n",
X		   			sectnum+1);
X               			errorflag = TRUE;
X	   			}
X        		}
X	
X        	if (errorflag)
X        		{  
X			errors++;
X	   		while (readbyte(3) != TIMEOUT)
X				;
X			sendbyte(NAK);
X        		}
X  		}
X  		while ((firstchar != EOT) && (errors < ERRORMAX) && !fatalerror);
X
X  	if ((firstchar == EOT) && (errors < ERRORMAX) && !fatalerror)
X  		{
X     		close(sfd);
X		sendbyte(ACK);
X		printf("\n\rReceive Complete");
X		printf("\n\r%ld CP/M Records ", recvsectcnt);
X		prtime(" transferred in ", time(0)-start);
X
X		ioctl (0, TIOCSETC, &tchars);		/* reset ttys */
X		write (fildes[1], (char *)&ccc, 1);	/* wakeup tip */
X		signal (SIGINT, SIG_DFL);		/* reset control-c catcher */
X  		}
X  	else
X  		{ 
X		sendbyte(CAN);
X     		error("\r\nABORTED -- Too Many Errors");
X		close (sfd);
X		return;
X  		}
X	}
X
X/**  send a file  **/
Xsfile(mode)
Xchar mode;
X	{
X	register int bufctr, checksum, sectnum;
X	char blockbuf[134];
X	int fd, attempts;
X	int nlflag, sendfin, tmode;
X	int bbufcnt;
X	int firstchar;
X	int getretrn;
X	char c;
X	int sendresp;  /* response char to sent block */
X
X	int (*f) ();
X	char *cp, *expand();
X	time_t start;
X
X	if (prompt (" Send local file name? ", copyname))
X		return;
X	cp = expand (copyname);
X	if ((fd = open(cp, 0)) < 0)
X		{  
X     	   	printf("Can't open file for send\n");
X		return;
X		}
X
X	kill (pid, SIGIOT);
X	read (repdes[0], (char *)&ccc, 1); /* wait until read process stops */
X	ioctl (0, TIOCSETC, &defchars);    /* setup tty */
X	f = signal (SIGINT, intcopy);	   /* prepare to catch control-c */
X	start = time(0);
X	quit = 0;
X	(void) setjmp(intbuf);		   /* setup control-c catcher */
X
X	nbchr = 0;  /* clear buffered read char count */
X		
X	if (quit)
X		{
X		sendbyte(CAN);
X		error("Control-C; Send canceled");
X		close (fd);
X		return;
X		}
X
X	printf("XMODEM:  File %s Ready to SEND", cp);
X	prfilestat(cp);  /* print file size statistics */
X	puts("\r\nControl-C to cancel.\n");
X
X	if (mode == 't')
X	   tmode = TRUE;
X	else
X	   tmode = FALSE;
X
X        sendfin = nlflag = FALSE;
X  	attempts = 0;
X
X	while (((firstchar=readbyte(30)) != NAK) && (firstchar != CAN))
X		{
X		if (++attempts > RETRYMAX) {
X			error("Remote System Not Responding");
X			close (fd);
X			return;
X		}
X		}
X
X	if ((firstchar & 0x7f) == CAN)
X		{
X		error("Send cancelled at user's request.");
X		close (fd);
X		return;
X		}
X
X	sectnum = 1;  /* first sector number */
X	attempts = 0;
X
X        do 
X		{   
X		for (bufctr=0; bufctr < BBUFSIZ;)
X	    		{
X			if (nlflag)
X	        		{  
X				buff[bufctr++] = LF;  /* leftover newline */
X	           		nlflag = FALSE;
X	        		}
X			getretrn = getbyte(fd, &c);
X			if (getretrn == GETERR)
X				{
X				sendbyte(CAN);
X				error ("Read error on local file");
X				close (fd);
X				return;
X				}
X			if (getretrn == 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		   		if (tmode)
X		      			buff[bufctr++] = CTRLZ;  /* Control-Z for CP/M EOF */
X	           		else
X		      			bufctr++;
X		   		continue;
X		      		}
X
X			if (tmode && c == LF)  /* text mode & Unix newline? */
X	    			{
X				buff[bufctr++] = CR;  /* insert carriage return */
X		     		if (bufctr < BBUFSIZ)
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            	attempts = 0;
X	
X	    	if (!bufctr)  /* if EOF on sector boundary */
X   	       		break;  /* avoid sending empty sector */
X
X            	do
X            		{
X			bbufcnt = 0;		/* start building block to be sent */
X			blockbuf[bbufcnt++] = SOH;	    /* start of packet char */
X			blockbuf[bbufcnt++] = sectnum;	    /* current sector # */
X			blockbuf[bbufcnt++] = -sectnum-1;   /* and its complement */
X
X                	checksum = 0;  /* init checksum */
X                	for (bufctr=0; bufctr < BBUFSIZ; bufctr++)
X               			{
X				blockbuf[bbufcnt++] = buff[bufctr];
X                 		checksum = ((checksum+buff[bufctr]) & 0xff);
X	         		}
X
X			blockbuf[bbufcnt++] = checksum;
X			write(FD, blockbuf, 132);  /* write the block */
X			ioctl(FD,TIOCFLUSH,0);
X
X                	attempts++;
X			sendresp = readbyte(10);  /* get response */
X			if (sendresp != ACK)
X		   		{
X		   		printf("\r\nNon-ACK Received on Sector %d\n",sectnum);
X		   		if (sendresp == TIMEOUT)
X					printf("\r\nThis Non-ACK was a TIMEOUT\n");
X		   		}
X            		}
X			while((sendresp != ACK) && (attempts < RETRYMAX));
X
X       		sectnum++;  /* increment to next sector number */
X		if (DEBUG) printf ("\n");
X		printf ("\rsent sector %-4d", sectnum-1);
X    		}
X		while (!sendfin && (attempts < RETRYMAX));
X
X    	if (attempts >= RETRYMAX) 
X		{
X		error("Remote System Not Responding");
X		close (fd);
X		return;
X		}
X
X    	attempts = 0;
X    	sendbyte(EOT);  /* send 1st EOT */
X	
X    	while ((readbyte(15) != ACK) && (attempts++ < RETRYMAX))
X	   	sendbyte(EOT);
X
X    	if (attempts >= RETRYMAX)
X	   	error("Remote System Not Responding on Completion");
X
X    	close(fd);
X    	printf("\r\nSend Complete\r\n");
X	prtime("Data transferred in ", time(0)-start);
X
X	ioctl (0, TIOCSETC, &tchars);		/* restore tty */
X	write (fildes[1], (char *)&ccc, 1);	/* wakeup tip */
X	signal (SIGINT, SIG_DFL);		/* reset control-c catcher */
X    	sleep(5);  /* give other side time to return to terminal mode */
X
X	}
X
X/*  print file size status information  */
Xprfilestat(name)
Xchar *name;
X	{
X	struct stat filestatbuf; /* file status info */
X
X	stat(name, &filestatbuf);  /* get file status bytes */
X	printf("\r\nEstimated File Size %ldK, %ld Records, %ld Bytes",
X	  	(filestatbuf.st_size/1024)+1, (filestatbuf.st_size/128)+1,
X	  	filestatbuf.st_size);
X		return;
X	}
X
X/* get a byte from data stream -- timeout if "seconds" elapses */
Xint readbyte(seconds)
Xint seconds;
X	{
X	int i, readfd;
X	char c;
X	struct timeval tmout;
X
X	tmout.tv_sec = seconds;
X	tmout.tv_usec = 0;
X
X	readfd = 1 << FD;
X
X	if ((i=select(FD+1, &readfd, 0, 0, &tmout)) == 0)
X		{
X		return(TIMEOUT);
X		}
X
X	read(FD, &c, 1);
X
X	return(c & 0xff);  /* return the char */
X	}
X
X/* send a byte to data stream */
Xsendbyte(data)
Xchar data;
X	{
X	write(FD, &data, 1);  	/* write the byte */
X	ioctl(FD,TIOCFLUSH,0);	/* flush so it really happens now! */
X	return;
X	}
!Funky!Stuff!
exit
-- 
Steve Grandi, National Optical Astronomy Observatories, Tucson, AZ  USA
UUCP:	{allegra,arizona,astrovax,decvax,hao,ihnp4,lbl-csam,seismo} !noao!grandi
Arpa:	noao!grandi@lbl-csam