/*
 * Copyright (c) 1988, 1990 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted provided
 * that: (1) source distributions retain this entire copyright notice and
 * comment, and (2) distributions including binaries display the following
 * acknowledgement:  ``This product includes software developed by the
 * University of California, Berkeley and its contributors'' in the
 * documentation or other materials provided with the distribution and in
 * all advertising materials mentioning features or use of this software.
 * Neither the name of the University nor the names of its contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifndef lint
static char sccsid[] = "@(#)sys_bsd.c	5.1 (Berkeley) 9/14/90";
#endif /* not lint */

/*
 * The following routines try to encapsulate what is system dependent
 * (at least between 4.x and dos) which is used in telnet.c.
 */


#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <string.h>
#include <sys/socket.h>
#ifdef AMI_TCP
#include <bsdsocket.h>
#else
#include <ss/socket.h>
#endif
#include <signal.h>
#include <errno.h>
#include <arpa/telnet.h>
#ifdef __SASC
#include <ios1.h>
#endif
#include "ring.h"

#include "fdset.h"

#include "defines.h"
#include "externs.h"
#include "types.h"

#define	SIG_FUNC_RET	int

int
	tout,			/* Output file descriptor */
	tin,			/* Input file descriptor */
	net;

#ifndef AMI_TCP
struct	tchars otc = { 0 }, ntc = { 0 };
struct	sgttyb ottyb = { 0 }, nttyb = { 0 };
#endif
int	olmode = 0;
# define cfgetispeed(ptr)	(ptr)->sg_ispeed
# define cfgetospeed(ptr)	(ptr)->sg_ospeed
# define old_tc ottyb

static fd_set ibits, obits, xbits;

//void (*signal())()
//{	return;
//}

init_sys()
{
    tout = fileno(stdout);
    tin = fileno(stdin);
    FD_ZERO(&ibits);
    FD_ZERO(&obits);
    FD_ZERO(&xbits);

    errno = 0;
}


TerminalWrite(buf, n)
char	*buf;
int	n;
{
    return write(tout, buf, n);
}

TerminalRead(buf, n)
char	*buf;
int	n;
{
    return read(tin, buf, n);
}

/*
 *
 */

int
TerminalAutoFlush()
{
    return 1;
}


/*
 * Flush output to the terminal
 */
 
void
TerminalFlushOutput()
{
	fflush(stdout);
}

void
TerminalSaveState()
{
return;
}

cc_t *
tcval(func)
register int func;
{
    switch(func) {
    case SLC_SYNCH:
    case SLC_BRK:
    case SLC_EOR:
    default:
	return((cc_t *)0);
    }
}

void
TerminalDefaultChars()
{
return;
}

#ifdef notdef
void
TerminalRestoreState()
{
}
#endif

/*
 * TerminalNewMode - set up terminal to a specific mode.
 *	MODE_ECHO: do local terminal echo
 *	MODE_FLOW: do local flow control
 *	MODE_TRAPSIG: do local mapping to TELNET IAC sequences
 *	MODE_EDIT: do local line editing
 *
 *	Command mode:
 *		MODE_ECHO|MODE_EDIT|MODE_FLOW|MODE_TRAPSIG
 *		local echo
 *		local editing
 *		local xon/xoff
 *		local signal mapping
 *
 *	Linemode:
 *		local/no editing
 *	Both Linemode and Single Character mode:
 *		local/remote echo
 *		local/no xon/xoff
 *		local/no signal mapping
 */


