/*% cc -g -DNOCOPYTX -DMANYREGS -DZSTAB -DUNIX -DDIAGS %  -o isz; size isz;
 ****************************************************************************
 *
 * isz.c By Chuck Forsberg,	 Omen Technology INC
 *	Copyright 2004 2011 Omen Technology INC All Rights Reserved
 *	   This is UNPUBLISHED PROPRIETARY SOURCE CODE of Omen Technology INC.
 *	   The Copyright notice above does not evidence any actual or intended
 *	   publication such source code.
 *	Entry: see sample main() in code below
 *
 *	External functions called:
 *
 * sendline(c)	sends a character to host
 *
 * purgeline()	empty receive queue
 * int readline(tenths) read char from host, timeout in seconds/10
		return received byte 0 to 0377
		negative return values indicate error:
				return -1 on recoverable error
				return -2 on timeout
		trap fatal errors (loss of carrier, etc.), close file
 * checkline() return non 0 if readline has 1 or more bytes ready
 *
 * zfilbuf() fill buffer with blklen bytes, set Eofseen if EOT, return count
 *		(See #ifdef NOCOPYTX for alternate version)
 * zseek(whence) clear EOF, errors, seek
 * zclose(status) close the input dataset associated with zfilbuf() et al.
 *		N.B: zclose might be called after the file is closed.
 *
 * When trapping on errors, close file, call canit() unless line dropped
 *
 ****************************************************************************
 */

#ifndef IZOK
#include "izm.h"
#endif


#define LOGFILE "/tmp/szlog"
int Verbose=0;
int Fullpath=0;

#ifdef NOCOPYTX
#ifdef KKSIZE
char privsecbuf[KKSIZE];
#else
char privsecbuf[1025];
#endif
#endif



int Zmodem=0;			/* ZMODEM protocol requested by receiver */
unsigned Txwindow=0;	/* Control the size of the transmitted window if non-zero */
unsigned Txwspac;		/* Spacing between zcrcq requests */
unsigned Txwcnt;		/* Counter used to space ack requests */
long vpos = 0;			/* Number of bytes read from file */
long Lrxpos;			/* Receiver's last reported offset */
#ifdef ADDRLE
int Lztrans;
#endif

int wctxpn(), wcs(), getnak(), zsendfile(), getzrxinit(), zsendfdata();
int getinsync(), zfilbuf(), zseek(), zfilbuf(), checkline();
void zclose(int);

#include "icrctab.c"

int Lastrx;
char Lzconv;	/* Local ZMODEM file conversion request */
#ifdef KKSIZE
int blklen=KKSIZE;		/* length of transmitted records */
#else
int blklen=1024;		/* length of transmitted records */
#endif
int Eofseen;			/* EOF seen on input set by zfilbuf */
unsigned Rxbuflen = 16384;		/* Receiver's max buffer length */
unsigned Tframlen = 0;	/* Override for tx frame length */
int Rxflags = 0;
long Lastsync;			/* Last offset to which we got a ZRPOS */
int Beenhereb4;			/* How many times we've been ZRPOS'd same place */
int Zctlesc;	/* Encode control characters */
int Zrwindow = 1400;	/* RX window size (controls garbage count) */
int Rxtimeout = 600;
void saybibi(), startz();

int Exitcode;
int errcnt=0;			/* number of files unreadable */
#ifdef UNIX
#include "rbunix.c"		/* most of the system dependent stuff here */
#endif

#include "izm.c"
#ifdef ADDRLE
#include "izmr.c"
#endif


#ifdef UNIX
FILE *in;

/* called by signal interrupt or terminate to clean things up */
void
bibi(n)
{
		canit(); mode(0);
		fprintf(stderr, "sz: caught signal %d; exiting\n", n);
		if (n == SIGQUIT)
				abort();
		if (n == 99)
				fprintf(stderr, "mode(2) in rbsb.c not implemented!!\n");
		exit(128+n);
}

