/*
 *			    ACKNOWLEDGEMENTS
 *
 *	ZMDM was derived from rz/sz for Unix  posted by 
 *	Chuck Forsberg (...!tektronix!reed!omen!caf ). We
 *	thank him for his excellent code, and for giving
 *	us permission to use and distribute his code and
 *	documentation.
 *
 *	Atari St version by:
 *		Jwahar Bammi
 *			usenet: mandrill!bammi@{decvax,sun}.UUCP
 *			csnet:  bammi@mandrill.ces.CWRU.edu
 *			arpa:   bammi@mandrill.ces.CWRU.edu
 *			CompuServe: 71515,155
 */

#include "config.h"
#define SVERSION "sz 1.23 01-15-87"
#define SSTVERSION "sz 1.01 03-07-87"
#define OS	"Unix V7/BSD"

#ifndef STANDALONE
#define RETURN return
#else
int bibi() {} /* dummy */
#endif 

/* #define SDEBUG */

/*
 * sz.c By Chuck Forsberg
 *
 *	cc -O sz.c -o sz		USG (SYS III/V) Unix
 * 	cc -O -DV7  sz.c -o sz		Unix Version 7, 2.8 - 4.3 BSD
 *
 *		define CRCTABLE to use table driven CRC
 *
 *  ******* Some systems (Venix, Coherent, Regulus) do not *******
 *  ******* support tty raw mode read(2) identically to    *******
 *  ******* Unix. ONEREAD must be defined to force one     *******
 *  ******* character reads for these systems.		   *******
 *
 * A program for Unix to send files and commands to computers running
 *  Professional-YAM, PowerCom, YAM, IMP, or programs supporting Y/XMODEM.
 *
 *  Sz uses buffered I/O to greatly reduce CPU time compared to UMODEM.
 *
 *  USG UNIX (3.0) ioctl conventions courtesy Jeff Martin
 *
 *	St v 1.01
 *		added support for 32 bit CRC's (Zmodem) ++jrb
 *
 */

#include "zmdm.h"
#include "common.h"
#include "zmodem.h"

#ifndef Vsync 			/* Atari forgot these in osbind.h */
#define Vsync()	xbios(37)
#endif

#ifndef Supexec
		/* Some versions of osbind don't define Supexec */
#define Supexec(X) xbios(38,X)
#endif

#define SLOGFILE "szlog"

#define purgeline()	while(Bconstat(1)) Bconin(1)
#define S_IFDIR 0x0010

/*
 * Attention string to be executed by receiver to interrupt streaming data
 *  when an error is detected.  A pause (0336) may be needed before the
 *  ^C (03) or after it.
 */
#ifdef READCHECK
char Myattn[] = { 0 };
#else
#ifdef USG
char Myattn[] = { 03, 0336, 0 };
#else
char Myattn[] = { 0 };
#endif
#endif

#if (MWC || MANX)
FILE *fopen();
#else
FILE *fopen(), *fopenb();
#endif
static unsigned long SaveIntr;
static int Resuming, ForceBin;
static int in;

/* called by signal interrupt or terminate to clean things up */
bibis(n)
int n;
{
	canit(); flush_modem(); mode(0);
	fprintf(STDERR, "\r\nsz: caught signal %d; exiting\n", n);

	aexit(128+n);
}

/* Called when Zmodem gets an interrupt (^X) */
#ifdef ONINTR
onintr()
{
	siggi = 0;
	longjmp(intrjmp, -1);
}
#endif

#define ZKER
int Zctlesc;	/* Encode control characters */

