/*	xcport.c -- modem interface routines for XC
	This file uses 4-character tabstops
*/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>
#include <termio.h>
#include <sys/ioctl.h>
#include <errno.h>
#include "xc.h"

/* define this if you need to send SIGUSR1/SIGUSR2 to
   handle an active getty process, or use ungetty.
*/
/*#define GETTY_HANDLER /**/

# if DIDO == 2			/* SCO Xenix 2.2 uses ungetty */
#	define UNGETTY "/usr/lib/uucp/ungetty"
#	define UG_NOTENAB	0
#	define UG_ENAB		1
#	define UG_FAIL		2
#	define LOCKDIR "/usr/spool/uucp"
#	define SIZEOFLOCKFILE sizeof(short)
	static	int	code, retcode, errflag;
#	ifndef GETTY_HANDLER
#		define GETTY_HANDLER
#	endif
# endif /*DIDO==2*/

# if DIDO == 3			/* SCO Xenix 2.3, SCO Unix */
#	include <utmp.h>
#	define LOCKDIR "/usr/spool/uucp"
#	ifndef ASCII_PID
#	 define ASCII_PID
#	 define PIDSIZE 10
#	endif
	static pid_t gettypid = -1;
# endif /*DIDO==3*/

# if DIDO == 4			/* System V Release 4 */
#	include <utmp.h>
#	include <sys/stat.h>
#	include <sys/mkdev.h>
#	define LOCKDIR "/var/spool/locks"
#	ifndef ASCII_PID
#	 define ASCII_PID
#	 define PIDSIZE 10
#	endif
	static pid_t gettypid = -1;
# endif /*DIDO==4*/

#ifndef SIZEOFLOCKFILE
#define SIZEOFLOCKFILE sizeof(int)
#endif

static pid_t pid;
int cbaud = B2400;			/* default bps */
short flowflag;				/* modem port i/o data mask */
static int mfd = -1;		/* modem port file descriptor */
static struct TERMIOS pmode;	/* modem device control string */
static char port[SM_BUFF],	/* modem port device file string */
		lckname[SM_BUFF];	/* lockfile string */
unsigned mrate ();
char protocol[] = "8N1";	/* default modem protocol */
extern int errno;

struct {
	char *proto;
	int  clear;
	int  set;
} prot_tbl[] = {
	"8N1",		~(CSIZE | PARENB | CSTOPB),	CS8,
	"7E2",		~(CSIZE | PARODD),			CS7 | PARENB | CSTOPB,
	"7O2",		~CSIZE,						CS7 | PARENB | PARODD | CSTOPB,
	NIL(char),	0,							0
};

struct {
	char	 *bps;
	unsigned rate;
	int		 cbaud;
} bps_tbl[] = {
	"300",	300,	B300,
	"600",	600,	B600,
	"1200",	1200,	B1200,
	"2400",	2400,	B2400,
	"4800",	4800,	B4800,
	"9600",	9600,	B9600,
#ifdef B19200
	"19200",19200,	B19200,
#endif
#ifdef B38400
	"38400",38400,	B38400,
	"57600",57600,	B50,
#endif
	"0",	0,		B0
};

#ifdef linux
extern short rtscts;
 
set_rtscts()
{
	if (rtscts)
		pmode.c_cflag |= CRTSCTS;
	else
		pmode.c_cflag &= ~CRTSCTS;
   
	if (mfd != -1)
		tcsetattr( mfd, TCSANOW, &pmode);
 }
#endif

void
xc_setflow(flow)
short flow;
{
	if (flow)
		pmode.c_iflag |= IXON | IXOFF | IXANY;
	else
		pmode.c_iflag &= ~(IXON | IXOFF | IXANY);
#ifdef linux
	tcsetattr(mfd, TCSANOW, &pmode);
#else	
	ioctl(mfd, TCSETAF, &pmode);
#endif	
}

/* get/set character size and parity on the port */
char *
xc_setproto(p)
char *p;
{
	register i;

	if (!p)
		return protocol;

	for (i=0; prot_tbl[i].proto; i++){
		if (!strcmp(p,prot_tbl[i].proto)){
			pmode.c_cflag &= prot_tbl[i].clear;
			pmode.c_cflag |= prot_tbl[i].set;
#ifdef linux
			tcsetattr( mfd, TCSANOW, &pmode);
#else						
			ioctl(mfd, TCSETAF, &pmode);
#endif			
			strcpy(protocol,p);
			return protocol;
		}
	}
	return NIL(char);
}

