[net.sources] XMODEM/YMODEM program for 4.2/4.3BSD

grandi@noao.UUCP (Steve Grandi) (01/07/87)

Several requests have been made lately for a XMODEM/YMODEM program to
support file transfer to or from micros.  What follows is my adaptation of
a previous XMODEM program.  It runs on 4.2/4.3BSD systems only.  I
submitted this same exact package to mod.sources almost three months ago,
so when the dam bursts in that newsgroup, you might see this program again.


Steve Grandi, National Optical Astronomy Observatories, Tucson, AZ, 602-325-9228
UUCP: {decvax,hao,ihnp4,seismo}!noao!grandi           Internet: grandi@noao.arpa
------------CUT HERE-----------------------------------------------------------
: This is a shar archive.  Extract with sh, not csh.
echo x - README
sed -e 's/^X//' > README << '!Funky!Stuff!'
XThe xmodem program implements the Christensen (XMODEM) file transfer
Xprotocol for moving files between 4.2/4.3BSD Unix systems and microcomputers.
XThe XMODEM/CRC protocol, the MODEM7 batch protocol, the XMODEM-1K
Xblock protocol and the YMODEM batch protocol are all supported by xmodem.
XFor details of the protocols, see the document edited by Chuck Forsberg titled
XXMODEM/YMODEM Protocol Reference.
X
XThis program runs on 4.2/4.3BSD systems ONLY.  It has been tested on VAXes
Xand Suns against the MEX-PC program from Niteowl Software.
X
XI have tried to keep the 4.2isms (select system call, 4.2BSD/v7 tty structures,
Xgettimeofday system call, etc.) confined to the source file getput.c; but I 
Xmake no guarantees.  Also, I have made no attempt to keep variable names 
Xunder 7 characters.
X
X
XProgram history:
X
XDescended from UMODEM 3.5 by Lauren Weinstein, Richard Conn, and others.
X
XBased on XMODEM Version 1.0 by Brian Kantor, UCSD (3/84)  (Don't blame him 
Xfor what follows....)
X
XVersion 2.0 (CRC-16 and Modem7 batch file transfer) (5/85)
X
XVersion 2.1 (1K packets) (7/85)
X
XVersion 2.2 (general clean-ups and multi-character read speed-ups) (9/85)
X
XVersion 2.3 (nap while reading packets; split into several source files) (1/86)
X
XVersion 3.0 (Ymodem batch receive; associated changes) (2/86)
X
XVersion 3.1 (Ymodem batch send; associated changes) (8/86)
X
XVersion 3.2 (general cleanups) (9/86)
X
X
X
XPlease send bug fixes, additions and comments to:
XSteve Grandi, National Optical Astronomy Observatories (Tucson, Arizona)
X	{ihnp4,seismo,hao,arizona,...}!noao!grandi   grandi@noao.arpa
!Funky!Stuff!
echo x - xmodem.1
sed -e 's/^X//' > xmodem.1 << '!Funky!Stuff!'
X.TH XMODEM LOCAL "August 26, 1986"
X.UC 4.2
X.SH NAME
Xxmodem \- Christensen protocol file transfer utility
X.SH SYNOPSIS
X.B xmodem
X[\fBst|sb|rt|rb\fR][\fBYBKcdlx\fR]
X[file...]
X.br
X.SH DESCRIPTION
XThe
X.I xmodem
Xprogram implements the Christensen (XMODEM) file transfer
Xprotocol for moving files between 4.2/4.3BSD Unix systems and microcomputers.
XThe XMODEM/CRC protocol, the MODEM7 batch protocol, the XMODEM-1K
Xblock protocol and the YMODEM batch protocol are all supported by 
X.IR xmodem .
XFor details of the protocols,
Xsee the document edited by Chuck Forsberg titled
X.I
XXMODEM/YMODEM Protocol Reference.
X.sp
XThis program runs on 4.2/4.3BSD systems ONLY.  It has been tested on VAXes
Xand Suns against the MEX-PC program from Niteowl Software.
X.PP
X.SH PARAMETERS
XExactly one of the following must be selected:
X.TP
X.B rb  
XReceive Binary - files are placed on the Unix disk without conversion.
X.I Xmodem
Xwill silently destroy existing files of the same name.
X.TP
X.B rt  
XReceive Text - files are converted from the CP/M format of CR-LF pairs to the Unix
Xconvention of 
X.I newline 
Xcharacters only between lines.  
XBit 8 of each character is stripped (which makes Wordstar files much
Xmore readable).
XThe resulting file
Xis acceptable to the Unix editors and compilers, and is usually slightly
Xsmaller than the original CP/M file.
X.I Xmodem
Xwill silently destroy existing files of the same name.
X.TP
X.B sb  
XSend Binary - files are sent without conversion as they exist on the Unix disk.
X.TP
X.B st  
XSend Text - newline characters in the file are converted to CR-LF pairs
Xin accord with the CP/M conventions for text files.  The file grows
Xslightly as this occurs so the estimate of file transmission size and
Xtime are always optimistically low.
X.PP
X.SH OPTIONS
X.TP
X.B Y
XSelect the YMODEM batch protocol for sending files; a list of files specified
Xon the command line will be sent in sequence.  The YMODEM batch protocol is 
Xused automatically for file reception if the sending program requests it.
X.TP
X.B B
XSelect the MODEM7 batch protocol for sending files; a list of files specified
Xon the command line will be sent in sequence.  The MODEM7 batch protocol is 
Xused automatically for file reception if the sending program requests it.
X.TP
X.B K
XSelect the XMODEM-1K file transfer mode for sending files. Use of 1K packets on
Xlow-error lines increases throughput.  1K packets are automatically
Xused for file reception if the sending program requests it.
X.TP
X.B c   
XSelect the CRC-16 error-checking protocol on receive.  CRC mode is better at catching
Xtransmission errors that occur than the alternative checksum protocol.  
XCRC mode is automatically selected for file
Xtransmission if the receiving modem program requests it.
X.TP
X.B d   
XDelete the 
X.I xmodem.log
Xfile before file transfer is begun.
X.TP
X.B l   
XDo NOT write to the log file.  If logging is selected, a file
X.I xmodem.log 
Xwill be created (or appended to), with entries for significant events, errors
Xand retries.  This can be useful to see why things went wrong
Xwhen they do.
X.TP
X.B x
XToggle on debug mode.  If debug mode is selected, copious and possibly
Xuseful debugging information will be placed in 
X.IR xmodem.log .
X.SH "FILE NAMES"
XFiles transmitted using one of the batch modes
Xwill be stored on the remote machine under a CP/M-ified name (limited
Xto eight characters plus a three character extension; ":" characters will
Xbe turned into "/" characters; all characters will be in monocase).  Files received using one of the batch modes
Xwill be stored under their transmitted names (except that any "/" characters
Xin the file name will be converted into ":" characters and all upper-case
Xcharacters will be translated into lower case).
X.PP
XWhen a batch receive is requested,
X.I xmodem
Xtakes a wait and see attitude and can adapt to either batch protocol or even
Xa classic XMODEM transfer (note that CRC-16 mode is automatically set under
Xthese circumstances).
XIf a classic, "non-batch" XMODEM file reception takes place, 
Xthe received file is stored as
X.IR xmodem.in .
XFile names present on the command line for a batch receive are ignored.
X.SH NOTES
XWhile waiting for the beginning of a transfer or the beginning of a packet,
X.I xmodem
Xtreats two CAN (Cntrl-X) characters that are received within 3 seconds
Xas a request to abort.
X.PP
XSqueezed CP/M files must be transferred in binary mode, even if they
Xcontain text.
X.PP
XIf you use 
X.I xmodem
Xover a 
X.I rlogin
Xlink, you must use the flag
X.IR "rlogin machine -8" .
X.SH EXAMPLES
XTo receive a text file transmitted from a micro (using CRC-16
Xerror-checking) and store it under the
Xname 
X.IR file.name ,
Xuse the command line
X.RS
X.B "xmodem rtc file.name"
X.RE
XNote that if the transmitting program on the micro uses the 1K packet
Xprotocol or either batch protocol,
X.I xmodem
Xdetects this automatically and takes appropriate action.  Further
Xnote that if one of the batch protocols is used, the received file(s)
Xwill be stored under their own names and the name on the command line
X(if any) will be ignored.
X.PP
XTo send a set of text files to a microcomputer using 1K packets and the
XYMODEM batch protocol, use the command line
X.RS
X.B "xmodem stYK *.txt"
X.RE
X.SH FILES
Xxmodem.log (if logging is enabled)
X.SH BUGS
XBatch mode could be smarter about bad file-names in the midst of a
Xbatch transmit/receive.
X.PP
XBatch mode could allow a mixture of binary and text files.
X.PP
XThe file lengths and creation times embedded in the YMODEM batch protocol are
Xneither set on transmit nor used on receive.  However, the "IMP/KMD record
Xcount" field is utilized.
X.SH SEE ALSO
Xkermit(1)
X.SH AUTHOR
XSteve Grandi, National Optical Astronomy Observatories.  Based on
X.I xmodem
Xby Brian Kantor, University of California at San Diego.
XThis, in turn, was based on
X.I umodem
Xby Lauren Weinstein, Richard Conn and others.
!Funky!Stuff!
echo x - Makefile
sed -e 's/^X//' > Makefile << '!Funky!Stuff!'
XOBJECTS = xmodem.o getput.o misc.o send.o receive.o batch.o
XCFLAGS = -O
X
Xxmodem: $(OBJECTS)
X	cc $(CFLAGS) $(OBJECTS) -o xmodem
X
X$(OBJECTS): xmodem.h
X
Xprint: 
X	lpr -p -Pvmslp xmodem.h xmodem.c getput.c receive.c send.c batch.c misc.c Makefile
!Funky!Stuff!
echo x - xmodem.h
sed -e 's/^X//' > xmodem.h << '!Funky!Stuff!'
X#include <ctype.h>
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/time.h>
X#include <sgtty.h>
X#include <signal.h>
X
X/* define macros to print messages in log file */
X#define  logit(string) if(LOGFLAG)fprintf(LOGFP,string)
X#define  logitarg(string,argument) if(LOGFLAG)fprintf(LOGFP,string,argument)
X
X#define	     VERSION	32	/* Version Number */
X#define      FALSE      0
X#define      TRUE       1
X
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
X/*  XMODEM Constants  */
X#define      TIMEOUT  	-1
X#define      ERRORMAX  	10    /* maximum errors tolerated */
X#define      NAKMAX	2     /* maximum times to wait for initial NAK when sending */
X#define      RETRYMAX  	5     /* maximum retries to be made */
X#define      CRCSWMAX	4     /* maximum time to try CRC mode before switching */
X#define      KSWMAX	5     /* maximum errors before switching to 128 byte packets */
X#define      SLEEPNUM	100   /* target number of characters to collect during sleepy time */
X#define	     BBUFSIZ	1024  /* buffer size */
X#define      NAMSIZ	11    /* length of a CP/M file name string */
X#define	     CTRLZ	032   /* CP/M EOF for text (usually!) */
X#define      CRCCHR	'C'   /* CRC request character */
X#define      BAD_NAME	'u'   /* Bad filename indicator */
X
X#define      CREATMODE	0644  /* mode for created files */
X
X/* GLOBAL VARIABLES */
X
Xint ttyspeed;		/* tty speed (bits per second) */
Xunsigned char buff[BBUFSIZ];	/* buffer for data */
Xint nbchr;		/* number of chars read so far for buffered read */
Xchar filename[256];	/* place to construct filenames */
XFILE *LOGFP;		/* descriptor for LOG file */
X
X/* option flags and state variables */
Xchar	XMITTYPE;	/* text or binary? */
Xint	DEBUG;		/* keep debugging info in log? */
Xint	RECVFLAG;	/* receive? */
Xint	SENDFLAG;	/* send? */
Xint	BATCH;		/* batch? (Now used as a state variable) */
Xint	CRCMODE;	/* CRC or checksums? */
Xint	DELFLAG;	/* don't delete old log file? */
Xint	LOGFLAG;	/* keep log? */
Xint	LONGPACK; 	/* do not use long packets on transmit? */
Xint	MDM7BAT;	/* MODEM7 batch protocol */
Xint	YMDMBAT;	/* YMODEM batch protocol */
X
X
X/*   CRC-16 constants.  From Usenet contribution by Mark G. Mendel, 
X     Network Systems Corp.  (ihnp4!umn-cs!hyper!mark)
X*/
X
X    /* the CRC polynomial. */
X#define	P	0x1021
X
X    /* number of bits in CRC */
X#define W	16
X
X    /* this the number of bits per char */
X#define B	8
!Funky!Stuff!
echo x - xmodem.c
sed -e 's/^X//' > xmodem.c << '!Funky!Stuff!'
X/*
X *  XMODEM -- Implements the Christensen XMODEM protocol, 
X *            for packetized file up/downloading.    
X *
X *	This version runs on 4.2/4.3BSD ONLY!  It won't work ANYWHERE else.  
X *
X *	I have tried to keep the 4.2isms (select system call, 4.2BSD/v7 tty
X *	structures, gettimeofday system call, etc.) in the source file
X *	getput.c; but I make no guarantees.  Also, I have made no attempt to
X *	keep variable names under 7 characters (although a cursory check
X *	shows that all variables are unique within 7 first characters).
X *	The program has been successfully run on VAXes (4.2/4.3BSD) and SUNs
X *	(2.0/3.0) against MEX-PC.
X *
X *   -- Based on UMODEM 3.5 by Lauren Weinstein, Richard Conn, and others.
X *
X *  XMODEM Version 1.0  - by Brian Kantor, UCSD (3/84)
X *
X *  Version 2.0 (CRC-16 and Modem7 batch file transfer) -- Steve Grandi, NOAO (5/85)
X *
X *  Version 2.1 (1K packets) -- Steve Grandi, NOAO (7/85)
X *
X *  Version 2.2 (general clean-ups and multi-character read speed-ups) -- Steve Grandi, NOAO (9/85)
X *
X *  Version 2.3 (napping while reading packet; split into several source files) -- Steve Grandi, NOAO (1/86)
X *
X *  Version 3.0 (Ymodem batch receive; associated changes) -- Steve Grandi, NOAO (2/86)
X *
X *  Version 3.1 (Ymodem batch send; associated changes) -- Steve Grandi, NOAO (8/86)
X *
X *  Version 3.2 (general cleanups) -- Steve Grandi, NOAO (9/86)
X *
X *  Please send bug fixes, additions and comments to:
X *	{ihnp4,seismo,hao}!noao!grandi   grandi@noao.arpa
X */
X
X#include "xmodem.h"
X
Xmain(argc, argv)
Xint argc;
Xchar **argv;
X{
X	char *getenv();
X	FILE *fopen();
X	char *unix_cpm();
X	char *strcpy();
X	char *strcat();
X	
X	char *fname = filename;		/* convenient place to stash file names */
X	char *logfile = "xmodem.log";	/* Name of LOG File */
X	
X	char *stamptime();		/* for timestamp */
X
X	char *defname = "xmodem.in";	/* default file name if none given */
X
X	struct stat filestatbuf;	/* file status info */
X
X	int index;
X	char flag;
X	long expsect;
X
X	/* initialize option flags */
X
X	XMITTYPE = 't';		/* assume text transfer */
X	DEBUG = FALSE;		/* keep debugging info in log */
X	RECVFLAG = FALSE;	/* not receive */
X	SENDFLAG = FALSE;	/* not send either */
X	BATCH = FALSE;		/* nor batch */
X	CRCMODE = FALSE;	/* use checksums for now */
X	DELFLAG = FALSE;	/* don't delete old log file */
X	LOGFLAG = TRUE;		/* keep log */
X	LONGPACK = FALSE; 	/* do not use long packets on transmit */
X	MDM7BAT = FALSE;	/* no MODEM7 batch mode */
X	YMDMBAT = FALSE;	/* no YMODEM batch mode */
X
X	printf("XMODEM Version %d.%d", VERSION/10, VERSION%10);
X	printf(" -- UNIX-CP/M Remote File Transfer Facility\n");
X
X	if (argc == 1)
X		{
X		help();
X		exit(-1);
X		}
X
X	index = 0;		/* set index for flag loop */
X
X	while ((flag = argv[1][index++]) != '\0')
X	    switch (flag) {
X		case '-' : break;
X		case 'x' : DEBUG = TRUE;  /* turn on debugging log */
X			   break;
X		case 'c' : CRCMODE = TRUE; /* enable CRC on receive */
X			   break;
X		case 'd' : DELFLAG = TRUE;  /* delete log file */
X			   break;
X		case 'l' : LOGFLAG = FALSE;  /* turn off log  */
X			   break;
X		case 'B' : MDM7BAT = TRUE;  /* turn on MODEM7 batch protocol */
X			   BATCH   = TRUE;
X			   break;
X		case 'Y' : YMDMBAT = TRUE;  /* turn on YMODEM batch protocol */
X			   BATCH   = TRUE;
X			   break;
X		case 'K' : LONGPACK = TRUE;  /* use 1K packets on transmit */
X			   break;
X		case 'r' : RECVFLAG = TRUE;  /* receive file */
X			   XMITTYPE = gettype(argv[1][index++]);  /* get t/b */
X			   break;
X		case 's' : SENDFLAG = TRUE;  /* send file */
X			   XMITTYPE = gettype(argv[1][index++]);
X			   break;
X		default  : printf ("Invalid Flag %c ignored\n", flag);
X			   break;
X	   }
X
X	if (DEBUG)
X		LOGFLAG = TRUE;
X
X	if (LOGFLAG)
X	   { 
X	     if ((fname = getenv("HOME")) == 0)	/* Get HOME variable */
X		error("Fatal - Can't get Environment!", FALSE);
X	     fname = strcat(fname, "/");
X	     fname = strcat(fname, logfile);
X	     if (!DELFLAG)
X		LOGFP = fopen(fname, "a");  /* append to LOG file */
X	     else
X		LOGFP = fopen(fname, "w");  /* new LOG file */
X	     if (!LOGFP)
X		error("Fatal - Can't Open Log File", FALSE);
X
X	     fprintf(LOGFP,"\n++++++++  %s", stamptime());
X	     fprintf(LOGFP,"XMODEM Version %d.%d\n", VERSION/10, VERSION%10);
X	   }
X
X	getspeed();		/* get tty-speed for time estimates */
X
X	if (RECVFLAG && SENDFLAG)
X		error("Fatal - Both Send and Receive Functions Specified", FALSE);
X
X	if (MDM7BAT && YMDMBAT)
X		error("Fatal - Both YMODEM and MODEM7 Batch Protocols Specified", FALSE);
X
X	if (!RECVFLAG && !SENDFLAG)
X		error("Fatal - Either Send or Receive Function must be chosen!",FALSE);
X	
X	if (SENDFLAG && argc==2)
X		error("Fatal - No file specified to send",FALSE);
X
X	if (RECVFLAG && argc==2)
X		{
X		CRCMODE = TRUE;		/* assume we really want CRC-16 in batch */
X		printf("Ready for BATCH RECEIVE");
X		printf(" in %s mode\n", (XMITTYPE == 't') ? "text" : "binary");
X		printf("Send several Control-X characters to cancel\n");
X		logit("Batch Receive Started");
X		logitarg(" in %s mode\n", (XMITTYPE == 't') ? "text" : "binary");
X		strcpy(fname, defname);
X		}
X
X	if (RECVFLAG && argc>2)
X		{
X		if(open(argv[2], 0) != -1)  /* check for overwriting */
X			{
X			logit("Warning -- Target File Exists and is Being Overwritten\n");
X			printf("Warning -- Target File Exists and is Being Overwritten\n");
X			}
X		printf("Ready to RECEIVE File %s", argv[2]);
X		printf(" in %s mode\n", (XMITTYPE == 't') ? "text" : "binary");
X		printf("Send several Control-X characters to cancel\n");
X		logitarg("Receiving in %s mode\n", (XMITTYPE == 't') ? "text" : "binary");
X		strcpy(fname,argv[2]);
X		}
X
X	if (RECVFLAG)
X		{  
X		setmodes();		/* set tty modes for transfer */
X
X		while(rfile(fname) != FALSE);  /* receive files */
X
X		restoremodes(FALSE);	/* restore normal tty modes */
X
X		sleep(2);		/* give other side time to return to terminal mode */
X		exit(0);
X		}
X
X	if (SENDFLAG && BATCH) 
X		{
X		if (YMDMBAT)
X			{
X			printf("Ready to YMODEM BATCH SEND");
X			printf(" in %s mode\n", (XMITTYPE == 't') ? "text" : "binary");
X			logit("YMODEM Batch Send Started");
X			logitarg(" in %s mode\n", (XMITTYPE == 't') ? "text" : "binary");
X			}
X		else if (MDM7BAT)
X			{
X			printf("Ready to MODEM7 BATCH SEND");
X			printf(" in %s mode\n", (XMITTYPE == 't') ? "text" : "binary");
X			logit("MODEM7 Batch Send Started");
X			logitarg(" in %s mode\n", (XMITTYPE == 't') ? "text" : "binary");
X			}
X		printf("Send several Control-X characters to cancel\n");
X
X		setmodes();
X		for (index=2; index<argc; index++) {
X			if (stat(argv[index], &filestatbuf) < 0) {
X				logitarg("\nFile %s not found\n", argv[index]);
X				continue;
X			}
X			sfile(argv[index]);
X		}
X		sfile("");
X		restoremodes(FALSE);
X
X		logit("Batch Send Complete\n");
X		sleep(2);
X		exit (0);
X		}
X
X	if (SENDFLAG && !BATCH) 
X		{
X		if (stat(argv[2], &filestatbuf) < 0)
X			error("Can't find requested file", FALSE);
X		expsect = (filestatbuf.st_size/128)+1;
X			
X		printf("File %s Ready to SEND", argv[2]);
X		printf(" in %s mode\n", (XMITTYPE == 't') ? "text" : "binary");
X		printf("Estimated File Size %ldK, %ld Records, %ld Bytes\n",
X	    	  (filestatbuf.st_size/1024)+1, expsect,
X	  	  filestatbuf.st_size);
X		projtime(expsect, stdout);
X		printf("Send several Control-X characters to cancel\n");
X		logitarg("Sending in %s mode\n", (XMITTYPE == 't') ? "text" : "binary");
X
X		setmodes();
X		sfile(argv[2]);
X		restoremodes(FALSE);
X
X		sleep(2);
X		exit(0);
X		}
X}
!Funky!Stuff!
echo x - getput.c
sed -e 's/^X//' > getput.c << '!Funky!Stuff!'
X/*
X * Contains system routines to get and put bytes, change tty modes, etc
X * Most of the routines are VERY 4.2BSD Specific!!!
X */
X
X#include "xmodem.h"
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			error("File Read Error", TRUE);
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		{
X		return(EOF);
X		}
X	}
X
X/*   CRC-16 constant array...
X     from Usenet contribution by Mark G. Mendel, Network Systems Corp.
X     (ihnp4!umn-cs!hyper!mark)
X*/
X
X/* crctab as calculated by initcrctab() */
Xunsigned short crctab[1<<B] = { 
X    0x0000,  0x1021,  0x2042,  0x3063,  0x4084,  0x50a5,  0x60c6,  0x70e7,
X    0x8108,  0x9129,  0xa14a,  0xb16b,  0xc18c,  0xd1ad,  0xe1ce,  0xf1ef,
X    0x1231,  0x0210,  0x3273,  0x2252,  0x52b5,  0x4294,  0x72f7,  0x62d6,
X    0x9339,  0x8318,  0xb37b,  0xa35a,  0xd3bd,  0xc39c,  0xf3ff,  0xe3de,
X    0x2462,  0x3443,  0x0420,  0x1401,  0x64e6,  0x74c7,  0x44a4,  0x5485,
X    0xa56a,  0xb54b,  0x8528,  0x9509,  0xe5ee,  0xf5cf,  0xc5ac,  0xd58d,
X    0x3653,  0x2672,  0x1611,  0x0630,  0x76d7,  0x66f6,  0x5695,  0x46b4,
X    0xb75b,  0xa77a,  0x9719,  0x8738,  0xf7df,  0xe7fe,  0xd79d,  0xc7bc,
X    0x48c4,  0x58e5,  0x6886,  0x78a7,  0x0840,  0x1861,  0x2802,  0x3823,
X    0xc9cc,  0xd9ed,  0xe98e,  0xf9af,  0x8948,  0x9969,  0xa90a,  0xb92b,
X    0x5af5,  0x4ad4,  0x7ab7,  0x6a96,  0x1a71,  0x0a50,  0x3a33,  0x2a12,
X    0xdbfd,  0xcbdc,  0xfbbf,  0xeb9e,  0x9b79,  0x8b58,  0xbb3b,  0xab1a,
X    0x6ca6,  0x7c87,  0x4ce4,  0x5cc5,  0x2c22,  0x3c03,  0x0c60,  0x1c41,
X    0xedae,  0xfd8f,  0xcdec,  0xddcd,  0xad2a,  0xbd0b,  0x8d68,  0x9d49,
X    0x7e97,  0x6eb6,  0x5ed5,  0x4ef4,  0x3e13,  0x2e32,  0x1e51,  0x0e70,
X    0xff9f,  0xefbe,  0xdfdd,  0xcffc,  0xbf1b,  0xaf3a,  0x9f59,  0x8f78,
X    0x9188,  0x81a9,  0xb1ca,  0xa1eb,  0xd10c,  0xc12d,  0xf14e,  0xe16f,
X    0x1080,  0x00a1,  0x30c2,  0x20e3,  0x5004,  0x4025,  0x7046,  0x6067,
X    0x83b9,  0x9398,  0xa3fb,  0xb3da,  0xc33d,  0xd31c,  0xe37f,  0xf35e,
X    0x02b1,  0x1290,  0x22f3,  0x32d2,  0x4235,  0x5214,  0x6277,  0x7256,
X    0xb5ea,  0xa5cb,  0x95a8,  0x8589,  0xf56e,  0xe54f,  0xd52c,  0xc50d,
X    0x34e2,  0x24c3,  0x14a0,  0x0481,  0x7466,  0x6447,  0x5424,  0x4405,
X    0xa7db,  0xb7fa,  0x8799,  0x97b8,  0xe75f,  0xf77e,  0xc71d,  0xd73c,
X    0x26d3,  0x36f2,  0x0691,  0x16b0,  0x6657,  0x7676,  0x4615,  0x5634,
X    0xd94c,  0xc96d,  0xf90e,  0xe92f,  0x99c8,  0x89e9,  0xb98a,  0xa9ab,
X    0x5844,  0x4865,  0x7806,  0x6827,  0x18c0,  0x08e1,  0x3882,  0x28a3,
X    0xcb7d,  0xdb5c,  0xeb3f,  0xfb1e,  0x8bf9,  0x9bd8,  0xabbb,  0xbb9a,
X    0x4a75,  0x5a54,  0x6a37,  0x7a16,  0x0af1,  0x1ad0,  0x2ab3,  0x3a92,
X    0xfd2e,  0xed0f,  0xdd6c,  0xcd4d,  0xbdaa,  0xad8b,  0x9de8,  0x8dc9,
X    0x7c26,  0x6c07,  0x5c64,  0x4c45,  0x3ca2,  0x2c83,  0x1ce0,  0x0cc1,
X    0xef1f,  0xff3e,  0xcf5d,  0xdf7c,  0xaf9b,  0xbfba,  0x8fd9,  0x9ff8,
X    0x6e17,  0x7e36,  0x4e55,  0x5e74,  0x2e93,  0x3eb2,  0x0ed1,  0x1ef0
X    };
X
X/* get a byte from data stream -- timeout if "seconds" elapses */
X/* This routine is VERY 4.2 specific */
X
Xint
Xreadbyte(seconds)
Xint seconds;
X	{
X	int readfd;
X	char c;
X	struct timeval tmout;
X
X	tmout.tv_sec = seconds;
X	tmout.tv_usec = 0;
X
X	readfd = 1<<0;
X
X	if ((select(1, &readfd, (int *)0, (int *)0, &tmout)) == 0)
X		return(TIMEOUT);
X
X	read(0, &c, 1);
X
X	if (DEBUG)
X		fprintf(LOGFP, "DEBUG: readbyte %02xh\n", c & 0xff);
X
X	return(c & 0xff);  /* return the char */
X	}
X
X/* 
X get a buffer (length bufsize) from data stream -- timeout if "seconds" elapses.
X Read bunches of characters to save system overhead;
X Further process data while kernel is reading stream (calculating "checksum").
X Try to nap long enough so kernel collects 100 characters or so until we wake up
X*/
X
X/* This routine is VERY 4.2 specific */
X
Xint
Xreadbuf(bufsize, seconds, tmode, checksum, bufctr)
X
Xint bufsize,	/* number of chars to be read */
Xseconds, 	/* timeout period for each read */
Xtmode, 		/* transmission mode: TRUE if text, FALSE if binary */
X*checksum, 	/* pointer to checksum value */
X*bufctr;	/* length of actual data string in buffer */
X
X{
X	int readfd;		/* mask for select call */
X	struct timeval tmout;	/* timeout structure for select */
X	int numread;		/* number of chars read */
X	int left;		/* number of chars left to read */
X	int recfin = 0;		/* flag that EOF read */
X	char inbuf[BBUFSIZ];	/* buffer for incoming packet */
X	register unsigned char c;	/* character being processed */
X	register unsigned short chksm;	/* working copy of checksum */
X	register int bfctr;	/* working copy of bufctr */
X	int j;			/* loop index */
X
X	tmout.tv_sec = seconds;
X	tmout.tv_usec = 0;
X	readfd = 1<<0;
X	chksm = 0;
X	bfctr = 0;
X
X	for (left = bufsize; left > 0;) {
X
X		/* read however many chars are waiting */
X
X		if ((select(1, &readfd, (int *)0, (int *)0, &tmout)) == 0)
X			return(TIMEOUT);
X
X		numread = read(0, inbuf, left);
X		left -= numread;
X
X		if (DEBUG)
X			fprintf(LOGFP, "DEBUG: readbuf--read %d characters\n", numread);
X
X		/* now process part of packet we just read */
X
X		for (j =  0; j < numread; j++) 
X			{  
X				buff[bfctr] = c = inbuf[j] & 0xff;
X
X				if (CRCMODE)  /* CRC */
X					chksm = (chksm<<B) ^ crctab[(chksm>>(W-B)) ^ c];
X
X				else        /* checksum */
X		       			chksm = ((chksm+c) & 0xff);
X
X				/* binary mode */
X				if (!tmode)
X					{  
X					bfctr++;
X		       			continue;
X		       			}
X
X				/* text mode */
X				buff[bfctr] &= 0x7f; /* nuke bit 8 */
X				if (c == CR)       /* skip CR's */
X				continue;
X				if (c == CTRLZ)  /* CP/M EOF char */
X					{  
X					recfin = TRUE;
X		       			continue;
X		       			}
X		       		if (!recfin)
X					bfctr++;
X		     	}	
X
X		/* go to sleep to save uneeded system calls while kernel
X		   is reading data from serial line; forget this when we
X		   running at 9600 bps; also fudge constant from 10000 to
X		   9000 to avoid sleeping too long
X		*/
X		if (ttyspeed < 9600)
X			napms ( (left<SLEEPNUM ? left:SLEEPNUM) * 9000/ttyspeed);
X	}
X
X	*checksum = chksm;
X	*bufctr = bfctr;
X	return(0);
X}
X
X/* send a byte to data stream */
X
Xsendbyte(data)
Xchar data;
X	{
X	if (DEBUG)
X		fprintf(LOGFP, "DEBUG: sendbyte %02xh\n", data & 0xff);
X
X	if (write(1, &data, 1) != 1)  	/* write the byte (assume it goes NOW; no flushing needed) */
X		error ("Write error on stream", TRUE);
X	return;
X	}
X
X/* send a buffer to data stream */
Xwritebuf(buffer, nbytes)
Xchar *buffer;
Xint  nbytes;
X	{
X	if (DEBUG)
X		fprintf(LOGFP, "DEBUG: writebuf (%d bytes)\n", nbytes);
X
X	if (write(1, buffer, nbytes) != nbytes)		/* write the buffer (assume no TIOCFLUSH needed) */
X		error ("Write error on stream", TRUE);
X	return;
X	}
X
X/*
X * "nap" for specified time -- VERY 4.2BSD specific
X */
X
Xnapms (milliseconds)
Xint	milliseconds;
X{
X	struct	timeval	timeout;
X	int readfd;
X
X	if (milliseconds == 0)
X		return;
X	if (DEBUG)
X		fprintf (LOGFP, "DEBUG: napping for %d ms\n", milliseconds);
X	timeout.tv_sec = 0;
X	timeout.tv_usec = milliseconds * 1000;
X	readfd = 0;
X
X	(void) select(1, &readfd, (int *)0, (int *)0, &timeout);
X}
X
X 
X/* set and restore tty modes for XMODEM transfers */
X/* These routines are 4.2/v7(?) specific */
X
Xstruct sgttyb ttys, ttysnew;	/* for stty terminal mode calls */
Xstruct stat statbuf;		/* for terminal message on/off control */
X
Xint wason;			/* holds status of tty read write/modes */
Xchar *tty;			/* current tty name */
X
X
Xsetmodes()
X	{
X	char *ttyname();
X
X	extern onintr();
X
X	sleep(2);			/* let the output appear */
X	if (ioctl(0,TIOCGETP,&ttys)<0)  /* get tty params [V7] */
X		error("Can't get TTY Parameters", TRUE);
X
X	tty = ttyname(0);  /* identify current tty */
X	
X	/* transfer current modes to new structure */
X	ttysnew.sg_ispeed = ttys.sg_ispeed;	/* copy input speed */
X	ttysnew.sg_ospeed = ttys.sg_ospeed;	/* copy output speed */
X	ttysnew.sg_erase  = ttys.sg_erase;	/* copy erase flags */
X	ttysnew.sg_flags  = ttys.sg_flags;	/* copy flags */
X 	ttysnew.sg_kill   = ttys.sg_kill;	/* copy std terminal flags */
X
X	ttysnew.sg_flags |= RAW;    /* set for RAW Mode */
X	ttysnew.sg_flags &= ~ECHO;  /* set for no echoing */
X	ttysnew.sg_flags &= ~XTABS;  /* set for no tab expansion */
X	ttysnew.sg_flags &= ~LCASE;  /* set for no upper-to-lower case xlate */
X	ttysnew.sg_flags |= ANYP;  /* set for ANY Parity */
X	ttysnew.sg_flags &= ~NL3;  /* turn off ALL 3s - new line */
X	ttysnew.sg_flags &= ~TAB2; /* turn off tab 3s */
X	ttysnew.sg_flags &= ~CR3;  /* turn off CR 3s */
X	ttysnew.sg_flags &= ~FF1;  /* turn off FF 3s */
X	ttysnew.sg_flags &= ~BS1;  /* turn off BS 3s */
X	ttysnew.sg_flags &= ~TANDEM;  /* turn off flow control */
X
X	/* set new paramters */
X	if (ioctl(0,TIOCSETP,&ttysnew) < 0)
X		error("Can't set new TTY Parameters", TRUE);
X
X	if (stat(tty, &statbuf) < 0)  /* get tty status */ 
X		error("Can't get your TTY Status", TRUE);
X
X	if (statbuf.st_mode & 022)	/* Need to turn messages off */
X		if (chmod(tty, (int)statbuf.st_mode & ~022) < 0)
X			error("Can't change  TTY mode", TRUE);
X		else 
X			wason = TRUE;
X	else 
X		wason = FALSE;
X
X	/* set up signal catcher to restore tty state if we are KILLed */
X
X	if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
X		signal(SIGTERM, onintr);
X	}
X
X/* restore normal tty modes */
X
Xrestoremodes(errcall)
Xint errcall;
X	{
X	if (wason)
X		if (chmod(tty, (int)statbuf.st_mode | 022) < 0)
X			error("Can't change TTY mode", FALSE);
X	if (ioctl(0,TIOCSETP,&ttys) < 0)
X		{ if (!errcall)
X		   error("RESET - Can't restore normal TTY Params", FALSE);
X		else
X		     printf("RESET - Can't restore normal TTY Params\n");
X		}
X	if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
X		signal(SIGTERM, SIG_DFL);
X	return;
X	}
X
X
X
X
X/* signal catcher */
Xonintr()
X	{
X	error("Kill signal; bailing out", TRUE);
X	}
X
X/* create string with a timestamp for log file */
X
Xchar *stamptime()
X{
X	char *asctime();		/* stuff to get timestamp */
X	struct tm *localtime(), *tp;
X	struct timeval tv;
X	struct timezone tz;
X
X	gettimeofday (&tv, &tz);		/* fill in timestamp */
X	tp = localtime ((time_t *)&tv.tv_sec);
X	return(asctime(tp));
X}
X
X
X
X/* get tty speed for time estimates */
X
Xgetspeed()
X	{
X	static int speedtbl[] = {0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800,
X			2400, 4800, 9600, 19200, 0};
X	if (ioctl(0,TIOCGETP,&ttys) < 0)	/* get tty structure */
X		error("Can't get TTY parameters", FALSE);
X
X	ttyspeed = speedtbl[ttys.sg_ispeed];
X	logitarg ("Line speed = %d bits per second\n", ttyspeed);
X	}
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