#ifdef STANDALONE
int main(argc, argv)
#else
int dosz(argc, argv)
#endif
int argc;
char **argv;
{
	register char *cp;
	register int npats;
	int agcnt; char **agcv;
	char **patts;

#ifdef STANDALONE
#if (MWC || MANX)
	extern char *lmalloc();
#endif

	/* Set up Dta */
	Fsetdta(&statbuf);

	/* Get screen rez */
	rez = Getrez();
	drv_map = Drvmap();

#if (MWC || MANX)
#ifndef DYNABUF
	if((bufr = (unsigned char *)lmalloc((unsigned long)BBUFSIZ))
					 == (unsigned char *)NULL)
#else
	if((bufr = dalloc()) == (unsigned char *)NULL)
#endif /* DYNABUF */
	{
#ifdef REMOTE
		Bauxws("Sorry, could not allocate enough memory\r\n");
#else
		Bconws("Sorry, could not allocate enough memory\r\n");
#endif

		Pterm(4);
	}
#else /* MWC || MANX */
#ifdef DYNABUF
	if((bufr = dalloc()) == (unsigned char *)NULL)
	{
#ifdef REMOTE
		Bauxws("Sorry, could not allocate enough memory\r\n");
#else
		Bconws("Sorry, could not allocate enough memory\r\n");
#endif
		Pterm(5);
	}
#endif /* DYNABUF */
#endif /* MWC || MANX */

#ifndef REMOTE
	STDERR = stderr;
#else
#ifndef DLIBS
	if((STDERR = fopen("aux:", "rw")) == (FILE *)NULL)
	{
		Bauxws("Could not Open Aux Stream for Stderr\r\n");
		finish();
	}
	setbuf(STDERR, (char *)NULL);
#else
	STDERR = stdaux;
#endif /* DLIBS */
	
#endif /* REMOTE */
	{
		int speed;
		speed = getbaud();
		Baudrate = BAUD_RATE(speed);
		SetIoBuf();
		Rsconf(speed, 0,-1,-1,-1,-1);
		Vsync(); Vsync();
	}
#endif /* STANDALONE */

	SendType = 1;
	Rxtimeout = 600;
	npats=0;
	if (argc<2)
	{
		susage();
		RETURN(1);
	}

	initz();
#ifndef STANDALONE
	schkinvok(argv[0]);
#else
	Progname = "sz";
#endif

	SaveIntr = Setexc(0x0102, -1L);
	BusErr   = Setexc(2, -1L);
	AddrErr  = Setexc(3, -1L);

	Verbose = 0;
	Resuming = FALSE;
	ForceBin = FALSE;
	in = (-1);
	vdebug = 0;

#ifdef SDEBUG
	logf = (FILE *)NULL;
#endif
	while (--argc) {
		cp = *++argv;
		if (*cp++ == '-' && *cp) {
			while ( *cp) {
				switch(*cp++) {
				case '+':
					Lzmanag = ZMAPND; break;
#ifdef CSTOPB
				case '2':
					Twostop = TRUE; break;
#endif
				case '7':
					Wcsmask=0177; break;

/*
	On the St we look up the ext and decide. For Xmodem
	transfers, the file is always sent in binary mode
	and it is the responsibility of the receiver to
	strip CR if so desired.
				case 'a':
					Lzconv = ZCNL;
					Ascii = TRUE; break;
				case 'b':
					Lzconv = ZCBIN; break;
*/

/*  ST extention, force binary, useful to back up every thing
 *  in image mode, see -B option of rz too +jrb
 */
				case 'B':
					ForceBin = TRUE;
					Lzconv = ZCBIN;
					break;
				case 'C':
					if (--argc < 1) {
						susage();
						RETURN(1);
					}
					Cmdtries = atoi(*++argv);
					break;
				case 'i':
					Cmdack1 = ZCACK1;
					/* **** FALL THROUGH TO **** */
				case 'c':
					if (--argc != 1) {
						susage();
						RETURN(1);
					}
					Command = TRUE;
					Cmdstr = *++argv;
					break;
				case 'd':
					++Dottoslash;
					/* **** FALL THROUGH TO **** */
				case 'f':
					Fullname=TRUE; break;
				case 'E':
					Zctlesc = (-1); break;
				case 'e':
					Zctlesc = 1; break;
				case 'k':
					Blklen=KSIZE; break;
				case 'L':
					if (--argc < 1) {
						susage();
						RETURN(1);
					}
					blkopt = atoi(*++argv);
					if (blkopt<32 || blkopt>1024)
					{
						susage();
						RETURN(1);
					}
					break;
				case 'l':
					if (--argc < 1) {
						susage();
						RETURN(1);
					}
					Tframlen = atoi(*++argv);
					if (Tframlen<32 || Tframlen>1024)
					{
						susage();
						RETURN(1);
					}
					break;
				case 'N':
					Lzmanag = ZMDIFF;  break;
				case 'n':
					Lzmanag = ZMNEW;  break;
				case 'o':
					Wantfcs32 = FALSE; break;
				case 'p':
					Lzmanag = ZMPROT;  break;
				case 'r':
					Lzconv = ZCRESUM; Resuming = TRUE; break;
				case 'q':
					Quiet=TRUE; Verbose=0; break;
				case 't':
					if (--argc < 1) {
						susage();
						RETURN(1);
					}
					Rxtimeout = atoi(*++argv);
					if (Rxtimeout<10 || Rxtimeout>1000)
					{
						susage();
						RETURN(1);
					}
					break;
#ifdef TESTATTN
				case 'T':
					Testattn = TRUE; break;
#endif
				case 'u':
					++Unlinkafter; break;
				case 'v':
					++Verbose; break;
				case 'X':
					++Modem; break;
				case 'y':
					Lzmanag = ZMCLOB; break;
				default:
					susage();
					RETURN(1);
				}
			}
		}
		else if ( !npats && argc>0) {
			if (argv[0][0]) {
				npats=argc;
				patts=argv;
			}
		}
	}
	if (npats < 1 && !Command) 
	{
		susage();
		RETURN(1);
	}

#ifdef SDEBUG
	if (Verbose > 2)
	{
		if ((logf = fopen(SLOGFILE, "a"))== (FILE *)NULL)
		{
			fprintf(STDERR, "Can't open log file %s\n",SLOGFILE);
			RETURN(0200);
		}
		fprintf(logf, "Progname=%s\n", Progname);
		vdebug = 1;
		fflush(logf);
	}
#endif

	if ( !Quiet)
	{
		if (Verbose < 2)
			Verbose = 2;
	}


	Setexc(0x0102, bibis);
	Setexc(2, buserr);
	Setexc(3, addrerr);

	if(setjmp(busjmp))
	{
		/* On a bus error - instead of 2 bombs */
		fprintf(STDERR,"\r\nFATAL: Bus Error\n\n");
#ifdef SDEBUG
		if(logf != (FILE *)NULL)
			fclose(logf);
#endif
		if(in != -1)
		{
			stfclose(in);
			in = (-1);
		}
		canit();
		Setexc(2, BusErr);
		Setexc(3, AddrErr);
		Setexc(0x0102, SaveIntr);
		RETURN(2);
	}

	if(setjmp(addrjmp))
	{
		/* On address error - instead of 3 bombs */
		fprintf(STDERR,"\r\nFATAL: Address Error\n\n");
#ifdef SDEBUG
		if(logf != (FILE *)NULL)
			fclose(logf);
#endif
		if(in != -1)
		{
			stfclose(in);
			in = (-1);
		}
		canit();
		Setexc(2, BusErr);
		Setexc(3, AddrErr);
		Setexc(0x0102, SaveIntr);
		RETURN(3);
	}

	if((Exitcode = setjmp(abrtjmp)))
	{
		fprintf(STDERR,"\nTransfer ABORT\n\n");
#ifdef SDEBUG
	if(logf != (FILE *)NULL)
		fclose(logf);
#endif
		if(in != -1)
		{
			stfclose(in);
			in = (-1);
		}
		Setexc(2, BusErr);
		Setexc(3, AddrErr);
		Setexc(0x0102, SaveIntr);
		RETURN(Exitcode);
	}

	mode(1);


	if ( !Modem) {
		if (!Command && !Quiet && Verbose != 1)
		{
			fprintf(STDERR, "sz: %d file%s requested:\n",
				npats, npats>1?"s":"");
			for ( agcnt=npats, agcv=patts; --agcnt>=0; )
			{
				fprintf(STDERR, "%s ", *agcv++);
			}
			fprintf(STDERR, "\n\n");

#ifdef SDEBUG
			if(Verbose > 2)
			{
				fprintf(logf, "sz: %d file%s requested:\n",
					npats, npats>1?"s":"");
				for ( agcnt=npats, agcv=patts; --agcnt>=0; )
				{
					fprintf(logf, "%s ", *agcv++);
				}
				fprintf(logf, "\n");
				fflush(logf);
			}
#endif
		}
		if (!Nozmodem) {
			stohdr(0L);
			if (Command)
				Txhdr[ZF0] = ZCOMMAND;
			zshhdr(ZRQINIT, Txhdr);
		}
	}
	flush_modem();

	if (Command) {
		if (getzrxinit()) {
			Exitcode=0200; canit();
		}
		else if (zsendcmd(Cmdstr, 1+strlen(Cmdstr))) {
			Exitcode=0200; canit();
		}
	} else if (wcsend(npats, patts)==ERROR) {
		Exitcode=0200;
		canit();
	}
	flush_modem();
	mode(0);

#ifdef SDEBUG
	if(logf != (FILE *)NULL)
		fclose(logf);
#endif
	if(in != -1)
	{
		fclose(in);
		in = (-1);
	}

	putc('\n', STDERR);
	Setexc(2, BusErr);
	Setexc(3, AddrErr);
	Setexc(0x0102, SaveIntr);
	RETURN((errcnt != 0) | Exitcode);

}