main(argc, argv)
char *argv[];
{
		register char *cp;
		register npats;
		int dm;
		char **patts;

		if (cp = getenv("VERBOSE"))
				Verbose = atoi(cp);

		npats=0;
		if (argc<2)
				usage();
		while (--argc) {
				cp = *++argv;
				if (*cp++ == '-' && *cp) {
						while ( *cp) {
								switch(*cp++) {
								case 'f':
										++Fullpath; break;
								case 'l':
										if (--argc < 1)
												usage();
										Tframlen = atol(*++argv);
										if (Tframlen<32 || Tframlen>1024)
												usage();
										break;
								case 'r':
										Lzconv = ZCRESUM;
								case 'v':
										++Verbose; break;
								case 'w':
										if (--argc < 1)
												usage();
										Txwindow = atoi(*++argv);
										if (Txwindow < 256)
												Txwindow = 256;
										Txwindow = (Txwindow/64) * 64;
										Txwspac = Txwindow/4;
										if (blklen > Txwspac)
												blklen = Txwspac;
										break;
#ifdef ADDRLE
								case 'Z':
								case 'z':
										Lztrans = ZTRLE;  break;
#endif
								default:
										usage();
								}
						}
				}
				else if ( !npats && argc>0) {
						if (argv[0][0]) {
								npats=argc;
								patts=argv;
						}
				}
		}
		if (npats < 1)
				usage();
		if (Verbose) {
				if (freopen(LOGFILE, "w", stderr)==NULL) {
						printf("Can't open log file %s\n",LOGFILE);
						exit(0200);
				}
				setbuf(stderr, NULL);
		}

		mode(1);

		signal(SIGKILL, bibi);
		signal(SIGINT, SIG_IGN);
		signal(SIGQUIT, SIG_IGN);
		signal(SIGTERM, bibi);

		startz();

		if (wcsend(npats, patts)==IZERROR) {
				Exitcode=0200;
				canit();
		}
		mode(0);
		dm = ((errcnt != 0) | Exitcode);
#ifdef DIAGS
		vfile("isz finished with transfers dm=%x", dm);
#endif
		if (dm) {
				exit(dm);
		}
		exit(0);
		/*NOTREACHED*/
}

wcsend(argc, argp)
char *argp[];
{
		register n;

		bytcnt = -1;
		for (n=0; n<argc; ++n) {
				if (wcs(argp[n])==IZERROR)
						return IZERROR;
		}
		saybibi();
		return IZOK;
}
#endif /* UNIX */


#ifdef SAMPLE
main()
{
		startz();				/* Initialize receiver */

		/*
		 *	To ask receiver for crash rocovery, set
		 *				Lzconv = ZCRESUM;
		 *
		 * call wcs() once for each file.
		 *	Initialize zfilbuf before calling wcs()
		 *	The string is the file name, null terminated
		 *	  file length is not sent
		 */
		if (wcs("file.ext"))
				return 1;

		saybibi();				/* Orderly conclusion of ZMODEM session */

		return 0;
		
}
#endif



void
startz()
{
#ifdef BOTH
		Rxtimeout = 600;
#endif
		Zmodem = 0;
#ifdef LDP
		Zldpactive = 0;
		Zcrcpreset = 0xFFFFFFFFL;		/* CRC preset from filename header */
#endif
		mputs("rz\r"); 
		stohdr(0L);
		zshhdr(ZRQINIT, Txhdr);
}

#ifdef UNIX
struct stat filestat;
#endif

int
wcs(name)
char *name;
{
		int ret;

#ifdef UNIX
#ifdef NOCOPYTX
		secbuf = privsecbuf;
#endif
		if ((in=fopen(name, "r"))==NULL) {
				++errcnt;
				return IZOK;	/* pass over it, there may be others */
		}
		fstat(fileno(in), &filestat);
#endif
		Eofseen = 0;  vpos = 0;

		ret =  wctxpn(name);
#ifdef DIAGS
		vfile("wctxpn returned %d", ret);
#endif
		switch (ret) {
		case ZSKIP:
		case ZFERR:
				return IZOK;
		case IZOK:
				break;
		default:
				return IZERROR;
		}
		return 0;
}

/*
 * generate and transmit pathname block consisting of
 *	pathname (null terminated),
 */
wctxpn(name)
char *name;
{
		register char *p, *q;
		int n;

		if ( !Zmodem)
				if (getnak())
						return IZERROR;
		Zmodem = 1;

		for (p=name, q=secbuf ; *p; ) {
				switch(*q++ = *p++) {
				case '/':
						if (!Fullpath)
								q = secbuf;
				};
		}
		*q++ = 0;
#ifdef UNIX
		sprintf(q, "%lu %lo", filestat.st_size, filestat.st_mtime);
		q += strlen(q);
#else
#ifdef HAVE_STAT
		q += insertStat(q);
#endif
#endif
		*q++ = 0;
		*q++ = 0;
		n = q-secbuf;
		return zsendfile(secbuf, n);
}

