/*
 *
 *  Rev 10-30-91
 *  This file contains Unix specific code for setting terminal modes,
 *  very little is specific to ZMODEM or YMODEM per se (that code is in
 *  sz.c and rz.c).  The CRC-16 routines used by XMODEM, YMODEM, and ZMODEM
 *  are also in this file, a fast table driven macro version
 *
 *	V7/BSD HACKERS:  SEE NOTES UNDER mode(2) !!!
 *
 *   This file is #included so the main file can set parameters such as HOWMANY.
 *   See the main files (rz.c/sz.c) for compile instructions.
 */

#ifdef V7
#include <sys/types.h>
#include <sys/stat.h>
#define STAT
#include <sgtty.h>
#define OS "V7/BSD"
#define ROPMODE "r"
#ifdef LLITOUT
long Locmode;		/* Saved "local mode" for 4.x BSD "new driver" */
long Locbit = LLITOUT;	/* Bit SUPPOSED to disable output translations */
#include <strings.h>
#endif
#endif

#ifndef OS
#ifndef USG
#define USG
#endif
#endif

#ifdef USG
#include <sys/types.h>
#include <sys/stat.h>
#define STAT
#include <termio.h>
#define OS "SYS III/V"
#define ROPMODE "r"
#define MODE2OK
#include <string.h>
#endif

#ifdef T6K
#include <sys/ioctl.h>		/* JPRadley: for the Tandy 6000 */
#endif

#include <setjmp.h>

#if HOWMANY  > 255
Howmany must be 255 or less
#endif

#ifdef linux
#include <stdlib.h>
#define signal(s,f) signal(s, (__sighandler_t) f)
#endif

 /*
 *  Some systems (Venix, Coherent, Regulus) may not support tty raw mode
 *  read(2) the same way as Unix. ONEREAD must be defined to force one
 *  character reads for these systems. Added 7-01-84 CAF
 */

#define fxsendline(c) putc(c, Ttystream),fflush(Ttystream)
#define fsendline(c) putc(c & 0377, Ttystream),fflush(Ttystream)
#define sendline(c) putc(c & 0377, Ttystream)
#define xsendline(c) putc(c, Ttystream)

FILE *Ttystream;
int Tty;
char linbuf[HOWMANY];
char xXbuf[BUFSIZ];
int Lleft=0;		/* number of characters in linbuf */
jmp_buf tohere;		/* For the interrupt on RX timeout */
#ifdef ONEREAD
/* Sorry, Regulus and some others don't work right in raw mode! */
int Readnum = 1;	/* Number of bytes to ask for in read() from modem */
#else
int Readnum = HOWMANY;	/* Number of bytes to ask for in read() from modem */
#endif
int Verbose=2;


struct {
	unsigned baudr;
	int speedcode;
} speeds[] = {
	110,	B110,
	300,	B300,
	600,	B600,
	1200,	B1200,
	2400,	B2400,
	4800,	B4800,
	9600,	B9600,
	19200,	EXTA,
	38400,	EXTB,
	0,
};

int Twostop;		/* Use two stop bits */


/*
 *  The following uses an external rdchk() routine if available,
 *  otherwise defines the function for BSD or fakes it for SYSV.
 */

#ifndef READCHECK
#ifdef FIONREAD
#define READCHECK
/*
 *  Return non 0 iff something to read from io descriptor f
 */
rdchk(f)
{
	static long lf;

	ioctl(f, FIONREAD, &lf);
	return ((int) lf);
}

#else		/* FIONREAD */

#ifdef SV
#define READCHECK
#include <fcntl.h>

int checked = 0;
/*
 * Nonblocking I/O is a bit different in System V, Release 2
 *  Note: this rdchk vsn throws away a byte, OK for ZMODEM
 *  sender because protocol design anticipates this problem.
 */
#define EATSIT
rdchk(f)
{
	int lf, savestat;
	static char bchecked;

	savestat = fcntl(f, F_GETFL) ;
	fcntl(f, F_SETFL, savestat | O_NDELAY) ;
	lf = read(f, &bchecked, 1) ;
	fcntl(f, F_SETFL, savestat) ;
	checked = bchecked & 0377;	/* force unsigned byte */
	return(lf) ;
}
#endif
#endif
#endif


static unsigned
getspeed(code)
{
	register n;

	for (n=0; speeds[n].baudr; ++n)
		if (speeds[n].speedcode == code)
			return speeds[n].baudr;
	return 38400;	/* Assume fifo if ioctl failed */
}



#ifdef ICANON
struct termio oldtty, tty;
#else
struct sgttyb oldtty, tty;
struct tchars oldtch, tch;
#endif

/*
 * mode(n)
 *  3: save old tty stat, set raw mode with flow control
 *  2: set XON/XOFF for sb/sz with ZMODEM or YMODEM-g
 *  1: save old tty stat, set raw mode 
 *  0: restore original tty mode
 */
