#

/* talkline  -  interface for outgoing lines
 *	Written by J. Pershing  -  all rights reversed
 *	Redone by Noel Chiappa  MIT-LCS-CSR  21-Feb-80
 *	4.1 mods by Larry Allen MIT-LCS-CSC 12-July-83
 *	4.2 mods by Larry Allen	MIT-LCS-DCS 3-Jan-84
 *
 * The way this works is that there are two processes; the top-level one
 * reads from the keyboard and sends to the remote line, and the other reads
 * from the remote line and sends to the display. The top level one is
 * the one that gets the commands frm the user; it sends them to the
 * other over a one-way pipe; a signal is sent to say that a message
 * is in the pipe to be read. Since the two processes only block on
 * TTY reads, which an interrupt will break out of, they manage not
 * to get wedged.
 *
 * The code that handles stopping and restarting the two processes,
 * as well as things like handling interrupts, continuing, etc is quite
 * delicate. The input loop and routine are even more carefully
 * crafted. Don't play with this unless you understand EXACTLY what is
 * going on and how the global flags are used in each process.
 */


#include	<errno.h>
#include	<sgtty.h>
#include	<sys/types.h>
#include	<sys/time.h>
#include	<sys/stat.h>
#include	<stdio.h>
#include	<signal.h>
#include	<fcntl.h>


struct	sgttyb	OTTYMode;		/* Saved TTY modes */
struct	sgttyb	FOLMode;		/* and line modes */

struct	sgttyb	NTTYMode = {		/* New TTY mode */
				0,	/* IBAUD - set at init time */
				0,	/* OBAUD - " */
				0,	/* ERASEC - set from old mode */
				0,	/* KILLC - set from old mode */
				EVENP|ODDP|RAW, /* flags */
				};

struct	sgttyb	NFNLMode = {		/* New line, no foreign hold */
				0,	/* IBAUD - set at init time */
				0,	/* OBAUD - " */
				0,	/* ERASEC - set from old mode */
				0,	/* KILLC - set from old mode */
				EVENP|ODDP|RAW, /* flags */
				};

struct	sgttyb	FNLMode = {		/* New line with foreign hold */
				0,	/* IBAUD - set at init time */
				0,	/* OBAUD - " */
				0,	/* ERASEC - set from old mode */
				0,	/* KILLC - set from old mode */
				EVENP|ODDP|RAW|TANDEM, /* flags */
				};


int	BaudTab[] = {	9600, B9600,
			4800, B4800,
			2400, B2400,
			1800, B1800,
			1200, B1200,
			 600, B600,
			 300, B300,
			 200, B200,
			 150, B150,
			 134, B134,
			 110, B110,
			  75, B75,
			  50, B50,
			   0
			};


#define	FCHCL	3			/* First useless channel */
#define	NFCHAN	20			/* Max no of file channels */

#define	SIG	30			/* Signals */
#define	ACK	31
#define	HUP	1			/* Hangup signal */
#define	TRM	15			/* Software process termination */
#define	INT	2			/* Terminal quits */


#define	BUFSIZE	512


/* Channels */

int	fin;				/* Terminal - set in library */
int	fout;
int	LineIn;				/* Line we are going to talk to */
int	LineOut;
int	P[2];				/* Pipes */
int	readfds;			/* mask for select call */
int	TTYfstat;			/* file descriptor status of TTY */
int	Input;				/* My input and output (per process) */
int	Output;
int	IPipe;				/* Comm channel (only one used) */
int	OPipe;
FILE	*InFile;			/* Temp data source file */
int	InChan = -1;			/* Flag if reading from file */
FILE	*OutFile;			/* Transcription File */
int	OutChan = -1;			/* Flag if trn - fuck you too, stdio */
int	Locked = 0;			/* True iff we locked line */

/* Global state flags */