/* get/set port string */
char *
mport(s)
char *s;
{
	if (s && mfd == -1)
		if (strncmp("/dev/", s, 5))
			strcpy(port, "/dev/"),
			strcat(port, s);
		else
			strcpy(port, s);

	return(port);
}

/*	Get/set the bps of the modem port; set the terminal rate to correspond. */
unsigned
mrate(s)
char *s;
{
	register i;

	if (s){
		for (i=0; bps_tbl[i].cbaud; i++){
			if (!strcmp(s,bps_tbl[i].bps)){
				cbaud = bps_tbl[i].cbaud;
				pmode.c_cflag &= ~CBAUD;
				pmode.c_cflag |= cbaud;
#ifdef linux
				tcsetattr( mfd, TCSANOW, &pmode );
#else				
				ioctl(mfd, TCSETAF, &pmode);
#endif				
				return (bps_tbl[i].rate);
			}
		}
		return FAILURE;
	}

	for (i=0; bps_tbl[i].cbaud; i++)
		if ((pmode.c_cflag & CBAUD) == bps_tbl[i].cbaud)
			return (bps_tbl[i].rate);
		
	return FAILURE;
}

/*	The following routine is used to hang up the modem. This is accomplished
	by setting bps to 0. According to my documentation on termio, setting bps
	to zero will result in DTR not being asserted. This hangs up some (most?)
	modems. If not, the second part of the routine sends the Hayes modem
	"escape" and then a hangup command.
*/
hangup()
{
	S1("<< HANGUP >>");

#if DTR_DROPS_CARRIER
	pmode.c_cflag &= ~CBAUD;
	pmode.c_cflag |= B0;		/* set cbaud 0 (drop DTR) */
#ifdef linux
	tcsetattr(mfd,TCSANOW, &pmode);
#else		
	ioctl(mfd, TCSETAF, &pmode);
#endif
	sleep(1);					/* wait a second */
	pmode.c_cflag &= ~CBAUD;	/* reset bps */
	pmode.c_cflag |= cbaud;
#ifdef linux
	tcsetattr(mfd,TCSANOW, &pmode);
#else		
	ioctl(mfd, TCSETAF, &pmode);
#endif
#else /* use Hayes command */
	sleep(2);					/* Allow for "escape guard time" */
	send_string(ATTEN);			/* Send modem escape command */
	sleep(3);					/* More "escape guard time" */
	send_string(HANGUP);		/* Send hangup command */
#endif
	return SUCCESS;
}

#ifdef GETTY_HANDLER
# if DIDO >= 2
/*	suspend() sends signal to a running getty
			 sets:	gettypid, process number of running getty, if DIDO > 2
					retcode, exit value of 'ungetty', if DIDO = 2
	restart(): restarts getty if it had been running before
*/
#  if DIDO >= 3
static void
suspend()
{
	struct	utmp *t, *getutent();
	char buf[12];
	void endutent();

	strcpy(buf, strrchr(port, '/') +1);
	while ((t = getutent())){
		if (t->ut_type == LOGIN_PROCESS && (!strcmp(buf, t->ut_line))){
			gettypid = t->ut_pid;	/* get getty PID */
			if (kill(gettypid, SIGUSR1) == -1 && errno != EPERM)
				S1("Can't signal getty");
		}
	}
	endutent();
}

static void
restart()
{
	if (gettypid != -1)
		kill(gettypid, SIGUSR2);
}
#  endif /*DIDO>=3*/
#  if DIDO == 2
static void
suspend()
{
	code=errflag=pid=retcode=0;
	if ((pid = fork()) == 0){
		execl(UNGETTY, "ungetty", port, NIL(char));
		S1("ungetty exec error");
		exit(8);
		}
	while (((code = wait(&errflag)) != pid) && code != -1);
	switch ((errflag>>8) & 0xff){
	case UG_NOTENAB:	/* line acquired: not enabled */
		retcode = UG_NOTENAB;
		break;
	case UG_ENAB:	/* line acquired: need ungetty -r when done */
		retcode = UG_ENAB;
		break;
	case UG_FAIL:		/* could not acquire line */
	case 255:
		exit(8);
	}
}