void
TerminalNewMode(f)
register int f;
{
    static int prevmode = 0;
#ifndef	USE_TERMIO
#ifdef __SASC
	 int	ami_cbreak = 0;
#else
    struct sgttyb sb;
#endif
#endif
    int lmode;
    int onoff;
    int old;

    globalmode = f&~MODE_FORCE;
    if (prevmode == f)
	return;

    /*
     * Write any outstanding data before switching modes
     * ttyflush() returns 0 only when there is no more data
     * left to write out, it returns -1 if it couldn't do
     * anything at all, otherwise it returns 1 + the number
     * of characters left to write.
#ifndef	USE_TERMIO
     * We would really like ask the kernel to wait for the output
     * to drain, like we can do with the TCSADRAIN, but we don't have
     * that option.  The only ioctl that waits for the output to
     * drain, TIOCSETP, also flushes the input queue, which is NOT
     * what we want (TIOCSETP is like TCSADFLUSH).
#endif
     */
    old = ttyflush(SYNCHing|flushout);
    if (old < 0 || old > 1) {
	do {
	    /*
	     * Wait for data to drain, then flush again.
	     */
	    old = ttyflush(SYNCHing|flushout);
	} while (old < 0 || old > 1);
    }

    old = prevmode;
    prevmode = f&~MODE_FORCE;
    lmode = olmode;

    if (f&MODE_ECHO) {
#ifndef __SASC
	sb.sg_flags |= ECHO;
#endif
    } else {
#ifndef __SASC
	sb.sg_flags &= ~ECHO;
#endif
    }

    if ((f&MODE_FLOW) == 0) {
    }

    if ((f&MODE_TRAPSIG) == 0) {
	localchars = 0;
    } else {
	localchars = 1;
    }

    if (f&MODE_EDIT) {
#ifdef __SASC
	ami_cbreak = 0;
#else
	sb.sg_flags &= ~CBREAK;
	sb.sg_flags &= ~RAW;
	sb.sg_flags |= CRMOD;
#endif
    } else {
#ifdef __SASC
	ami_cbreak = 1;
#else
	sb.sg_flags |= CBREAK;
	sb.sg_flags |= RAW;
	if (f&MODE_ECHO)
	    sb.sg_flags |= CRMOD;
	else
	    sb.sg_flags &= ~CRMOD;
#endif
    }

    if ((f&(MODE_EDIT|MODE_TRAPSIG)) == 0) {
    }

    if (f&MODE_SOFT_TAB) {
#ifndef __SASC
	sb.sg_flags |= XTABS;
#endif
    } else {
#ifndef __SASC
	sb.sg_flags &= ~XTABS;
#endif
    }

    if (f&MODE_LIT_ECHO) {
    } else {
    }

    if (f == -1) {
	onoff = 0;
    } else {
	onoff = 1;
    }

    if (f != -1) {
	/*
	 * We don't want to process ^Y here.  It's just another
	 * character that we'll pass on to the back end.  It has
	 * to process it because it will be processed when the
	 * user attempts to read it, not when we send it.
	 */
    } else {
	lmode = olmode;
    }
#ifdef __SASC
    SetMode(chkufb(0)->ufbfh,ami_cbreak);
#else
    SetMode(_devtab[0].fd,(sb.sg_flags & CBREAK) ? 1L : 0L);
#endif

    ioctl(tin, FIONBIO, (char *)&onoff);
    ioctl(tout, FIONBIO, (char *)&onoff);
}


int
TerminalWindowSize(rows, cols)
long *rows, *cols;
{
    return 0;
}

int
NetClose(fd)
int	fd;
{
#ifdef AMI_TCP
	 return CloseSocket(fd);
#else
    return s_close(fd);
#endif
}


void
NetNonblockingIO(fd, onoff)
int
	fd,
	onoff;
{
    ioctl(fd, FIONBIO, (char *)&onoff);
}

/*
 * Various signal handling routines.
 */

/* ARGSUSED */
static SIG_FUNC_RET
deadpeer(sig)
int sig;
{
	setcommandmode();
	longjmp(peerdied, -1);
}

/* ARGSUSED */
static SIG_FUNC_RET
intr(sig)
int sig;
{
    if (localchars) {
	intp();
	return;
    }
    setcommandmode();
    longjmp(toplevel, -1);
}

/* ARGSUSED */
static SIG_FUNC_RET
intr2(sig)
int sig;
{
    if (localchars) {
	    sendabort();
	return;
    }
}