#ifdef STANDALONE
RETURN(n)
int n;
{
	ResetIoBuf();
#if (MWC || MANX)
#ifndef DYNABUF
	free(bufr);
#else
	Mfree(bufr);
#endif
#else
#ifdef DYNABUF
	Mfree(bufr);
#endif
#endif
	exit(n);
}
#endif /* STANDALONE */

wcsend(argc, argp)
char *argp[];
{
	register int n;

	Crcflg=FALSE;
	Firstsec=TRUE;
	for (n=0; n<argc; ++n) {
		Totsecs = 0;
		if (wcs(argp[n])==ERROR)
			return ERROR;
	}
	Totsecs = 0;
	if (Filcnt==0) {	/* bitch if we couldn't open ANY files */
		if (1) {
			Command = TRUE;
			Cmdstr = "echo \"sz: Can't open any requested files\"";
			if (getzrxinit()) {
				Exitcode=0200; canit();
			}
			else if (zsendcmd(Cmdstr, 1+strlen(Cmdstr))) {
				Exitcode=0200; canit();
			}
			Exitcode = 1; return OK;
		}
		canit();
		fprintf(STDERR,"\n\nCan't open any requested files.\n\n");
		return ERROR;
	}
	if (Zmodem)
		saybibi();
	else
		wctxpn("");
	return OK;
}

wcs(oname)
char *oname;
{
	extern struct stat statbuf;
	char name[PATHLEN];

	strcpy(name, oname);
	++Noeofseen;  Lastread = 0;  Lastc = (-1); Dontread = FALSE;

	/* Check for directory or block special files */
	if(Fsfirst(name,(int)(0x01 | 0x010 | 0x020)) != 0)
	{
		++errcnt;
		return OK;	/* may be others */
	}

	if (statbuf.st_mode & S_IFDIR ) {
		return OK;
	}

	if((in = stfopen(oname,"r")) <= 0){
		++errcnt;
		return OK;	/* pass over it, there may be others */
	}

	++Filcnt;
	switch (wctxpn(name)) {
	case ERROR:
		return ERROR;
	case ZSKIP:
		return OK;
	}
	if (!Zmodem && wctx()==ERROR)
		return ERROR;
	if (Unlinkafter)
		unlink(oname);
	return 0;
}

#define ISDRIVE(X) ( (((X >= 'a') && (X <= 'n'))) || ((X >= 'A') && (X <= 'N')))
/*
 * generate and transmit pathname block consisting of
 *  pathname (null terminated),
 *  file length, mode time and file mode in octal
 *  as provided by the St's Fsfirst() call.
 *  N.B.: modifies the passed name, may extend it!
 */