mode(n)
{
	static did0 = FALSE;

	vfile("mode:%d", n);
	switch(n) {
#ifdef USG
	case 2:		/* Un-raw mode used by sz, sb when -g detected */
		if(!did0)
			(void) ioctl(Tty, TCGETA, &oldtty);
		tty = oldtty;

		tty.c_iflag = BRKINT|IXON;

		tty.c_oflag = 0;	/* Transparent output */

		tty.c_cflag &= ~(PARENB|HUPCL);		/* Disable parity */
		tty.c_cflag |= (CREAD|CS8);	/* Set character size = 8 */
		if (Twostop)
			tty.c_cflag |= CSTOPB;	/* Set two stop bits */


#ifdef READCHECK
		tty.c_lflag = Zmodem ? 0 : ISIG;
		tty.c_cc[VINTR] = Zmodem ? -1:030;	/* Interrupt char */
#else
		tty.c_lflag = ISIG;
		tty.c_cc[VINTR] = Zmodem ? 03:030;	/* Interrupt char */
#endif
		tty.c_cc[VQUIT] = -1;			/* Quit char */
#ifdef NFGVMIN
		tty.c_cc[VMIN] = 1;
#else
		tty.c_cc[VMIN] = 3;	 /* This many chars satisfies reads */
#endif
		tty.c_cc[VTIME] = 1;	/* or in this many tenths of seconds */

		(void) ioctl(Tty, TCSETAW, &tty);
		did0 = TRUE;
		return OK;
	case 1:
	case 3:
		if(!did0)
			(void) ioctl(Tty, TCGETA, &oldtty);
		tty = oldtty;

		tty.c_iflag = n==3 ? (IGNBRK|IXOFF) : IGNBRK;

		 /* No echo, crlf mapping, INTR, QUIT, delays, no erase/kill */
		tty.c_lflag &= ~(ECHO | ICANON | ISIG);

		tty.c_oflag = 0;	/* Transparent output */

		tty.c_cflag &= ~PARENB;	/* Same baud rate, disable parity */
		tty.c_cflag |= CS8;	/* Set character size = 8 */
		if (Twostop)
			tty.c_cflag |= CSTOPB;	/* Set two stop bits */
#ifdef NFGVMIN
		tty.c_cc[VMIN] = 1; /* This many chars satisfies reads */
#else
		tty.c_cc[VMIN] = HOWMANY; /* This many chars satisfies reads */
#endif
		tty.c_cc[VTIME] = 1;	/* or in this many tenths of seconds */
		(void) ioctl(Tty, TCSETAW, &tty);
		did0 = TRUE;
		Effbaud = Baudrate = getspeed(tty.c_cflag & CBAUD);
		return OK;
#endif
#ifdef V7
	/*
	 *  NOTE: this should transmit all 8 bits and at the same time
	 *   respond to XOFF/XON flow control.  If no FIONREAD or other
	 *   rdchk() alternative, also must respond to INTRRUPT char
	 *   This doesn't work with V7.  It should work with LLITOUT,
	 *   but LLITOUT was broken on the machine I tried it on.
	 */
	case 2:		/* Un-raw mode used by sz, sb when -g detected */
		if(!did0) {
			ioctl(Tty, TIOCEXCL, 0);
			ioctl(Tty, TIOCGETP, &oldtty);
			ioctl(Tty, TIOCGETC, &oldtch);
#ifdef LLITOUT
			ioctl(Tty, TIOCLGET, &Locmode);
#endif
		}
		tty = oldtty;
		tch = oldtch;
#ifdef READCHECK
		tch.t_intrc = Zmodem ? -1:030;	/* Interrupt char */
#else
		tch.t_intrc = Zmodem ? 03:030;	/* Interrupt char */
#endif
		tty.sg_flags |= (ODDP|EVENP|CBREAK);
		tty.sg_flags &= ~(ALLDELAY|CRMOD|ECHO|LCASE);
		ioctl(Tty, TIOCSETP, &tty);
		ioctl(Tty, TIOCSETC, &tch);
#ifdef LLITOUT
		ioctl(Tty, TIOCLBIS, &Locbit);
#endif
		bibi(99);	/* un-raw doesn't work w/o lit out */
		did0 = TRUE;
		return OK;
	case 1:
	case 3:
		if(!did0) {
			ioctl(Tty, TIOCEXCL, 0);
			ioctl(Tty, TIOCGETP, &oldtty);
			ioctl(Tty, TIOCGETC, &oldtch);
#ifdef LLITOUT
			ioctl(Tty, TIOCLGET, &Locmode);
#endif
		}
		tty = oldtty;
		tty.sg_flags |= (RAW|TANDEM);
		tty.sg_flags &= ~ECHO;
		ioctl(Tty, TIOCSETP, &tty);
		did0 = TRUE;
		Effbaud = Baudrate = getspeed(tty.sg_ospeed);
		return OK;
#endif
	case 0:
		if(!did0)
			return ERROR;
#ifdef USG
		(void) ioctl(Tty, TCSBRK, 1);	/* Wait for output to drain */
		(void) ioctl(Tty, TCFLSH, 1);	/* Flush input queue */
		(void) ioctl(Tty, TCSETAW, &oldtty);	/* Restore modes */
		(void) ioctl(Tty, TCXONC,1);	/* Restart output */
#endif
#ifdef V7
		ioctl(Tty, TIOCSETP, &oldtty);
		ioctl(Tty, TIOCSETC, &oldtch);
		ioctl(Tty, TIOCNXCL, 0);
#ifdef LLITOUT
		ioctl(Tty, TIOCLSET, &Locmode);
#endif
#endif

		return OK;
	default:
		return ERROR;
	}
}