#ifdef	SIGTSTP
/* ARGSUSED */
static SIG_FUNC_RET
susp(sig)
int sig;
{
    if (localchars)
	sendsusp();
}
#endif

#ifdef	SIGWINCH
/* ARGSUSED */
static SIG_FUNC_RET
sendwin(sig)
int sig;
{
    if (connected) {
	sendnaws();
    }
}
#endif

#ifdef	SIGINFO
/* ARGSUSED */
static SIG_FUNC_RET
ayt(sig)
int sig;
{
    if (connected)
	sendayt();
    else
	ayt_status();
}
#endif

void
sys_telnet_init()
{
    setconnmode(0);

    NetNonblockingIO(net, 1);

#if	defined(SO_OOBINLINE)
   {	int one=1;
#ifdef AMI_TCP
    if (setsockopt(net, SOL_SOCKET, SO_OOBINLINE, &one,sizeof(one)) == -1) 
#else
    if (SetSockOpt(net, SOL_SOCKET, SO_OOBINLINE, &one,sizeof(one)) == -1) 
#endif
    {
	    perror("SetSockOpt");
    }
   }
#endif	/* defined(SO_OOBINLINE) */
}

/*
 * Process rings -
 *
 *	This routine tries to fill up/empty our various rings.
 *
 *	The parameter specifies whether this is a poll operation,
 *	or a block-until-something-happens operation.
 *
 *	The return value is 1 if something happened, 0 if not.
 */