int	Quiescent;			/* Non-Zero if We're Stopped  */
int	Dead;				/* Non-Zero if We're Dead  */
int	Master;				/* Non-Zero if We're the Master  */
int	Him;				/* Process ID of the Other Guy  */
int	Pending;			/* Message Sent & Not Acknowledged  */
int	LitMode;			/* Non-Zero if We're in LitMode  */
int	NoCopy;				/* Non-Zero if not copying thru */


/* Parameters, static, etc */

int	InCount;			/* Number of chars read from TTY */
char	*InBufP;			/* Current location */
int	InBuf[BUFSIZE];			/* TTY input buffer */
int	InFileCount;			/* No of chars read so far from file */
int	InFileLimit = 512;		/* Number of chars to zap at once */
struct	sgttyb	*NLineMode = &NFNLMode;	/* Line md; dflt no foreign hold */
int	BaudRt = 9600;			/* Baud rate of subdevice */
int	BaudCd = B9600;			/* Code for ioctl */
char	Escape = '\036';		/* Escape Char to Watch For  */
char	Line[] = "/dev/tty--";
char	Lock[] = "/dev/ttylocked---";
char	Del[] = "\010 \010";
extern	int	errno;


/* Parse parameters and call all the subroutines that actually
 * do things.
 */

main(argc, argv)
char	*argv[];

{	register	char	Ch;
	register	int	i;
	int		Rcvr();			/* Catches Messages via SIG */
	int		Acknowledge();		/* Catches Acks - ACK */
	int		Intr();			/* Handler for Random Sigs */


	if ((argc < 2) || (argc > 5))
		ArgErr();

	for (i = 1; i < argc; i++) {
		Ch = argv[i][0];
		switch(Ch) {
			case '#':	Lock[15] = argv[i][1];
					Lock[16] = argv[i][2];
					Line[8] = argv[i][1];
					Line[9] = argv[i][2];
					break;

			case '+':	BaudCd = GetBaud(&argv[i][1]);
					if (BaudCd == 0)
						Error("Illegal Baud Rate");
					break;

			case '-':	NLineMode = &FNLMode;
					break;

			default:	if ((Ch >= '0') && (Ch <= '9'))
						InFileLimit = atoi(argv[i]);
					  else
						ArgErr();
			}
		}


	signal(TRM, 1);			/* Hack lines in peace */
	signal(HUP, 1);			/* Let's not die with lines locked */
	signal(INT, 1);

	for (i = FCHCL; i < NFCHAN; i++)
		close(i);		/* Get around shell bug with '.' */

	OpenLine();

	SetLine();
	SetTTY();

	signal(TRM, Intr);
	signal(HUP, Intr);
	signal(INT, Intr);


	signal(SIG, Rcvr);		/* Now to create servers */
	signal(ACK, Acknowledge);

	ForkSlave();
	PrS("\r\n");

	DoServer();

	if (Master) {
		RstrTTY();
		RstrLine();
		unlink(Lock);
		}
	return(0);
}


/* This is the main loop of both servers. Basically, it just reads
 * and writes characters using the tailored routines and checks to
 * make sure nothing special happens.
 */

DoServer()

{	register	char	Ch;

	for(;;) {
		Ch = ReadCh();
		if (Dead)
			break;

		if ((Ch == Escape) && Master && (InChan < 0)) {
				DoEscape();
				continue;
				}
		WriteCh(Ch);
		}
}


/* Read from Appropriate Input Source. The read call will return
 * with the funny value if the read call was interrupted
 * by a signal - i.e. communication occurred. If so, go back and
 * check to see if we've had a change of state.
 */

ReadCh()

{	register	char	Ch;

	if (InChan >= 0) {
		if (InFileCount >= InFileLimit) {
			sleep(1);
			InFileCount = 0;
			}

		InFileCount++;
		Ch = getc(InFile);
		if (!feof(InFile) && !ferror(InFile))
			return(Ch);
		fclose(InFile);
		InChan = -1;
		PrS("\r\ntalkline: EndOfFile on Input\r\n");
		}

	for (;;) {
		while (InCount <= 0) {
			while (Quiescent || Dead) {
				if (Dead)
					return(0);
				pause();
				}

			InCount = read(Input, InBuf, BUFSIZE);
			if (InCount > 0)
				InBufP = (char *)InBuf;
			else if (errno == EWOULDBLOCK)
				select(32, &readfds, 0, 0, 0);
			}

		Ch = (*InBufP++ & 0177);
		InCount--;
		if (Ch || LitMode)
			return(Ch);
		}
}