getnak()
{
		register firstch;

#ifdef DIAGS
		vfile("entering getnak");
#endif
		Lastrx = 0;
		for (;;) {
				switch (firstch = readline(800)) {
				case ZPAD:
						if (getzrxinit())
								return IZERROR;
						return FALSE;
				case TIMEOUT:
						return TRUE;
				case CAN:
						if ((firstch = readline(20)) == CAN && Lastrx == CAN)
								return IZERROR;
				default:
						break;
				}
				Lastrx = firstch;
		}
}


#ifdef UNIX
void
zclose(status)
{
#ifdef DIAGS
		vfile("zclose: status=%d", status);
#endif
		if (in) {
				fclose(in);
#ifdef DIAGS
				vfile("fclose returned");
#endif
		} else {
#ifdef DIAGS
		vfile("file already closed!!");
#endif
		}

		in = 0;
}

int
zseek(n)
long n;
{
		clearerr(in);	/* Clear EOF */
		return (fseek(in, n, 0));
}

/* Fill buffer with blklen chars */
int
zfilbuf()
{
		size_t n;

#ifdef NOCOPYTX
		n = fread(secbuf=privsecbuf, 1, blklen, in);
#else
		n = fread(secbuf, 1, blklen, in);
#endif
		if (n < blklen)
				Eofseen = 1;
		return n;
}


usage()
{
		char **pp;

		fprintf(stderr, "Usage: isz [-l blklen] [-f] [-r] [-v] [-w window] file ...\n");
		exit(0);
}
#endif



/*
 * Get the receiver's init parameters
 */
getzrxinit()
{
		register triesleft;

		for (triesleft=10; --triesleft>=0; ) {
				
				switch (zgethdr()) {
				case ZCHALLENGE:		/* Echo receiver's challenge numbr */
						stohdr(Rxpos);
						zshhdr(ZACK, Txhdr);
						continue;
				case ZRINIT:
#ifdef LDP
						/* LDP protection active */
						Zldpactive = Rxhdr[ZF1] & CANLDP;
#ifdef DIAGS
						vfile("Zldpactive=%d", Zldpactive);
#endif
#endif
						Rxflags = 0377 & Rxhdr[ZF0];
						Txfcs32 = (Rxflags & CANFC32)?1:0;
						Zctlesc |= Rxflags & TESCCTL;
						Rxbuflen = (0377 & Rxhdr[ZP0])+((0377 & Rxhdr[ZP1])<<8);
						if ( !(Rxflags & CANFDX))
								Txwindow = 0;
#ifdef DIAGS
						vfile("Rxbuflen=%d Tframlen=%u", Rxbuflen, Tframlen);
#endif
						/* Override to force shorter frame length */
						if (Rxbuflen && (Rxbuflen>Tframlen) && (Tframlen>=32))
								Rxbuflen = Tframlen;
						if ( !Rxbuflen && (Tframlen>=32) && (Tframlen<=1024))
								Rxbuflen = Tframlen;
#ifdef DIAGS
						vfile("Rxbuflen=%d", Rxbuflen);
#endif

						/* Set initial subpacket length */
						if (Rxbuflen && ((unsigned)blklen)>Rxbuflen)
								blklen = Rxbuflen;
#ifdef DIAGS
						vfile("Rxbuflen=%d blklen=%d", Rxbuflen, blklen);
						vfile("Txwindow = %u Txwspac = %d", Txwindow, Txwspac);
#endif
#ifdef ADDRLE
						if (Lztrans == ZTRLE && (Rxflags & CANRLE))
								Txfcs32 = 2;
						else
								Lztrans = 0;
#endif
#ifdef DIAGS
						vfile("Rxflags=%X Txfcs32=%d", Rxflags, Txfcs32);
#endif

						return IZOK;
				case ZCAN:
				case TIMEOUT:
						return IZERROR;
				default:
						zshhdr(ZNAK, Txhdr);
						continue;
				}
		}
		return IZERROR;
}