int
process_rings(netin, netout, netex, ttyin, ttyout, poll)
int poll;		/* If 0, then block until something to do */
{
    register int c;
		/* One wants to be a bit careful about setting returnValue
		 * to one, since a one implies we did some useful work,
		 * and therefore probably won't be called to block next
		 * time (TN3270 mode only).
		 */
    int returnValue = 0;
    static struct timeval TimeValue = { 0 };
    static struct timeval Quickie = {0L,20000L};
	extern long bytesin;
    if (netout) {
	FD_SET(net, &obits);
    } 
    if (netin) {
	FD_SET(net, &ibits);
    }
    if (netex) {
	FD_SET(net, &xbits);
    }
    if ((c = select(16, &ibits, &obits, &xbits,
			(poll == 0)? (struct timeval *)&Quickie : &TimeValue)) < 0) {
	if (c == -1) {
		    /*
		     * we can get EINTR if we are in line mode,
		     * and the user does an escape (TSTP), or
		     * some other signal generator.
		     */
	    if (errno == EINTR) {
		return 0;
	    }
		    /* I don't like this, does it ever happen? */
	    printf("sleep(5) from telnet, after select\r\n");
	    sleep(5);
	}
	return 0;
    }

    /*
     * Any urgent data?
     */
    if (FD_ISSET(net, &xbits)) {
	FD_CLR(net, &xbits);
	SYNCHing = 1;
	(void) ttyflush(1);	/* flush already enqueued data */
    }

    /*
     * Something to read from the network...
     */
    if (FD_ISSET(net, &ibits)) {
	int canread;

	FD_CLR(net, &ibits);
	canread = ring_empty_consecutive(&netiring);
#if	!defined(SO_OOBINLINE)
	    /*
	     * In 4.2 (and some early 4.3) systems, the
	     * OOB indication and data handling in the kernel
	     * is such that if two separate TCP Urgent requests
	     * come in, one byte of TCP data will be overlaid.
	     * This is fatal for Telnet, but we try to live
	     * with it.
	     *
	     * In addition, in 4.2 (and...), a special protocol
	     * is needed to pick up the TCP Urgent data in
	     * the correct sequence.
	     *
	     * What we do is:  if we think we are in urgent
	     * mode, we look to see if we are "at the mark".
	     * If we are, we do an OOB receive.  If we run
	     * this twice, we will do the OOB receive twice,
	     * but the second will fail, since the second
	     * time we were "at the mark", but there wasn't
	     * any data there (the kernel doesn't reset
	     * "at the mark" until we do a normal read).
	     * Once we've read the OOB data, we go ahead
	     * and do normal reads.
	     *
	     * There is also another problem, which is that
	     * since the OOB byte we read doesn't put us
	     * out of OOB state, and since that byte is most
	     * likely the TELNET DM (data mark), we would
	     * stay in the TELNET SYNCH (SYNCHing) state.
	     * So, clocks to the rescue.  If we've "just"
	     * received a DM, then we test for the
	     * presence of OOB data when the receive OOB
	     * fails (and AFTER we did the normal mode read
	     * to clear "at the mark").
	     */
	if (SYNCHing) {
	    int atmark;
	    static int bogus_oob = 0, first = 1;

	    s_ioctl(net, SIOCATMARK, (char *)&atmark);
	    if (atmark) {
		c = recv(net, netiring.supply, canread, MSG_OOB);
		if ((c == -1) && (errno == EINVAL)) {
		    c = recv(net, netiring.supply, canread, 0);
		    if (clocks.didnetreceive < clocks.gotDM) {
			SYNCHing = stilloob(net);
		    }
		} else if (first && c > 0) {
		    /*
		     * Bogosity check.  Systems based on 4.2BSD
		     * do not return an error if you do a second
		     * recv(MSG_OOB).  So, we do one.  If it
		     * succeeds and returns exactly the same
		     * data, then assume that we are running
		     * on a broken system and set the bogus_oob
		     * flag.  (If the data was different, then
		     * we probably got some valid new data, so
		     * increment the count...)
		     */
		    int i;
		    i = recv(net, netiring.supply + c, canread - c, MSG_OOB);
		    if (i == c &&
			  bcmp(netiring.supply, netiring.supply + c, i) == 0) {
			bogus_oob = 1;
			first = 0;
		    } else if (i < 0) {
			bogus_oob = 0;
			first = 0;
		    } else
			c += i;
		}
		if (bogus_oob && c > 0) {
		    int i;
		    /*
		     * Bogosity.  We have to do the read
		     * to clear the atmark to get out of
		     * an infinate loop.
		     */
		    i = read(net, netiring.supply + c, canread - c);
		    if (i > 0)
			c += i;
		}
	    } else {
		c = recv(net, netiring.supply, canread, 0);
	    }
	} else {
	    c = recv(net, netiring.supply, canread, 0);
	}
	settimer(didnetreceive);
#else	/* !defined(SO_OOBINLINE) */
	c = recv(net, netiring.supply, canread, 0);
#endif	/* !defined(SO_OOBINLINE) */
	if (c < 0 && errno == EWOULDBLOCK) {
	    c = 0;
	} else if (c <= 0) {
	    return -1;
	}
	if (netdata) {
	    Dump('<', netiring.supply, c);
	}
	if (c)
	    bytesin+=c;
	    ring_supplied(&netiring, c);
	returnValue = 1;
    }

    /*
     * Something to read from the tty...
     */
#ifdef __SASC
    if (WaitForChar(chkufb(0)->ufbfh,Quickie.tv_usec)) {
#else
    if (WaitForChar(_devtab[0].fd,Quickie.tv_usec)) {
#endif
//	FD_CLR(tin, &ibits);
	c = TerminalRead(ttyiring.supply, ring_empty_consecutive(&ttyiring));
	if (c < 0 && errno == EWOULDBLOCK) {
	    c = 0;
	} else {
	    /* EOF detection for line mode!!!! */
	    if ((c == 0) && MODE_LOCAL_CHARS(globalmode) && isatty(tin)) {
			/* must be an EOF... */
#ifndef __SASC
		*ttyiring.supply = termEofChar;
#endif
		c = 1;
	    }
	    if (c <= 0) {
		return -1;
	    }
	    ring_supplied(&ttyiring, c);
	}
	returnValue = 1;		/* did something useful */
    }

    if (FD_ISSET(net, &obits)) {
	FD_CLR(net, &obits);
	returnValue |= netflush();
    }
	returnValue |= (ttyflush(SYNCHing|flushout) > 0);
    return returnValue;
}