static void
restart()
{
	code=errflag=pid=0;
	if(retcode == UG_ENAB){
		if ((pid = fork()) == 0){
			execl(UNGETTY, "ungetty", "-r", port, NIL(char));
			exit(8);
		}
	while (((code = wait(&errflag)) != pid) && code != -1)
		;
	}
}
#  endif /*DIDO==2*/
# endif /*DIDO>=2*/
#endif /*GETTY_HANDLER*/

/*	Attach standard input and output to the modem port. This only gets called
	after a fork by the child process, which then exec's a program that uses
	standard i/o for some data transfer protocol. (To put this here is actually
	a kludge, but I wanted to keep the modem-specific stuff in a black box.)
*/
void
mattach()
{
	dup2(mfd, 0);	/* close local stdin and connect to port */
	dup2(mfd, 1);	/* close local stdout and connect to port */

	close(mfd);		/* close the old port descriptor */
}

static void
alrm(junk)
int junk;
{ /* do nothing */
}

/*	Get a byte from the modem port within 'seconds' or return -1.
	All data read from the modem are input through this routine.
*/
readbyte(seconds)
unsigned seconds;
{
	static int count = 0;
	static char *p, rxbuf[SM_BUFF];
	unsigned alarm();

	if (count > 0){
		count--;
		return(*p++ & 0xff);
	}
	if (seconds){
		signal(SIGALRM, alrm);
		alarm(seconds);
	}
	if ((count = read(mfd, p = rxbuf, SM_BUFF)) < 1)
		return(-1);
	if (seconds)
		alarm(0);

	count--;
	return(*p++ & 0xff);
}

/*	Output a byte to the modem port.
	All data sent to the modem are output through this routine.
*/
void
sendbyte(ch)
int ch;
{
	char c = ch & 0xff;

	if(write(mfd, &c, 1)<0)
		S1("sendbyte: write error!");
}

void
send_string(s)
char *s;
{
	while (*s){
		sendbyte(*s++);
		/* msecs(35); 		/* season to taste ... */
	}
}

/* send a modem break */
xmitbrk()
{
	S1("<< BREAK >>");
#ifdef linux
	tcsendbreak( mfd, 0 ); 
#else	
	ioctl(mfd, TCSBRK, 0);
#endif
	return SUCCESS;
}

/*	lock_tty() returns FAILURE if the lock file exists (and XC will not run).

	unlock_tty() deletes the lock file.

	SCOXENIX 2.3 mods: Steve Manes
	Check for active LCK file and try to delete it

	SCOXENIX 2.2 mods: Jean-Pierre Radley
	As above, using 'ungetty'

	Tandy 6000 mods: Fred Buck
  	SVR4 mods: Larry Rosenman
*/

static
lock_tty()
{
#if (DIDO >= 2) || defined(linux)
	int lckfd;
	char *s, buf[12];
#ifdef ASCII_PID
	static char apid[PIDSIZE+2] = { '\0' };
#else
	pid = -1;
#endif

	strcpy(buf, strrchr(port, '/') +1);
	s = buf + strlen(buf) - 1;

#if DIDO == 2
	*s = toupper(*s);
#endif
#if DIDO >= 3
	*s = tolower(*s);
#endif

#if DIDO == 4
	struct stat stat_buf;
	if(stat(port,&stat_buf)==0){
		sprintf(lckname,"%s/LK.%03d.%03d.%03d",LOCKDIR,
			major(stat_buf.st_dev),
			major(stat_buf.st_rdev),
			minor(stat_buf.st_rdev));
	}
#else
	sprintf(lckname, "%s/LCK..%s", LOCKDIR, buf);
#endif  /*DIDO==4*/

	if (!checkLCK())	/* check LCK file */
		return FAILURE;	/* can't unlock it */
	
	if ((lckfd = creat(lckname, 0666)) < 0){
		sprintf(Msg,"Can't create '%s'", lckname);
		S;
		return FAILURE;
	}

#ifdef ASCII_PID
	sprintf(apid, "%*d\n", PIDSIZE, getpid());
	write(lckfd, apid, PIDSIZE+1);
#else
	pid = getpid();
	write(lckfd, (char *)&pid, SIZEOFLOCKFILE);
#endif

	close(lckfd);
#endif /*DIDO*/
	return SUCCESS;
}