sendbrk()
{
#ifdef V7
#ifdef TIOCSBRK
#define CANBREAK
	sleep(1);
	ioctl(Tty, TIOCSBRK, 0);
	sleep(1);
	ioctl(Tty, TIOCCBRK, 0);
#endif
#endif
#ifdef USG
#define CANBREAK
	ioctl(Tty, TCSBRK, 0);
#endif
}

/* Initialize tty device for serial file xfer */
inittty()
{
#ifdef oldLINUXold
	char *tty = (char *) getenv("RZSZLINE");
	if (!tty) {
		fprintf(stderr, "Warning: Missing environment variable "
				"'RZSZLINE' (Linux)\n");
	}
	Tty = open(tty ? tty : "/dev/tty", 2);
	if (Tty < 0) {
		perror(tty ? tty : "/dev/tty");  exit(2);
        }
#else
	Tty = 0 ; 
#endif	
	Ttystream = fdopen(Tty, "w");
#ifdef NOBF
	setvbuf(Ttystream, NULL, _IONBF, 0);
#else
	setbuf(Ttystream, xXbuf);		
#endif
	setvbuf(stderr, NULL, _IONBF, 0);
}

flushmoc()
{
	fflush(Ttystream);
}
flushmo()
{
	fflush(Ttystream);
}

/*
 * This version of readline is reasoably well suited for
 * reading many characters.
 *
 * timeout is in tenths of seconds
 */
#ifdef linux
void alrm ()
#else
alrm()
#endif
{
	longjmp(tohere, -1);
}
readline(timeout)
int timeout;
{
	register n;
	static char *cdq;	/* pointer for removing chars from linbuf */

	if (--Lleft >= 0) {
		if (Verbose > 8) {
			fprintf(stderr, "%02x ", *cdq&0377);
		}
		return (*cdq++ & 0377);
	}
	n = timeout/10;
	if (n < 2)
		n = 3;
	if (Verbose > 5)
		fprintf(stderr, "Calling read: alarm=%d  Readnum=%d ",
		  n, Readnum);
	if (setjmp(tohere)) {
#ifdef TIOCFLUSH
/*		ioctl(Tty, TIOCFLUSH, 0); */
#endif
		Lleft = 0;
		if (Verbose>1)
			fprintf(stderr, "Readline:TIMEOUT\n");
		return TIMEOUT;
	}
	signal(SIGALRM, alrm); alarm(n);
	errno = 0;
	Lleft=read(Tty, cdq=linbuf, Readnum);
	alarm(0);
	if (Verbose > 5) {
		fprintf(stderr, "Read returned %d bytes errno=%d\n",
		  Lleft, errno);
	}
	if (Lleft < 1)
		return TIMEOUT;
	--Lleft;
	if (Verbose > 8) {
		fprintf(stderr, "%02x ", *cdq&0377);
	}
	return (*cdq++ & 0377);
}



/*
 * Purge the modem input queue of all characters
 */
purgeline()
{
	Lleft = 0;
#ifdef USG
	ioctl(Tty, TCFLSH, 0);
#else
	lseek(Tty, 0L, 2);
#endif
}


/* send cancel string to get the other end to shut up */
canit()
{
	static char canistr[] = {
	 24,24,24,24,24,24,24,24,24,24,8,8,8,8,8,8,8,8,8,8,0
	};

	zmputs(canistr);
	Lleft=0;	/* Do read next time ... */
}

/*
 * Send a string to the modem, processing for \336 (sleep 1 sec)
 *   and \335 (break signal)
 */
zmputs(s)
char *s;
{
	register c;

	while (*s) {
		switch (c = *s++) {
		case '\336':
			sleep(1); continue;
		case '\335':
			sendbrk(); continue;
		default:
			sendline(c);
		}
	}
	flushmo();
}


/* VARARGS1 */
vfile(f, a, b, c, d)
char *f;
long a, b, c, d;
{
	if (Verbose > 2) {
		fprintf(stderr, f, a, b, c, d);
		fprintf(stderr, "\n");
	}
}


/* End of rbsb.c */