grandi@noao.UUCP (Steve Grandi) (01/07/87)

: 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<NAMSIZ; i++)
X                {
X                c = readbyte(3);
X
X                if (c == CAN)
X                    {
X                    if (readbyte(3) == CAN)
X                        error("Program Canceled by User", TRUE);
X                    else
X                        {
X                        logit("Received single CAN character\n");
X                        errorflag = TRUE;
X                        break;
X                        }
X                    }
X
X                if (c == EOT && i == 0)
X                    {
X                    logit("MODEM7 Batch Receive Complete\n");
X                    return (FALSE);
X                    }
X
X                if (c == TIMEOUT)
X                    {
X                    logit("Timeout waiting for MODEM7 filename character\n");
X                    errorflag = TRUE;
X                    break;
X                    }
X
X                if (c == BAD_NAME)
X                    {
X                    logit("Error during MODEM7 filename transfer\n");
X                    errorflag = TRUE;
X                    break;
X                    }
X
X                *nameptr++ = c;
X                checksum += c;
X                sendbyte(ACK);
X                }
X
X            if (!errorflag)
X                {
X                c = readbyte(3);
X                if (c == CTRLZ)     /* OK; end of string found */
X                    {
X                    sendbyte(checksum + CTRLZ);
X                    if (readbyte(3) == ACK)     /* file name found! */
X                        {
X                        xmdebug("MODEM7 file name OK");
X                        *nameptr = '\000';
X                        name = cpm_unix(buff);
X                        BATCH = TRUE;
X                        }
X                    else
X                        {
X                        logit("Checksum error in MODEM7 filename\n");
X                        errorflag = TRUE;
X                        }
X                    }
X                else
X                    {
X                    logit("Length error in MODEM7 fielname\n");
X                    errorflag = TRUE;
X                    }
X                }
X            }
X            
X
X        if (firstchar == SOH || firstchar == STX)  /* start reading packet */
X            {
X            bufsize = (firstchar == SOH) ? 128 : 1024;
X
X            if (recvsectcnt == 0)           /* 1st data packet, initialize */
X                {
X                if (bufsize == 1024)
X                    logit("1K packet mode chosen\n");
X                start = time((time_t *) 0);
X                errors = 0;
X                }
X
X	    sectcurr = readbyte(3);
X	    sectcomp = readbyte(3);
X		if ((sectcurr + sectcomp) == 0xff)  /* is packet number checksum correct? */
X		    {  
X			if (sectcurr == ((sectnum+1) & 0xff))   /* is packet number correct? */
X			    {  
X			    if (DEBUG)
X			        fprintf(LOGFP,"DEBUG: packet %d started\n", sectnum);
X
X            /* Read, process and calculate checksum for a buffer of data */
X
X			    if (readbuf(bufsize, 3, tmode, &checksum, &bufctr) != TIMEOUT) 
X                                {
X
X		    /* verify checksum or CRC */
X
X				if (CRCMODE) 
X                                    {
X				    checksum &= 0xffff;
X				    inchecksum = readbyte(3);  /* get 16-bit CRC */
X				    inchecksum = (inchecksum<<8) | readbyte(3);
X				    }
X                        
X				else
X				    inchecksum = readbyte(3);  /* get simple 8-bit checksum */
X
X				if (inchecksum == checksum) /* good checksum, hence good packet */
X				    {  
X				    xmdebug("checksum ok");
X				    errors = 0;
X				    recvsectcnt += (bufsize == 128) ? 1 : 8;
X				    sectnum = sectcurr; 
X
X				    if (!openflag)      /* open output file if necessary */
X					{
X					openflag = TRUE;
X					if ((fd = creat(name, CREATMODE)) < 0)
X					    {
X					    sendbyte(CAN); sendbyte(CAN); sendbyte(CAN);
X					    error("Can't create file for receive", TRUE);
X					    }
X					logitarg("File Name: %s\n", name);
X					}
X
X				    if (write(fd, (char *) buff, bufctr) < 0)
X					error("File Write Error", TRUE);
X				    else
X					sendbyte(ACK);      /* ACK the received packet */
X				    }
X
X            /* Start handling various errors and special conditions */
X
X				else        /* bad checksum */
X				    {  
X				    logitarg("Checksum Error on Sector %s\n", sectdisp(recvsectcnt,bufsize,1));
X				    errorflag = TRUE;
X				    }
X                                }
X
X			    else    /* read timeout */
X				{
X				logitarg("Timeout while reading sector %s\n",sectdisp(recvsectcnt,bufsize,1));
X				errorflag = TRUE;
X				}
X			    }
X
X                        else        /* sector number is wrong OR Ymodem filename */
X                            { 
X			    if (sectcurr == 0 && recvsectcnt == 0)  /* Ymodem file-name packet */
X				{
X				logit("YMODEM Batch Protocol\n");
X
X				/* Read and process a file-name packet */
X
X				if (readbuf(bufsize, 3, FALSE, &checksum, &bufctr) != TIMEOUT) 
X                                    {
X
X				    /* verify checksum or CRC */
X
X				    if (CRCMODE) 
X                                        {
X					checksum &= 0xffff;
X					inchecksum = readbyte(3);  /* get 16-bit CRC */
X					inchecksum = (inchecksum<<8) | readbyte(3);
X				        }
X                        
X				    else
X					inchecksum = readbyte(3);  /* get simple 8-bit checksum */
X
X				    if (inchecksum == checksum) /* good checksum, hence good filename */
X					{
X					xmdebug("checksum ok");
X					strcpy(name, (char *)buff);
X                                        expsect = ((buff[bufsize-1]<<8) | buff[bufsize-2]);
X					sendbyte(ACK);      /* ACK the packet */
X					BATCH = TRUE;
X					if (strlen(name) == 0)  /* check for no more files */
X					    {
X					    logit("YMODEM Batch Receive Complete\n");
X					    return (FALSE);
X					    }
X                                        unixify(name);       /* make filename canonical */
X					logitarg("YMODEM file name: %s\n", name);
X					logitarg("YMODEM estimated file length %d sectors\n", expsect);
X					logitarg("YMODEM header info: %s\n", (char *)buff + strlen(name) + 1);
X					}
X
X				    else                /* bad filename checksum */
X					{
X					logit("checksum error on filename sector\n");
X					errorflag = TRUE;
X					}
X				    }
X				else
X				    {
X				    logit("Timeout while reading filename packet\n");
X				    errorflag = TRUE;
X                                    }
X				}
X
X			    else if (sectcurr == sectnum)   /* duplicate sector? */
X				{  
X				logitarg("Duplicate sector %s flushed\n", sectdisp(recvsectcnt,bufsize,0));
X				while(readbyte(3) != TIMEOUT)
X				    ;
X				sendbyte(ACK);
X				}
X			    else                /* no, real phase error */
X				{
X				logitarg("Phase Error - Expected packet is %s\n", sectdisp(recvsectcnt,bufsize,1));
X				errorflag = TRUE;
X				fatalerror = TRUE;
X				sendbyte(CAN); sendbyte(CAN); sendbyte(CAN);
X				}
X			    }
X		    }
X
X		else        /* bad packet number checksum */
X		    {  
X		    logitarg("Header Sector Number Error on Sector %s\n", sectdisp(recvsectcnt, bufsize,1));
X		    errorflag = TRUE;
X		    }
X
X	    }           /* END reading packet loop */
X    
X	if ((errorflag && !fatalerror) || recvsectcnt == 0) /* check on errors or batch transfers */
X	    {  
X	    if (errorflag)
X	        errors++;
X	    if (recvsectcnt != 0)
X		while (readbyte(3) != TIMEOUT)  /* wait for line to settle if not beginning */
X		    ;
X
X	    if (CRCMODE && recvsectcnt == 0 && errors == CRCSWMAX)
X		{
X		CRCMODE = FALSE;
X		logit("Sender not accepting CRC request, changing to checksum\n");
X		sendbyte(NAK);
X		}
X	    else if (!CRCMODE && recvsectcnt == 0 && errors == CRCSWMAX)
X		{
X		CRCMODE = TRUE;
X		logit("Sender not accepting checksum request, changing to CRC\n");
X		sendbyte(CRCCHR);
X		}
X	    else if (CRCMODE && recvsectcnt == 0)
X		sendbyte(CRCCHR);
X	    else
X		sendbyte(NAK);
X	    }
X	}
X        while ((firstchar != EOT) && (errors < ERRORMAX) && !fatalerror);   /* end of MAIN Do-While */
X
X	if ((firstchar == EOT) && (errors < ERRORMAX))  /* normal exit? */
X	    {
X	    close(fd);
X	    sendbyte(ACK);
X	    logit("Receive Complete\n");
X	    prtime (recvsectcnt, time((time_t *) 0) - start);
X        
X	    if (BATCH)          /* send appropriate return code */
X		return(TRUE);
X	    else
X		return(FALSE);
X	    }
X	else                /* no, error exit */
X	    { 
X	    if (recvsectcnt != 0)
X		sendbyte(CAN); sendbyte(CAN); sendbyte(CAN);
X		error("ABORTED -- Too Many Errors", TRUE);
X		return (FALSE);
X	    }
X    }
!Funky!Stuff!
echo x - send.c
sed -e 's/^X//' > 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<<B];	/* CRC-16 constant values, see getput.c */
X
X	register int bufctr, 		/* array index for data buffer */
X	sectnum;			/* packet number for packet header */
X
X	register unsigned short checksum; 	/* checksum/crc */
X
X	char blockbuf[BBUFSIZ+6];	/* holds packet as it is constructed */
X
X	struct stat filestatbuf;	/* file status info */
X
X	int fd, 		/* file descriptor for file being transmitted */
X	errors,			/* cumulative count of errors */
X	attempts,		/* number of attempts made to transmit a packet */
X	nlflag, 		/* flag that we have to send a LF in next packet */
X	sendfin, 		/* flag that we are sending the last packet */
X	closeout,		/* flag that we are closing out batch send */
X	tmode,			/* TRUE for text mode; FALSE for binary mode */
X	bbufcnt,		/* array index for packet */
X	firstchar,		/* first character in protocol transaction */
X	bufsize,		/* packet size (128 or 1024) */
X	sendresp,  		/* response char to sent block received from remote*/
X	extrachar;		/* count of extra LF characters added */
X	long sentsect;		/* count of 128 byte sectors actually sent */
X	long expsect;		/* count of 128 byte sectors expected to be sent */
X	time_t start;		/* starting time of transfer */
X	char c;
X
X	nbchr = 0;  /* clear buffered read char count */
X
X	CRCMODE = FALSE;	/* Receiver determines use of crc or checksum */
X
X	closeout = FALSE;	/* Check on NULL file name */
X	if (strcmp(name,"") == 0)
X		{
X		if (BATCH)
X			closeout = TRUE;
X		else
X			{
X			sendbyte(CAN); sendbyte(CAN); sendbyte(CAN);
X			error("NULL file name in send", TRUE);
X			}
X		}
X
X	if (!closeout)		/* Are we closing down batch? */
X		{			/* no; let's send a file */
X		if ((fd = open(name, 0)) < 0)	
X			{  
X			sendbyte(CAN); sendbyte(CAN); sendbyte(CAN);
X     	   		error("Can't open file for send", TRUE);
X			}
X	
X		stat(name, &filestatbuf);  /* get file status bytes */
X		expsect = (filestatbuf.st_size/128) + 1;
X	
X		if (LOGFLAG)
X			{   
X			fprintf(LOGFP, "----\nXMODEM Send Function\n");
X		    	fprintf(LOGFP, "File Name: %s\n", name);
X		  	fprintf(LOGFP,"Estimated File Size %ldK, %ld Records, %ld Bytes\n",
X		  	  (filestatbuf.st_size/1024)+1, expsect, filestatbuf.st_size);
X			projtime(expsect, LOGFP);
X			}
X		}
X	else
X		{
X		logit("----\nXMODEM Send Function\n");
X		logit("Closing down Batch Transmission\n");
X		}
X
X
X	tmode = (XMITTYPE == 't') ? TRUE : FALSE;	/* set text mode */
X
X	bufsize = LONGPACK ? 1024 : 128;		/* set sector size */
X	if (LONGPACK && !closeout)
X		logit("1K packet mode chosen\n");
X
X        sendfin = nlflag = FALSE;
X  	attempts = 0;
X
X	/* wait for and read startup character */
X	do
X		{
X		while (((firstchar=readbyte(30)) != NAK) && (firstchar != CRCCHR) && (firstchar != CAN))
X			if (++attempts > 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<bufsize; bufctr++)
X				buff[bufctr]=0;
X			if (!closeout)
X				{
X				cpmify(name);
X				strcpy((char *)buff, name);
X				buff[bufsize-2]	= (expsect & 0xff);
X				buff[bufsize-1] = ((expsect >> 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<<B) ^ crctab[(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