/* Send file name and related info */
zsendfile(buf, blen)
char *buf;
{
		register c;
#ifdef LDP
		long crc;
		char *p;
#endif

		for (;;) {
				stohdr(0L);
				Txhdr[ZF0] = Lzconv;	/* file conversion request */
#ifdef LDP
				Zcrcpreset = crc = 0xFFFFFFFFL;
				bytcnt = 0;
				if (Zldpactive)
						Txhdr[ZF3] |= ZSLDP;
#ifdef KKSIZE
				Txhdr[ZF3] |= ZSRWKK;
#endif
#endif
				zsbhdr(ZFILE, Txhdr);
				zsdata(buf, blen, ZCRCW);
#ifdef DIAGS
				vfile("zsendfile: %d %s", blen, buf);
#endif
//		flushmo(); exit(0); // ----------------------
#ifdef LDP
				if (Zldpactive) {
						for (p=buf, c=blen; --c >=0; ++p) {
								crc = UPDC32(*p, crc);
#ifdef DIAGS
								vfile("crc=%lX", crc);
#endif
						}
						for (p = zcpyrldp; *p; ++p)
								crc = UPDC32(*p, crc);
						Zcrcpreset = crc;
#ifdef DIAGS
						vfile("Zcrcpreset=%lX", Zcrcpreset);
#endif
				}
#endif
again:
				c = zgethdr();
				switch (c) {
				case ZRINIT:
#ifdef DIAGS
				vfile("spurious ZRINIT 1");
#endif
						continue;
				case IZERROR:
						continue;
				default:
				case ZCAN:
				case TIMEOUT:
				case ZABORT:
				case ZFIN:
						return IZERROR;
				case ZSKIP:
#ifdef DIAGS
						vfile("1zskip");
#endif
						zclose(IZERROR); return c;
				case ZRPOS:
						/*
						 * Suppress zcrcw request otherwise triggered by
						 * lastyunc==bytcnt
						 */
						if (Rxpos && zseek(Rxpos))
								return IZERROR;
						Lastsync = (bytcnt = Txpos = Rxpos) -1;
						return zsendfdata();
				}
		}
}

#ifdef UNIX
char* pknames[] = {"CRC-16", "CRC-32", "RLE", ""};
#endif
/* Send the data in the file */
zsendfdata()
{
		register c, e, n;
		register newcnt;
		int junkcount;			/* Counts garbage chars received by TX */

		Lrxpos = 0;
		junkcount = 0;
		Beenhereb4 = FALSE;
		goto somemore;

waitack:
		junkcount = 0;
		c = getinsync(0);
gotack:
#ifdef DIAGS
		vfile("gotack: c=%d", c);
#endif
		switch (c) {
		default:
		case ZCAN:
#ifdef DIAGS
				vfile("zcan");
#endif
				zclose(IZERROR);
				return IZERROR;
		case ZSKIP:
#ifdef DIAGS
				vfile("2zskip");
#endif
				zclose(IZERROR);
				return c;
		case ZACK:
		case ZRPOS:
				break;
		case ZRINIT:
#ifdef DIAGS
				vfile("spurious ZRINIT 2");
#endif
				zclose(IZERROR);
				return IZOK;
		}
		while (checkline()) {
				switch (readline(1))
				{
				case XOFF:				/* Wait a while for an XON */
				case XOFF|0200:
						readline(100); continue;
				case CAN:
				case ZPAD:
						c = getinsync(1);
						goto gotack;
				}
		}

somemore:
		newcnt = Rxbuflen;
		Txwcnt = 0;
		e = ZCRCE;


		do {
				if (e == ZCRCE) {
						stohdr(Txpos);
						zsbhdr(ZDATA, Txhdr);
				}
				n = zfilbuf();
				if (Eofseen)
						e = ZCRCE;
				else if (junkcount > 3)
						e = ZCRCW;
				else if (bytcnt == Lastsync)
						e = ZCRCW;
				else if (Rxbuflen && (newcnt -= n) <= 0)
						e = ZCRCW;
				else if (Txwindow) {
#ifdef UNIX
						if (Verbose>1)
								fprintf(stderr, "Txwcnt=%lX\n", Txwcnt);
#endif
						if ((Txwcnt += n) >= Txwspac) {
								Txwcnt = 0;	 e = ZCRCQ;
						}
				}
				else
						e = ZCRCG;
#ifdef UNIX
				if (Verbose>1)
						fprintf(stderr, "%7lX ZMODEM %s\n", Txpos, pknames[Crc32t&3]);
#endif
				zsdata(secbuf, n, e);
				bytcnt = Txpos += n;
				if (e == ZCRCW) {
						for (n = 20; --n >=0;)
								sendline(021);
						flushmo();
						goto waitack;
				}
				while (checkline()) {
						switch (readline(1))
						{
						case CAN:
						case ZPAD:
								c = getinsync(1);
								if (c == ZACK)
										break;
#ifdef TCFLSH
								ioctl(wrfd, TCFLSH, 1);
#endif
								/* zcrce - dinna wanna starta ping-pong game */
								zsdata(secbuf, 0, ZCRCE);
								goto gotack;
						case XOFF:				/* Wait a while for an XON */
						case XOFF|0200:
								readline(100);
						case TIMEOUT:
#ifdef UNIX
								vfile("junktest: TIMEOUT");
#endif
								goto nojunk;
						default:
								++junkcount;
						}
				}
nojunk:
				if (Txwindow) {
						while (((unsigned)(Txpos - Lrxpos)) >= Txwindow) {
								if (e != ZCRCQ)
										zsdata(secbuf, 0, e = ZCRCQ);
								c = getinsync(1);
								if (c != ZACK) {
#ifdef TCFLSH
										ioctl(wrfd, TCFLSH, 1);
#endif
										zsdata(secbuf, 0, ZCRCE);
										goto gotack;
								}
						}
				}
		} while (!Eofseen);

		stohdr(Txpos);
		zshhdr(ZEOF, Txhdr);
		for (;;) {
				switch (getinsync(0)) {
				case ZACK:
						continue;
				case ZRPOS:
						goto somemore;
				case ZRINIT:
#ifdef DIAGS
						vfile("ZRINIT on eof");
#endif

#ifdef LDP
#ifdef DIAGS
						vfile("ZGOTFILEOK=%d", Rxhdr[ZF2]);
#endif
						if ((!Zldpactive) || (Rxhdr[ZF2] & ZGOTFILEOK)) {  /* Last file received and closed */
								zclose(IZOK);
								return IZOK;
						} else {
								zclose(IZERROR);
								return IZERROR;
						}
#else
						zclose(IZOK);
						return IZOK;
#endif

				case ZSKIP:
#ifdef DIAGS
						vfile("3zskip");
#endif
						zclose(IZERROR);
						return c;
				default:
#ifdef DIAGS
						vfile("DEFAULT on eof");
#endif
						zclose(IZERROR);
						return IZERROR;
				}
		}
}