wctxpn(name)
char *name;
{
	register char *p, *q;
	char name2[PATHLEN];
	unsigned long unixtime;
	extern struct stat statbuf;
	extern unsigned long st2unix();	/* Convert St's date and time to unix
				   time (seconds since Jan 1 1970 00:00:00) */

	if(*name)
		if(Fsfirst(name,(int)(0x01 | 0x020)) != 0)
			return ERROR;

	if (Modem) {
#ifndef REMOTE
		if (*name) {
			fprintf(STDERR,
			"Outgoing:\n\t Name: %s\n\t Size: %ld Bytes\n\
\tBlocks: %ld\n\tBufSize: %ld\n\n",
			  name, statbuf.st_size, statbuf.st_size>>7, (long)BBUFSIZ);
		}
#endif /* REMOTE */
		return OK;
	}

	vfile2("\r\nAwaiting pathname nak for %s\r\n", *name?name:"<END>");

	if ( !Zmodem)
		if (getnak())
			return ERROR;

	/* convert to Unix style path names */
	/* skip any device identifier */
	if(ISDRIVE(name[0]) && (name[1] == ':'))
		name = &name[2];

	for(p = name; *p != '\0'; p++)
	{
		if(*p == '\\')
			*p = '/';
	}

	if(!Resuming)
	{
		if(ForceBin)
		{
			Lzconv = ZCBIN;
			Ascii = FALSE;
		}
		else
		{
			if(!isbinary(name))
			{
				/* We indicate to the other side */
				Lzconv = ZCNL;
				Ascii = TRUE;
			}
			else
			{
				Lzconv = ZCBIN;
				Ascii = FALSE;
			}
		}
	}

	q = (char *) 0;
	if (Dottoslash) {		/* change . to . */
		for (p=name; *p; ++p) {
			if (*p == '/')
				q = p;
			else if (*p == '.')
				*(q=p) = '/';
		}
		if (q && strlen(++q) > 8) {	/* If name>8 chars */
			q += 8;			/*   make it .ext */
			strcpy(name2, q);	/* save excess of name */
			*q = '.';
			strcpy(++q, name2);	/* add it back */
		}
	}

	for (p=name, q=secbuf ; *p; )
		if ((*q++ = *p++) == '/' && !Fullname)
			q = secbuf;
	*q++ = 0;
	p=q;
	while (q < (secbuf + KSIZE))
		*q++ = 0;
	if (*name)
	{
		unixtime = st2unix(statbuf.st_time, statbuf.st_date);
		sprintf(p, "%lu %lo %o", statbuf.st_size,
			unixtime,
			 ((statbuf.st_mode & 0x01)?0444:0644));
	}

	if(Verbose)
#ifndef REMOTE
		fprintf(STDERR,
"Outgoing: [Hit CTRL-C to Cancel]\n\tName: %s\n\tSize: %ld Bytes\n\tBufSize:\
 %ld\n",
			name, statbuf.st_size, (long)BBUFSIZ);
	if(!Resuming)
	{
		fprintf(STDERR,"\tMode: %s\n\n", (Ascii)?"ASCII":"BINARY");
	}
	else
	{
		fprintf(STDERR,"\tMode: Resume Transfer Mode\n\n");
	}
#endif

#ifdef SDEBUG
	if(Verbose > 2)
	{
		fprintf(STDERR,"File: %s (%s)\n", name, p);
		fprintf(logf,"File: %s (%s)\n", name, p);
		fflush(logf);
	}
#endif

	/* force 1k blocks if name won't fit in 128 byte block */
	if (secbuf[125])
		Blklen=KSIZE;
	else {		/* A little goodie for IMP/KMD */
		if (Zmodem)
			Blklen = SECSIZ;
		secbuf[127] = (statbuf.st_size + 127) >>7;
		secbuf[126] = (statbuf.st_size + 127) >>15;
	}
	if (Zmodem)
		return zsendfile(secbuf, (int)(1+strlen(p)+ 
				(int)((long)p-(long)secbuf)), statbuf.st_size);
	if (wcputsec(secbuf, 0, SECSIZ)==ERROR)
		return ERROR;
	return OK;
}

getnak()
{
	register int firstch;

	Lastrx = 0;
	for (;;) {
		switch (firstch = readock(800,1)) {
		case ZPAD:
			if (getzrxinit())
				return ERROR;
			Ascii = 0;
			return FALSE;
		case TIMEOUT:
			vfile("Timeout on pathname\n");
			return TRUE;
		case WANTG:
#ifdef USG
			mode(2);	/* Set cbreak, XON/XOFF, etc. */
#endif
			Optiong = TRUE;
			Blklen=KSIZE;
		case WANTCRC:
			Crcflg = TRUE;
		case NAK:
			return FALSE;
		case CAN:
			if ((firstch = readock(20,1)) == CAN && Lastrx == CAN)
				return TRUE;
		default:
			break;
		}
		Lastrx = firstch;
	}
}