/* Write to Appropriate Output Sinks - the loop is becuase signal interrupts
 * may intervene.
 */

WriteCh(Ch)
char	Ch;

{	if (OutChan >= 0)
		putc(Ch, OutFile);
	if (Master || !NoCopy)
		while (write(Output, &Ch, 1) != 1);
	return(0);
}


/* Escape Handler */

DoEscape()

{	register	char	Ch;
	char			Buf[100];

	if (!Master) {
		PrS("\r\ntalkline: Escape in Slave\r\n");
		return(0);
		}

	Throw("stop");
	while(Pending)
		pause();
	PrS("\r\n\r\nEscape:");
	Ch = ReadCh();

	if (Ch == Escape) {
		PrS("\r\n\r\n");
		Throw("go");
		while(Pending)
			pause();
		WriteCh(Ch);
		return(0);
		}

	switch (Ch) {
	  case 'c': 	Throw("close");
			break;

	  case 'l':	Throw("litmode");
			break;

	  case 'm':	Throw("morecopy");
			break;

	  case 'n':	Throw("nocopy");
			break;

	  case 'p':	Throw("proceed");
			PrS("\r\n\r\n");
			RstrTTY();

			Quiescent++;
			kill(getpid(), SIGTSTP);
			Quiescent--;

			SetTTY();
			kill(Him, SIGCONT);
			break;

	  case 'q':	Throw("quit");
			Dead = 1;
			break;

	  case 'r':	PrS("\r\nInFile=");
			ReadFilName(Buf, &Buf[100]);
			if (InChan >= 0) {
				PrS("\r\ntalkline: Closing Prev InFile\r\n");
				fclose(InFile);
				}
			InFile = fopen(Buf, "r");
			if (InFile == NULL) {
				PrS("\r\ntalkline: Open Failed\r\n");
				InChan = -1;
				break;
				}
			InChan = fileno(InFile);
			break;

	  case 't':	Throw("throwout");
			break;

	  case 'w':	PrS("\r\nOutFile=");
			Buf[0] = 'w';
			ReadFilName(&Buf[1], &Buf[100]);
			Throw(Buf);
			break;

	  case 'b':	Throw("break");
			break;

	  case '?':	PrS("\r\nTalkLine Commands Are:");
			PrS("\r\nb - Send a BREAK character");
			PrS("\r\nc - Close current output file");
			PrS("\r\nl - Literal mode (dont ignore nulls)");
			PrS("\r\nm - Morecopy (continue printing)");
			PrS("\r\nn - Nocopy (suspend printing)");
			PrS("\r\np - Suspend talkline and return to shell");
			PrS("\r\nq - Quit");
			PrS("\r\nr - Read from given file");
			PrS("\r\nt - Throwout all null characters");
			PrS("\r\nw - Write output to given file");
			PrS("\r\n? - print this list");
			break;

	  default:	PrS("\r\nError - type '?' for help");
			break;
	}

	PrS("\r\n\r\n");
	if (!Dead)
		Throw("go");
	while (Pending)
		pause();
	return(0);
}


/* Read a file name */

ReadFilName(Buf, End)
register	char	*Buf, *End;

{	register	char	Ch;
	char			*Start;

	Start = Buf;
	while (((Ch = ReadCh()) != '\r\n') && (Ch != '\r')) {
		if(Ch == '\177') {
			if (Buf <= Start)
				continue;
			  else {
				PrS(Del);
				*--Buf = 0;
				}
			}
		  else {
			if (Buf >= End)
				continue;
			  else {
				*Buf++ = Ch;
				*Buf = 0;
				PrS(Buf - 1);
				}
			 }
		}
}