void
unlock_tty()
{
	static char byettyxx[50], *byeptr;
	extern char *ttyname();

	sprintf(byettyxx,"BYE%s", strrchr(ttyname(mfd),'/')+1);
	byeptr = getenv(byettyxx);
	if (byeptr && *byeptr)
		S1("Sending BYE string to modem"),
		send_string("\r"),
		send_string(byeptr),
		send_string("\r");

	pmode.c_cflag &= ~CLOCAL;
	pmode.c_cflag |= B0 | HUPCL;
#ifdef linux
	tcsetattr(mfd, TCSANOW, &pmode);
#else	
	ioctl(mfd, TCSETAF, &pmode);
#endif	
	close(mfd);

#if DIDO >= 2
	setuid(geteuid());
	setgid(getegid());
	unlink(lckname);
# ifdef GETTY_HANDLER
	restart();
# endif
#endif
#ifdef linux
	unlink(lckname);
#endif	
	S2("Exiting XC");
}

/*	check to see if lock file exists and is still active.
	kill(pid, 0) only works on ATTSV, some BSDs and Xenix
	returns: SUCCESS, or
			FAILURE if lock file active
	added: Steve Manes 7/29/88
*/
checkLCK()
{
	int rc, fd;
#ifdef ASCII_PID
	char alckpid[PIDSIZE+2];
#endif
#if DIDO == 2
	short lckpid = -1;
#else
	pid_t lckpid = -1;
#endif

	if ((fd = open(lckname, O_RDONLY)) == -1){
		if (errno == ENOENT)
			return SUCCESS;	/* lock file doesn't exist */
		goto unlock;
	}
#ifdef ASCII_PID
	rc = read(fd, (char *)alckpid, PIDSIZE+1);
	close(fd);
	lckpid = atoi(alckpid);
	if (rc != 11)
#else
	rc = read(fd, (char *)&lckpid, SIZEOFLOCKFILE);
	close(fd);
	if (rc != SIZEOFLOCKFILE)
#endif
	{
		S1("Lock file has bad format");
		goto unlock;
	}

	/* now, send a bogus 'kill' and check the results */
	if (kill(lckpid, 0) == 0 || errno == EPERM){
		sprintf(Msg,"Lock file process %d on %s is still active - try later",
			lckpid, port);
		S;
		return FAILURE;
	}

unlock:
	if (unlink(lckname) != 0){
		sprintf(Msg,"Can't unlink %s file", lckname);
		S;
		return FAILURE;
	}
	return SUCCESS;
}

/*	Opens the modem port and configures it. If the port string is
	already defined it will use that as the modem port; otherwise it
	gets the environment variable MODEM. Returns SUCCESS or FAILURE.
*/
mopen()
{
	int c;
	char *p;

	if (port[0] == '\0'){
		if (!(p = getenv("MODEM"))){
			S2("Exiting: no modem port specified or present in environment");
			exit(3);
		}
		mport(p);
	}
	if (!lock_tty())
		exit(4);

#if DIDO
	p = port +strlen(port) -1;
	*p = toupper(*p);
# ifdef GETTY_HANDLER
	suspend();
# endif
#endif

	if ((mfd = open(port, O_RDWR | O_NDELAY)) < 0){
		sprintf(Msg,"Can't open modem port %s",port);
		S;
		exit(5);
	}
#ifdef linux
	tcgetattr( mfd, &pmode);
#else	
	ioctl(mfd, TCGETA, &pmode);
#endif
	pmode.c_cflag &= ~(CBAUD | HUPCL);
	pmode.c_cflag |= CLOCAL | cbaud;
#if DIDO >= 3 & defined(CTSFLOW) & defined(RTSFLOW)
	pmode.c_cflag |= CTSFLOW | RTSFLOW ;
	/* pmode.c_cflag |= CRTSFL ; */
#endif
	pmode.c_iflag = IGNBRK ;
	pmode.c_oflag = pmode.c_lflag = 0;
	pmode.c_cc[VMIN] = 1; 	/* This many chars satisfies reads */
	pmode.c_cc[VTIME] = 0;	/* or in this many tenths of seconds */

	xc_setflow(flowflag);

	c = mfd;
	if ((mfd = open(port, O_RDWR)) < 0){	/* Reopen line with CLOCAL */
		sprintf(Msg,"Can't re-open modem port %s",port);
		S;
		return FAILURE;
	}
	close(c);

	return SUCCESS;
}