wctx()
{
	register int sectnum, attempts, firstch;

	Firstsec=TRUE;

	while ((firstch=readock(Rxtimeout, 2))!=NAK && firstch != WANTCRC
	  && firstch != WANTG && firstch!=TIMEOUT && firstch!=CAN)
		;
	if (firstch==CAN) {
		fprintf(STDERR, "\r\nReceiver CANcelled\n");
		return ERROR;
	}
	if (firstch==WANTCRC)
		Crcflg=TRUE;
	if (firstch==WANTG)
		Crcflg=TRUE;
	sectnum=1;
	while (filbuf(secbuf, Blklen)) {
		if (wcputsec(secbuf, sectnum, Blklen)==ERROR) {
			return ERROR;
		} else
			sectnum++;
	}

#ifndef REMOTE
	if (Verbose>1)
	{
		fprintf(STDERR, "\nClosing\n\n");
#ifdef SDEBUG
		if(Verbose > 2)
		{
			fprintf(logf, " Closing\n");
			fflush(logf);
		}
#endif
	}
#endif /* REMOTE */

	stfclose(in);
	in = (-1);

	attempts=0;
	do {
		vfile(" EOT ");
		purgeline();
		sendline(EOT);
		flush_modem();
		++attempts;
	}
		while ((firstch=(readock(Rxtimeout, 1)) != ACK) && attempts < RETRYMAX);
	if (attempts == RETRYMAX) {
		fprintf(STDERR, "\r\nNo ACK on EOT\n");
		return ERROR;
	}
	else
		return OK;
}

wcputsec(buf, sectnum, cseclen)
char *buf;
int sectnum;
int cseclen;	/* data length of this sector to send */
{
	register int checksum, wcj;
	register char *cp;
	unsigned int oldcrc;
	int firstch;
	int attempts;

	firstch=0;	/* part of logic to detect CAN CAN */

#ifndef REMOTE
	if (Verbose>1)
	{
		fprintf(STDERR, "\rBlock %06d  %04dK ", Totsecs, (Totsecs>>3));
#ifdef SDEBUG
		if(Verbose > 2)
		{
			fprintf(logf, "\rBlock %d %dK ", Totsecs, (Totsecs>>3) );
			fflush(logf);
		}
#endif
	}
#endif /* REMOTE */

	for (attempts=0; attempts <= RETRYMAX; attempts++) {
		Lastrx= firstch;
		sendline(cseclen==KSIZE?STX:SOH);
		sendline(sectnum);
		sendline(-sectnum -1);
		oldcrc=checksum=0;
		for (wcj=cseclen,cp=buf; --wcj>=0; ) {
			sendline(*cp);
			oldcrc=updcrc((0377& *cp), oldcrc);
			checksum += *cp++;
		}
		if (Crcflg) {
			oldcrc=updcrc(0,updcrc(0,oldcrc));
			sendline((int)oldcrc>>8);
			sendline((int)oldcrc);
		}
		else
			sendline(checksum);

		if (Optiong) {
			Firstsec = FALSE; return OK;
		}
		firstch = readock(Rxtimeout, (Noeofseen&&sectnum) ? 2:1);
gotnak:
		switch (firstch) {
		case CAN:
			if(Lastrx == CAN) {
cancan:
				fprintf(STDERR, "\r\nCancelled\n");  return ERROR;
			}
			break;
		case TIMEOUT:
			vfile("Timeout on sector ACK\n"); continue;
		case WANTCRC:
			if (Firstsec)
				Crcflg = TRUE;
		case NAK:
			vfile("NAK on sector\n"); continue;
		case ACK: 
			Firstsec=FALSE;
			Totsecs += (cseclen>>7);
			return OK;
		case ERROR:
			vfile("Got burst for sector ACK\n"); break;
		default:
			vfile("Got %02x for sector ACK\n", firstch); break;
		}
		for (;;) {
			Lastrx = firstch;
			if ((firstch = readock(Rxtimeout, 2)) == TIMEOUT)
				break;
			if (firstch == NAK || firstch == WANTCRC)
				goto gotnak;
			if (firstch == CAN && Lastrx == CAN)
				goto cancan;
		}
	}
	fprintf(STDERR, "\r\nRetry Count Exceeded\n");
	return ERROR;
}

/* fill buf with count chars padding with ^Z for CPM */
filbuf(buf, count)
register unsigned char *buf;
register int count;
{
	register int m;

	if((buf[0] = stgetc(in)) == EOF)
		return 0;

	for(m = 1; (m < count) && ((buf[m] = stgetc(in)) != EOF); m++)
		/* loop */ ;

	while (m < count)
		buf[m++] = 032;

	return count;

}
/* fill buf with count chars */
zfilbuf(buf, count)
register char *buf;
{
	register int c, m;

	m=count;
	while ((c=stgetc(in))!=EOF) {
		*buf++ =c;
		if (--m == 0)
			break;
	}
	return (count - m);
}

/*
 * readock(timeout, count) reads character(s) from file descriptor 0
 *  (1 <= count <= 3)
 * it attempts to read count characters. If it gets more than one,
 * it is an error unless all are CAN
 * (otherwise, only normal response is ACK, CAN, or C)
 *  Only looks for one if Optiong, which signifies cbreak, not raw input
 *
 * timeout is in tenths of seconds
 */