/* Handler for Text of Incoming Messages  */

Catch(Count, Str)
char			Count;
register	char	*Str;

{	register	int	i;
	int			cnt;

	if (Master) {
		PrS("\r\ntalkline: Catch in Master\r\n");
		return(0);
		}

	switch (*Str++) {

	  case 'c':	if (OutChan >= 0)
				fclose(OutFile);
			OutChan = -1;
		 	break;

	  case 'g':	Quiescent--;
			break;

	  case 'l':	LitMode = 1;
			break;

	  case 'm':	NoCopy = 0;
			break;

	  case 'n':	NoCopy = 1;
			break;

	  case 'p':	kill(getpid(), SIGTSTP);
			break;

	  case 'q':	Dead++;
			Quiescent--;
			break;

	  case 's':	Quiescent++;
			break;

	  case 't':	LitMode = 0;
			break;

	  case 'w':	if (OutChan >= 0) {
				PrS("\r\ntalkline: Closing Prev OutFile\r\n");
				fclose(OutFile);
				}
			OutFile = fopen(Str, "w");
			if (OutFile == NULL) {
				PrS("\r\ntalkline: Create Failed\r\n");
				OutChan = -1;
				break;
				}
			OutChan = fileno(OutFile);
			break;

	  case 'b':	cnt = (0500 * (9600 / BaudRt));	/* What a kludge... */
			ioctl(LineOut, TIOCSBRK, 0);
			for (i = 0; i < cnt; i++);
			ioctl(LineOut, TIOCCBRK, 0);
			break;

	  default:	PrS("\r\ntalkline: Bad Message in Catch\r\n");
			break;
	}

	return(0);
}


/* Called with message to be sent. The wait for Pending ensures that
 * we dont send him a second message until he has processes and reset
 * the interrupt for the first.
 */

Throw(Msg)
char	*Msg;

{	register	char	*P;
	char		Count;

	if (!Master) {
		PrS("\r\ntalkline: Throw in Slave\r\n");
		return(0);
		}

	Count = 0;
	for (P = Msg; *P++; Count++);

	while (Pending)
		pause();

	Pending = 1;
	write(OPipe, &Count, 1);
	write(OPipe, Msg, Count);
	kill(Him, SIG);
	return(0);
}


Rcvr()

{	char	Count;
	char	Msg[100];

	if (Master) {
		PrS("\r\ntalkline: Rcvr in Master\r\n");
		return(0);
		}

	read(IPipe, &Count, 1);
	read(IPipe, Msg, Count);
	Msg[Count] = 0;

	Catch(Count, Msg);

	signal(SIG, Rcvr);
	kill(Him, ACK);
}


Acknowledge()

{	if (!Master) {
		PrS("\r\ntalkline: Acknowledge in Slave\r\n");
		return(0);
		}

	signal(ACK, Acknowledge);
	Pending = 0;
}


/* Interrupt  -  Causes an Abort  */

Intr()

{	if (!Master) {
		PrS("Interrupt in Slave");
		return;
		}

	if (Quiescent)
		kill(Him, SIGCONT);

	if (!Dead) {
		Throw("quit");
		while (Pending)
			pause();
		}

	if (!Quiescent) {
		if (FOLMode.sg_ispeed)
			ioctl(LineOut, TIOCSETP, &FOLMode);
		if (OTTYMode.sg_ispeed)
			ioctl(fout, TIOCSETP, &OTTYMode);
			}

	if (Locked)
		unlink(Lock);
	exit(1);
}


/* Check to make sure that the line is available and the
 * lock and open it.
 */

OpenLine()