/*
 * Respond to receiver's complaint, get back in sync with receiver
 */
getinsync(flag)
{
		register c;

		for (;;) {
				c = zgethdr();
				switch (c) {
				case ZCAN:
				case ZABORT:
				case ZFIN:
				case TIMEOUT:
						return IZERROR;
				case ZRPOS:
#if 1
refoo:					c = readline(1);
						switch (c) {
						case '*':
								continue;
						case 015:  case 012: case 021: case 0221:
								goto refoo;
						default:
								break;
						}
#endif
						/* ************************************* */
						/*	If sending to a buffered modem, you	 */
						/*	 might send a break at this point to */
						/*	 dump the modem's buffer.			 */
						if (zseek(Rxpos))
								return IZERROR;
						Eofseen = 0;
						bytcnt = Lrxpos = Txpos = Rxpos;
						if (Lastsync == Rxpos) {
								if (++Beenhereb4 > 4)
										if (blklen > 64)
												blklen /= 2;
						}
						Lastsync = Rxpos;
						stohdr(Txpos);			// CAF 9-7-2011
						return ZRPOS;
				case ZACK:
						Lrxpos = Rxpos;
						if (flag || Txpos == Rxpos)
								return ZACK;
						continue;
				case ZRINIT:
#ifdef DIAGS
						vfile("zrinit2");
#endif
						return c;
				case ZSKIP:
#ifdef DIAGS
						vfile("4zskip");
#endif
						return c;
				case IZERROR:
				default:
						zshhdr(ZNAK, Txhdr);
						continue;
				}
		}
}


/* Say "bibi" to the receiver, try to do it cleanly */
void
saybibi()
{
		for (;;) {
				stohdr(0L);				/* CAF Was zsbhdr - minor change */
				zshhdr(ZFIN, Txhdr);	/*	to make debugging easier */
				switch (zgethdr()) {
				case ZFIN:
						sendline('O'); sendline('O');
#ifdef FLUSH
						flushmo();
#endif
				/*FALLTHRU*/
				case ZCAN:
				case TIMEOUT:
						return;
				}
		}
}



/* End of isz.c */