readock(timeout, count)
int timeout, count;
{
	register int c;
	static char byt[5];

	if (Optiong)
		count = 1;	/* Special hack for cbreak */

	if (setjmp(tohere)) {
		vfile("TIMEOUT\n");
		return TIMEOUT;
	}
	c = timeout >> 3;
	if (c<2)
		c=2;
#ifdef SDEBUG
	if (Verbose>3) {
		fprintf(STDERR, "Timeout=%d Calling alarm(%d) ", timeout, c);
		byt[1] = 0;
		fprintf(logf, "Timeout=%d Calling alarm(%d) ", timeout, c);
		fflush(logf);

	}
#endif
	stalarm(c);
	c=read_modem(byt, count);

	stalarm(0);
#ifdef SDEBUG
	if (Verbose>5)
	{
		fprintf(STDERR, "ret cnt=%d %x %x\n", c, byt[0], byt[1]);
		fprintf(logf, "ret cnt=%d %x %x\n", c, byt[0], byt[1]);
		fflush(logf);
	}
#endif

	if (c<1)
		return TIMEOUT;
	if (c==1)
		return (byt[0]&0377);
	else
		while (c)
			if (byt[--c] != CAN)
				return ERROR;
	return CAN;
}

sreadline(n)
{
	return (readock(n, 1));
}


susage()
{
	fprintf(STDERR,"\nSend file(s) with ZMODEM/YMODEM/XMODEM Protocol\n");
	fprintf(STDERR,"	(Y) = Option applies to YMODEM only\n");
	fprintf(STDERR,"	(Z) = Option applies to ZMODEM only\n");
	fprintf(STDERR,
	"%s for %s by ST Enthusiasts at Case Western Reserve University\n",
	   SSTVERSION, STOS);
	fprintf(STDERR,"\tBased on %s for %s by Chuck Forsberg\n\n", SVERSION, OS);
/*	fprintf(STDERR,"Usage:	sz [-12+adefknquvXy] [-] file ...\n"); */
	fprintf(STDERR,"Usage:	sz [-+defknquvXyB] file ...\n");
/*	fprintf(STDERR,"	sz [-1eqv] -c COMMAND\n"); */
	fprintf(STDERR,"	sz [-eqv] -c COMMAND\n");
/*	fprintf(STDERR,"	1 Use stdout for modem input\n"); */
#ifdef CSTOPB
	fprintf(STDERR,"	2 Use 2 stop bits\n");
#endif
	fprintf(STDERR,"	+ Append to existing destination file (Z)\n");
/*	fprintf(STDERR,"	a (ASCII) change NL to CR/LF\n"); */
	fprintf(STDERR,"	c send COMMAND (Z)\n");
	fprintf(STDERR,"	d Change '.' to '/' in pathnames (Y/Z)\n");
	fprintf(STDERR,"	e Escape control characters (Z)\n");
	fprintf(STDERR,"	f send Full pathname (Y/Z)\n");
	fprintf(STDERR,"	i send COMMAND, ack Immediately (Z)\n");
	fprintf(STDERR,"	k Send 1024 byte packets (Y)\n");
	fprintf(STDERR,"	L N Limit packet length to N bytes (Z)\n");
	fprintf(STDERR,"	l N Limit frame length to N bytes (l>=L) (Z)\n");
	fprintf(STDERR,"	n send file if Newer|longer (Z)\n");
	fprintf(STDERR,"	N send file if different length|date (Z)\n");
	fprintf(STDERR, "	o Use 16 bit CRC instead of 32 bit CRC (Z)\n");
	fprintf(STDERR,"	p Protect existing destination file (Z)\n");
	fprintf(STDERR,"	r Resume/Recover interrupted file transfer (Z)\n");
	fprintf(STDERR,"	q Quiet (no progress reports)\n");
	fprintf(STDERR,"	u Unlink file after transmission\n");
	fprintf(STDERR,"	v Verbose - debugging information\n");
	fprintf(STDERR,"	X XMODEM protocol - send no pathnames\n");
	fprintf(STDERR,"	y Yes, overwrite existing file (Z)\n");
	fprintf(STDERR,"	B Force Binary mode transfers (Z)\n");
/*	fprintf(STDERR,
"- as pathname sends standard input as sPID.sz or environment ONAME\n"); */
	return(1);
}

/*
 * Get the receiver's init parameters
 */
getzrxinit()
{
	register int n;

	for (n=10; --n>=0; ) {
		
		switch (zgethdr(Rxhdr, 1)) {
		case ZCHALLENGE:	/* Echo receiver's challenge numbr */
			stohdr(Rxpos);
			zshhdr(ZACK, Txhdr);
			continue;
		case ZCOMMAND:		/* They didn't see out ZRQINIT */
			stohdr(0L);
			zshhdr(ZRQINIT, Txhdr);
			continue;
		case ZRINIT:
			Rxflags = 0377 & Rxhdr[ZF0];
 			Txfcs32 = (Wantfcs32 && (Rxflags & CANFC32));

			Rxbuflen = (0337 & Rxhdr[ZP0])+((0377 & Rxhdr[ZP1])<<8);
			vfile("Rxbuflen=%d Tframlen=%d", Rxbuflen, Tframlen);
			siggi = 0;
#ifndef READCHECK
#ifdef USG
			mode(2);	/* Set cbreak, XON/XOFF, etc. */
#else
			/* Use 1024 byte frames if no sample/interrupt */
			if (Rxbuflen < 32 || Rxbuflen > 1024) {
				Rxbuflen = 1024;
				vfile("Rxbuflen=%d", Rxbuflen);
			}
#endif
#endif
			/* Override to force shorter frame length */
			if (Rxbuflen && (Rxbuflen>Tframlen) && (Tframlen>=32))
				Rxbuflen = Tframlen;
			if ( !Rxbuflen && (Tframlen>=32) && (Tframlen<=1024))
				Rxbuflen = Tframlen;
			vfile("Rxbuflen=%d", Rxbuflen);

			/*
			 * If input is not a regular file, force ACK's each 1024
			 *  (A smarter strategey could be used here ...)
			 */
			if ((Rxbuflen == 0) || (Rxbuflen > 1024))
				Rxbuflen = 1024;
			vfile("Rxbuflen=%d", Rxbuflen);

			return (sendzsinit());
		case ZCAN:
		case TIMEOUT:
			return ERROR;
		case ZRQINIT:
			if (Rxhdr[ZF0] == ZCOMMAND)
				continue;
		default:
			zshhdr(ZNAK, Txhdr);
			continue;
		}
	}
	return ERROR;
}