{	struct		stat	SBuf;
	int		chan;

	if (stat(Lock, &SBuf) >= 0)
		Error("Line is Already Locked");
	if ((chan = creat(Lock, 0)) < 0)
		Error("Cannot Lock the Line");
	close(chan);
	Locked = 1;
	if (stat(Line, &SBuf) < 0)
		Error("Line Doesn't Seem to Exist");
	if (SBuf.st_uid != 0)
		Error("Line Seems to be in Use by Someone");
	if ((LineIn = LineOut = open(Line, O_RDWR|O_APPEND, 0)) < 0)
		Error("Cannot Open Line");
}


/* Open the command pipe, fork off the slave process and
 * do all the I/O redirection.
 */

ForkSlave()

{	if (pipe(P) < 0)
		Error("Cannot Build Pipe");

	Him = getpid();
	Master = fork();
	if (Master == -1)
		Error("Cannot Fork");

	if (Master) {
		Input = fin;
		readfds = (1 << Input);
		Output = LineOut;
		OPipe = P[1];  close(P[0]);
		Him = Master;
		}
	  else  {
		Input = LineIn;
		readfds = (1 << Input);
		Output = fout;
		IPipe = P[0];  close(P[1]);
		}
}


/* Set up appropriate line characteristics - save old ones
 * first, then make copy into new ones (to get default parameters)
 * and install them.
 */

SetTTY()

{
	short	tpgrp;			/* terminal's process group */

	fin = fileno(stdin);
	fout = fileno(stdout);

	TTYfstat = fcntl(fin, F_GETFL, 0);
	if (fcntl(fin, F_SETFL, TTYfstat|FNDELAY) == -1)
		Error("Cannot Set TTY Ndelay mode");
	if (ioctl(fout, TIOCGETP, &OTTYMode) < 0)
		Error("Cannot Get TTY Mode");

	NTTYMode.sg_ispeed = OTTYMode.sg_ispeed;
	NTTYMode.sg_ospeed = OTTYMode.sg_ospeed;
	NTTYMode.sg_erase = OTTYMode.sg_erase;
	NTTYMode.sg_kill = OTTYMode.sg_kill;

	if (ioctl(fout, TIOCSETP, &NTTYMode) < 0)
		Error("Cannot Set TTY Mode");
}


SetLine()

{
	if (fcntl(LineIn, F_SETFL, FNDELAY) == -1)
		Error("Cannot Set Line Ndelay mode");
	if (ioctl(LineOut, TIOCGETP, &FOLMode) < 0)
		Error("Cannot Get Line Mode");

	NLineMode->sg_ispeed = BaudCd;
	NLineMode->sg_ospeed = BaudCd;
	NLineMode->sg_erase = FOLMode.sg_erase;
	NLineMode->sg_kill = FOLMode.sg_kill;

	if (ioctl(LineOut, TIOCSETP, NLineMode) < 0)
		Error("Cannot Set Line Mode");
}


/* RstrTTY and RstrLine replace the old line parameters.
 */

RstrTTY()

{	if (fcntl(fin, F_SETFL, TTYfstat) == -1)
		Error("Cannot restore TTY fd status");
	if (ioctl(fout, TIOCSETP, &OTTYMode) < 0)
		Error("Cannot Set TTY Mode");
}


RstrLine()

{	if (ioctl(LineOut, TIOCSETP, &FOLMode) < 0)
		Error("Cannot Set Line Mode");
}


/* getbaud returns baud number (a la stty) for string in a */

GetBaud(a)
char	*a;

{	int	*ptr;

	ptr = BaudTab;
	BaudRt = atoi(a);
	while(*ptr) {
		if (*ptr++ == BaudRt)
			return(*ptr);
		ptr++;
		}
	return(0);
}


/* Print a Message on the Console  */

PrS(Str)
char	*Str;

{	register	char	*P;

	for (P = Str; *P; P++)
		putchar(*P);
	fflush(stdout);
}


/* Fatal Error  -  Never Returns  */

Error(Str)
char *Str;

{	PrS("\r\ntalkline: ");
	PrS(Str);
	PrS("\r\n");
	Master++;
	Dead++;
	Intr();
}

ArgErr()

{	Error(">talkline [#x] [+baudrt] [charquantum] [-f]");
}