/* Send send-init information */
sendzsinit()
{
	register int c;
	register int errors;

	if (Myattn[0] == '\0')
		return OK;

	errors = 0;
	for (;;) {
		stohdr(0L);
		zsbhdr(ZSINIT, Txhdr);
		zsdata(Myattn, 1+strlen(Myattn), ZCRCW);
		c = zgethdr(Rxhdr, 1);
		switch (c) {
		case ZCAN:
			return ERROR;
		case ZACK:
			return OK;
		default:
			if (++errors > 9)
				return ERROR;
			continue;
		}
	}
}

/* Send file name and related info */
zsendfile(buf, blen, szbytes)
char *buf;
int blen;
long szbytes;
{
	register int c, szstat;
	long start_time, end_time;
	extern void rd_time();

	Supexec(rd_time);
	start_time = pr_time;
	
	for (;;) {
		Txhdr[ZF0] = Lzconv;	/* file conversion request */
		Txhdr[ZF1] = Lzmanag;	/* file management request */
		Txhdr[ZF2] = Lztrans;	/* file transport request */
		Txhdr[ZF3] = 0;
		zsbhdr(ZFILE, Txhdr);
		zsdata(buf, blen, ZCRCW);
again:
		c = zgethdr(Rxhdr, 1);
		switch (c) {
		case ZRINIT:
			goto again;
		case ZCAN:
		case TIMEOUT:
		case ZABORT:
		case ZFIN:
			return ERROR;
		case ZSKIP:
#ifndef REMOTE
			fprintf(STDERR,"\n\n");
#endif
			stfclose(in); in = (-1); return c;
		case ZRPOS:
			if(stfseek(in, Rxpos, 0))
			{
				fprintf(STDERR,"\r\nError While Seeking file\n");
				return ERROR;
			}
			Txpos = Rxpos; Lastc = (-1); Dontread = FALSE;
			if((szstat =  zsendfdata()) == OK)
			{
#ifndef REMOTE
				Supexec(rd_time);
				end_time = pr_time;
				fprintf(STDERR,"%ld Bytes Sent\t\
Transfer Time %ld secs.\tApprox %ld cps\n\n", szbytes, (end_time - start_time)/200L,
szbytes/((end_time - start_time)/200L));
#endif
			}
			return szstat;
		case ERROR:
		default:
			continue;
		}
	}
}

/* Send the data in the file */
zsendfdata()
{
	register int c, c1, e;
	register int newcnt;
	register long tcount = 0;
	static int tleft = 6;	/* Counter for test mode */
	
	if (Baudrate > 300)
		Blklen = 256;
	if (Baudrate > 2400)
		Blklen = KSIZE;
/*	if (Rxbuflen && Blklen>Rxbuflen)  --jrb do not limit block size */
	if (Rxbuflen)
		Blklen = Rxbuflen;
	if (blkopt && Blklen > blkopt)
		Blklen = blkopt;
	vfile("Rxbuflen=%d Blklen=%d", Rxbuflen, Blklen);
somemore:
	if (setjmp(intrjmp)) {
waitack:
		switch (c1 = getinsync()) {
		default:
		case ZCAN:
			fprintf(STDERR,"\r\nReceiver Cancelled Transfer\n\n");
			stfclose(in);
			in = (-1);
			return ERROR;
		case ZSKIP:
#ifndef REMOTE
			fprintf(STDERR,"\r\nReceiver forced SKIP Transfer(1)\n\n");
#endif
			stfclose(in);
			in = (-1);
			return c1;
		case ZACK:
			tcount += c;
			lreport(tcount);
			/* fall thru */
		case ZRPOS:
			break;
		case ZRINIT:
			return OK;
		}
	}

	siggi = 1;
	newcnt = Rxbuflen;
	stohdr(Txpos);
	zsbhdr(ZDATA, Txhdr);

	/*
	 * Special testing mode.  This should force receiver to Attn,ZRPOS
	 *  many times.  Each time the signal should be caught, causing the
	 *  file to be started over from the beginning.
	 */
#ifdef TESTATTN
	if (Testattn) {
		if ( --tleft)
			while (tcount < 20000) {
				wr_modem(qbf);
				tcount += strlen(qbf);
#ifdef READCHECK
				while (Bconstat(1)) {
					switch (sreadline(1)) {
					case CAN:
					case ZPAD:
						goto waitack;
					}
				}
#endif
			}
		siggi = 0; canit();
		stsleep(3); purgeline(); mode(0);
#ifdef SDEBUG
		if(logf != (FILE *)NULL)
			fclose(logf);
#endif

/*		printf("\nsz: Tcount = %ld\n", tcount); */
		if (tleft) {
			/* printf("\r\nERROR: Interrupts Not Caught\n"); */
			aexit(1);
		}
		aexit(0);
	}
#endif

	do {
		if (Dontread) {
			c = Lastc;
		} else {
			c = zfilbuf(secbuf, Blklen);
			Lastread = Txpos;  Lastc = c;
		}
#ifdef SDEBUG
		if (Verbose > 10)
			vfile("Dontread=%d c=%d", Dontread, c);
#endif

		Dontread = FALSE;
		if (c < Blklen)
			e = ZCRCE;
		else if (Rxbuflen && (newcnt -= c) <= 0)
			e = ZCRCW;
		else
			e = ZCRCG;
		zsdata(secbuf, c, e);
		Txpos += c;
		if(e == ZCRCG)
		{
			tcount += c;
			lreport(tcount);
		}

		if (e == ZCRCW)
			goto waitack;
#ifdef READCHECK
		/*
		 * If the reverse channel can be tested for data,
		 *  this logic may be used to detect error packets
		 *  sent by the receiver, in place of setjmp/longjmp
		 *  rdchk(fdes) returns non 0 if a character is available
		 */
		flush_modem();
		while (Bconstat(1)) {
			switch (sreadline(1)) {
			case CAN:
			case ZPAD:
				zsdata(secbuf, 0, ZCRCE);
				goto waitack;
			}
		}
#endif
	} while (c == Blklen);
	tcount += c;
	lreport(tcount);
	siggi = 0;
	lsct = 1;

	for (;;) {
		stohdr(Txpos);
		zsbhdr(ZEOF, Txhdr);
		switch (getinsync()) {
		case ZACK:
			continue;
		case ZRPOS:
			goto somemore;
		case ZRINIT:
			return OK;
		case ZSKIP:
#ifndef REMOTE
			fprintf(STDERR,"\r\nReceiver forced SKIP Transfer(2)\n\n");
#endif
			stfclose(in);
			in = (-1);
			return c;
		default:
			fprintf(STDERR,"\r\nErrors while Send Data\n\n");
			stfclose(in);
			in = (-1);
			return ERROR;
		}
	}
}

/*
 * Respond to receiver's complaint, get back in sync with receiver
 */
getinsync()
{
	register int c;

	for (;;) {
#ifdef TESTATTN
		if (Testattn) {
			wr_modem("\r\n\n\n***** Signal Caught *****\r\n");
			Rxpos = 0; c = ZRPOS;
		} else
#endif

			c = zgethdr(Rxhdr, 0);
		switch (c) {
		case ZCAN:
		case ZABORT:
		case ZFIN:
		case TIMEOUT:
			return ERROR;
		case ZRPOS:
			if (Lastc >= 0 && Lastread == Rxpos) {
				Dontread = TRUE;
			} else {
				/* clearerr(in); */	/* In case file EOF seen */
				if(stfseek(in, Rxpos, 0))
				{
					fprintf(STDERR,"\r\nError While Seeking file\n");
					return ERROR;
				}

			}
			Txpos = Rxpos;
			return c;
		case ZACK:
			return c;
		case ZRINIT:
		case ZSKIP:
#ifndef REMOTE
			fprintf(STDERR,"\n\n");
#endif
			stfclose(in);
			in = (-1);
			return c;
		case ERROR:
		default:
			zsbhdr(ZNAK, Txhdr);
			continue;
		}
	}
}
/* Say "bibi" to the receiver, try to do it cleanly */
saybibi()
{
	for (;;) {
		stohdr(0L);
		zsbhdr(ZFIN, Txhdr);
		switch (zgethdr(Rxhdr, 0)) {
		case ZFIN:
			sendline('O'); sendline('O'); flush_modem();
		case ZCAN:
		case TIMEOUT:
			return;
		}
	}
}

/* Send command and related info */
zsendcmd(buf, blen)
char *buf;
{
	register int c, errors;
	long cmdnum;

/*	cmdnum = getpid(); */
	cmdnum = 1;	/* A random # */
	errors = 0;
	for (;;) {
		stohdr(cmdnum);
		Txhdr[ZF0] = Cmdack1;
		zsbhdr(ZCOMMAND, Txhdr);
		zsdata(buf, blen, ZCRCW);
listen:
		Rxtimeout = 100;		/* Ten second wait for resp. */
		c = zgethdr(Rxhdr, 1);

		switch (c) {
		case ZRINIT:
			continue;
		case ERROR:
		case TIMEOUT:
			if (++errors > Cmdtries)
				return ERROR;
			continue;
		case ZCAN:
		case ZABORT:
		case ZFIN:
		case ZSKIP:
		case ZRPOS:
			return ERROR;
		default:
			if (++errors > 10)
				return ERROR;
			continue;
		case ZCOMPL:
			Exitcode = Rxpos;
			saybibi();
			return OK;
		case ZRQINIT:
			vfile("******** RZ *******");
/*			stsystem("rz"); */
			vfile("******** SZ *******");
			goto listen;
		}
	}
}


#ifndef STANDALONE
/*
 * If called as sb use YMODEM protocol
 */
schkinvok(s)
char *s;
{
	if (s[0]=='s' && s[1]=='b') {
		Nozmodem = TRUE; Blklen=KSIZE;
	}
}
#endif

/* -eof- */
