/* OS- and machine-dependent stuff for UNIX */
/* collected from a lot of other stuff from different releases of the package */
/* Written by Mikel Matthews, N9DVG */
/* SYS5 stuff added by Jere Sandidge, K4FUM */
/* cleanup and adaption into NETCHL by PE1CHL */

#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <termio.h>
#include <sys/types.h>
#include <sys/times.h>
#include <sys/ndir.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <prototypes.h>

#include "global.h"
#include "config.h"
#include "mbuf.h"
#include "internet.h"
#include "iface.h"
#include "cmdparse.h"
#include "timer.h"
#include "environ.h"
#include "asy.h"
#include "unix.h"

#ifndef B19200
#define B19200	EXTA
#endif

#ifndef B38400
#define B38400	EXTB
#endif

#if (defined(FD_SETSIZE) && !defined(M_I86MM))
#define SELECT				/* we can use the select() call */

# ifdef FD_ZERO
#  define bzero(s,n)	memset(s,0,n)	/* there is no bzero() in the lib! */
#  define NUL_SET	{0}
# else
typedef unsigned long fd_set;		/* equivalents of FD_xxx macros */
#  define FD_ZERO(fdsp)		*(fdsp) = 0
#  define FD_SET(fd,fdsp)	*(fdsp) |= (1 << (fd))
#  define FD_CLR(fd,fdsp)	*(fdsp) &= ~(1 << (fd))
#  define FD_ISSET(fd,fdsp)	(*(fdsp) & (1 << (fd)))
#  define NUL_SET	0
# endif
#endif

struct asy asy[ASY_MAX];
struct interface *ifaces;

unsigned nasy = 0;			/* number of ASY interfaces */

#ifdef SERVERS
extern FILE *logfp;			/* log file pointer */
extern char logname[];			/* log file name */
#endif

#ifdef TRACE
extern FILE *trfp;			/* trace file pointer */
extern char trname[];			/* trace file name */
#endif

#ifdef IP
extern struct mbuf *loopq;
#endif
#ifdef AX25
extern struct mbuf *axloopq;
#endif

static struct termio saved_termio;	/* termio on program startup */
static struct termio our_termio;	/* termio during program run */

#ifdef SELECT
static char *envselect;			/* options passed using SELECT envvar */
static int noselect = 0;		/* # of fd's NOT supporting select */
static int maxfd = 0;			/* # of fd's for select() */
static fd_set port_set = NUL_SET;	/* set containing all serial devices */
static fd_set read_set = NUL_SET;	/* returned set from last select() */
static prg_busy = 0;			/* set when busy */
#endif

static char ttbuf[BUFSIZ];		/* buffer used for stdout buffering */

/* Called at startup time to set up console I/O */
ioinit()
{
#ifdef SELECT
	fd_set fdset;			/* trial set for select */
	struct timeval timeout;
#endif

	setbuf(stdout,ttbuf);		/* buffering for stdout */

	tzset();			/* init the timezone variable */

	ioctl(0,TCGETA,&saved_termio);	/* read and save termio state */

	/* copy termio and drop input processing, echo, and INTR signal */
	/* the VMIN/VTIME will be set up for immediate return when no key */

	ASSIGN(our_termio,saved_termio);
	our_termio.c_lflag &= ~(ICANON|ECHO);
	our_termio.c_cc[VINTR] = 0xff;
	our_termio.c_cc[VMIN] = 0;
	our_termio.c_cc[VTIME] = 0;

#ifdef SELECT
	envselect = getnenv("SELECT");	/* get SELECT var settings */

	if (strchr(envselect,'k') != NULL)	/* select() on keyboard? */
	{
		/* check if stdin can use select() */

		FD_ZERO(&fdset);		/* setup an fd_set for stdin */
		FD_SET(0,&fdset);
		timeout.tv_sec = timeout.tv_usec = 0;

		if (select(1,&fdset,NULL,NULL,&timeout) < 0)
		{
			if (errno == EINVAL)	/* no select() support */
				printf("warning: stdin device does not support select()\n");
			else
				perror("stdin select");

			noselect++;
		}
		else			/* select() went okay */
		{
			maxfd = 1;	/* select on fd #0 */
			FD_SET(0,&port_set); /* enter stdin in the select set */
			our_termio.c_cc[VMIN] = 1; /* can wait on read() */
			our_termio.c_cc[VTIME] = 2;
		}
	}
	else
		noselect++;		/* no select on stdin */
#endif

	ioctl(0,TCSETA,&our_termio);	/* set our termio values */
}

/* Called just before exiting to restore console state */
iostop()
{
	while(ifaces != NULLIF){
		/* first flush the interface's buffers */
		while(ifaces->recv != NULLVFP && (*ifaces->recv)(ifaces))
			;
		if(ifaces->stop != NULLFP)
			(*ifaces->stop)(ifaces);
		ifaces = ifaces->next;
	}

	ioctl(0,TCSETAW,&saved_termio); /* reset to original state */
}

/* Spawn subshell */
doshell(argc,argv)
int argc;
char *argv[];
{
	char *command,**envp;
	int ret;

#ifdef SERVERS
	if (logfp != NULLFILE)		/* logging to file? */
	    fclose(logfp);		/* close the logfile */
#endif

#ifdef TRACE
	if (trfp != stdout)		/* trace to file? */
	    fclose(trfp);		/* close it during shell exec */
#endif

	fflush(stdout);

	ioctl(0,TCSETAW,&saved_termio);	/* set original terminal mode */

	if((command = getenv("SHELL")) == NULLCHAR)
	    command = "/bin/sh";	/* default command interpreter */

	envp = make_env();		/* create envinronment array */

	switch (fork())			/* spawn a subprocess */
	{
	case -1:			/* an error occurred... */
		perror("fork");
		ret = -1;
		break;
	case 0:				/* this is the child */
		execve(command,argv,envp);
		perror(command);
		exit(1);
	default:			/* this is the parent */
		wait(&ret);		/* wait for child to terminate */
		break;
	}

	ioctl(0,TCSETAW,&our_termio);	/* our terminal mode again */

	free_env();			/* free the envp and temp env */

#ifdef SERVERS
	if (logfp != NULLFILE)		/* re-open logfile if it was open */
	    logfp = fopen(logname,"a+");
#endif

#ifdef TRACE
	if (trfp != stdout)		/* re-open tracefile if not stdout */
	    if ((trfp = fopen(trname,"a+")) == NULLFILE)
		trfp = stdout;
#endif

	if (ret != 0)
	    return -1;

	return 0;
}

/* checks the time then ticks and updates ISS */
static time_t clkval = 0;
void
check_time()
{
	int32 iss();
	time_t ntime;
	int maxticks = SEC2TICK(15);	/* upper limit on delayed ticks */
	struct tms tms;			/* dummy required by times() */

	if(clkval == 0){
		/* Executed only once */
		clkval = times(&tms);
		return;
	}

	ntime = times(&tms);		/* read elapsed time */

	while(ntime != clkval){		/* Handle possibility of several missed ticks */
#ifdef IP
		icmpclk();		/* Call this one before tick */
#endif
		tick();
#ifdef IP
		(void)iss();
#endif
		if(maxticks--)		/* check limit on ticks to handle */
			clkval++;
		else
			clkval = ntime; /* ignore any remaining ticks */
	}
}

/* Read characters from the keyboard, translating them to internal codes.
 * If none are ready, return -1
 * This piece of code crudely assumes that you are on an ANSI terminal...
 */
int
kbread()
{
	int c;
#ifdef SELECT
	struct timeval timeout;

	if (stdin->_cnt == 0 &&		/* no buffered characters? */
            maxfd > 0)			/* select() allowed? */
	{
		read_set = port_set;
		timeout.tv_sec = timeout.tv_usec = 0;

#ifdef IP
		if (loopq == NULLBUF)	/* nothing on IP loopback? */
#endif
#ifdef AX25
		if (axloopq == NULLBUF)	/* nothing on AX.25 loopb? */
#endif

		if (noselect || prg_busy) /* reason to stay awake? */
			timeout.tv_usec = 20000; /* short sleeptime */
		else
			timeout.tv_sec = 1; /* sleep for a long time */

		prg_busy = 1;		/* assume we are busy */

		if (select(maxfd,&read_set,NULL,NULL,&timeout) >= 0)
		{
#ifdef DEBUG
			int i;

			printf("select:");
			for (i = 0; i < maxfd; i++)
				if (FD_ISSET(i,&read_set))
					printf(" %d",i);
			printf("\n");
			fflush(stdout);
#endif
		}
		else
		{
#ifdef DEBUG
			perror("select:");
#endif

			return -1;	/* error in select() */
		}

		if (FD_ISSET(0,&port_set) && !FD_ISSET(0,&read_set))
			return -1;	/* select() says no input available */
	}
#endif

	if((c = getchar()) == EOF)
		return -1;		/* no input available */

	if(c == 0x1b){			/* ESC character? */
		/* Lead-in to a special char */
		if(getchar() != '[')
			return 0x1b;

		switch(c = getchar())
		{
		case 'M':		/* F-1 key */
			c = -11;
			break;
		case 'N':		/* F-2 key */
			c = -10;
			break;
		case 'O':		/* F-3 key */
			c = -9;
			break;
		case 'P':		/* F-4 key */
			c = -8;
			break;
		case 'Q':		/* F-5 key */
			c = -7;
			break;
		case 'R':		/* F-6 key */
			c = -6;
			break;
		case 'S':		/* F-7 key */
			c = -5;
			break;
		case 'T':		/* F-8 key */
			c = -4;
			break;
		case 'U':		/* F-9 key */
			c = -3;
			break;
		case 'V':		/* F-10 key */
			c = -2;
			break;
		case 'H':		/* HOME key */
			c = -20;
			break;
		case 'A':		/* UP arrow */
			c = -19;
			break;
		case 'I':		/* PAGE UP */
			c = -18;
			break;
		case 'D':		/* LEFT arrow */
			c = -17;
			break;
		case 'C':		/* RIGHT arrow */
			c = -16;
			break;
		case 'F':		/* END key */
			c = -15;
			break;
		case 'B':		/* DOWN arrow */
			c = -14;
			break;
		case 'G':		/* PAGE DOWN */
			c = -13;
			break;
		case 'L':		/* INS key */
			c = -12;
			break;
		default:		/* Dunno what it is */
			c = -1;
		}
	}
	return c;
}

/* Create a directory listing in a temp file and return the resulting file
 * descriptor. If full == 1, give a full listing; else return just a list
 * of names.
 */
FILE *
dir(path,full)
char *path;
int full;
{
	char *tname;
	char *option;
	FILE *fp;
	char command[256];

	if ((tname = tmpnam(NULLCHAR)) == NULLCHAR)
		return NULLFILE;

	if (full < 0)
		option = "C";
	else
		if (full > 0)
			option = "l";
		else
			option = "1";

	sprintf(command,"ls -%s \"%s\" >%s",option,path,tname);
	system(command);
	fp = fopen(tname,"r");
	unlink(tname);			/* unlink the file now, it remains open */

	return fp;
}

/* wildcard filename lookup */
filedir(name,times,ret_str)
char *name;
int times;
char *ret_str;
{
	static char dname[128], fname[128];
	static DIR *dirp = NULL;
	struct direct *dp;
	struct stat sbuf;
	char *cp, temp[128];

	/*
	 * Make sure that the \0 is there in case we don't find anything
	 */
	ret_str[0] = '\0';

	if (times == 0) {
		/* default a null name to * */
		if (name == NULL || *name == '\0')
			name = "*";
		/* split path into directory and filename */
		if ((cp = strrchr(name,'/')) == NULL) {
			strcpy(dname,".");
			strcpy(fname,name);
		} else {
			strcpy(dname,name);
			dname[cp - name] = '\0';
			strcpy(fname,cp + 1);
			/* root directory */
			if (dname[0] == '\0')
				strcpy(dname,"/");
			/* trailing '/' */
			if (fname[0] == '\0')
				strcpy(fname,"*");
		}
		/* close directory left over from another call */
		if (dirp != NULL)
			closedir(dirp);
		/* open directory */
		if ((dirp = opendir(dname)) == NULL)
			return;
	} else {
		/* for people who don't check return values */
		if (dirp == NULL)
			return;
	}

	/* scan directory */
	while ((dp = readdir(dirp)) != NULL) {
		/* test for name match */
		if (wildmat(dp->d_name,fname)) {
			/* test for regular file */
			sprintf(temp,"%s/%s",dname,dp->d_name);
			if (stat(temp,&sbuf) < 0)
				continue;
			if ((sbuf.st_mode & S_IFMT) != S_IFREG)
				continue;
			strcpy(ret_str,dp->d_name);
			break;
		}
	}

	/* close directory if we hit the end */
	if (dp == NULL) {
		closedir(dirp);
		dirp = NULL;
	}
}

/* Initialize asynch port "dev" */
int
asy_init(dev,arg1,arg2,bufsize)
int16 dev;
char *arg1,*arg2;	/* Attach args for address and vector */
unsigned bufsize;
{
	register struct asy *ap;
	struct termio sgttyb;
#ifdef SELECT
	fd_set fdset;			/* trial set for select */
	struct timeval timeout;
#endif

	if (dev >= nasy)
		return -1;

	ap = &asy[dev];

	if ((ap->fd = open(arg1,O_RDWR,0)) < 0) {
		perror(arg1);
		return -1;
	}

	/*
	 * when possible, close the port upon exec
	 */

	fcntl(ap->fd,F_SETFD,1);

	/*
	 * get the termio structure and save it
	 */

	if (ioctl(ap->fd,TCGETA,&ap->orgmodes) < 0) {
		perror(arg1);
		return -1;
	}

	ASSIGN(sgttyb,ap->orgmodes);

	sgttyb.c_iflag = IGNBRK|IGNPAR;		/* KISS/SLIP setup */
	sgttyb.c_oflag = 0;
	sgttyb.c_cflag = B9600|CS8|CREAD|CLOCAL; /* default to 9600 baud */
	sgttyb.c_lflag = 0;
	sgttyb.c_cc[VTIME] = 0;
	sgttyb.c_cc[VMIN] = 0;

#ifdef CTSFLOW
	if (strchr(arg2,'c') != NULL)	/* CTSFLOW? */
		sgttyb.c_cflag |= CTSFLOW;

	if (strchr(arg2,'r') != NULL)	/* RTSFLOW? */
		sgttyb.c_cflag |= RTSFLOW;
#endif

#ifdef SELECT
	if (strchr(envselect,'t') != NULL) /* select() on ttys? */
	{
		/* check if this device supports select() */

		FD_ZERO(&fdset);	/* setup an fd_set for this port */
		FD_SET(ap->fd,&fdset);
		timeout.tv_sec = timeout.tv_usec = 0;

		if (select(ap->fd + 1,&fdset,NULL,NULL,&timeout) < 0)
		{
			if (errno == EINVAL)	/* no select() support */
				printf("warning: \"%s\" does not support select()\n",arg1);
			else
				perror(arg1);

			noselect++;
		}
		else
		{
			if (ap->fd >= maxfd)
				maxfd = ap->fd + 1;

			FD_SET(ap->fd,&port_set);
			sgttyb.c_cc[VMIN] = 1;	/* read() can block on it */
			sgttyb.c_cc[VTIME] = 2;
		}
	}
	else
		noselect++;		/* yet another one without select() */
#endif

	if (ioctl(ap->fd,TCSETAF,&sgttyb) < 0) {
		perror(arg1);
		return -1;
	}

	ap->tty = malloc(strlen(arg1)+1); /* keep device name */
	strcpy(ap->tty,arg1);

	ap->buf = malloc(ap->bufsize = bufsize); /* receive buffer */

	return 0;
}
int
asy_stop(iface)
struct interface *iface;
{
	register struct asy *ap;

	ap = &asy[iface->dev];

	if (ap->fd > 2)			/* sanity check */
		ioctl(ap->fd,TCSETAW,&ap->orgmodes); /* restore tty state */

	close(ap->fd);
}
/* Asynchronous line I/O control */
asy_ioctl(interface,argc,argv)
struct interface *interface;
int argc;
char *argv[];
{
	if (argc < 1) {
		printf("%u\n",asy[interface->dev].speed);
		return 0;
	}

	return asy_speed(interface->dev,atoi(argv[0]));
}
/* Set asynch line speed */
int
asy_speed(dev,speed)
int16 dev;
int speed;
{
	struct termio sgttyb;

	if (dev >= nasy)
		return -1;

	asy[dev].speed = speed;

	if (ioctl(asy[dev].fd,TCGETA,&sgttyb) < 0) {
		perror(asy[dev].tty);
		return -1;
	}

	sgttyb.c_cflag &= ~CBAUD;

	switch ((unsigned)speed) {
	case 0:
		sgttyb.c_cflag |= B0;
		break;
	case 50:
		sgttyb.c_cflag |= B50;
		break;
	case 75:
		sgttyb.c_cflag |= B75;
		break;
	case 110:
		sgttyb.c_cflag |= B110;
		break;
	case 134:
		sgttyb.c_cflag |= B134;
		break;
	case 150:
		sgttyb.c_cflag |= B150;
		break;
	case 200:
		sgttyb.c_cflag |= B200;
		break;
	case 300:
		sgttyb.c_cflag |= B300;
		break;
	case 600:
		sgttyb.c_cflag |= B600;
		break;
	case 1200:
		sgttyb.c_cflag |= B1200;
		break;
	case 1800:
		sgttyb.c_cflag |= B1800;
		break;
	case 2400:
		sgttyb.c_cflag |= B2400;
		break;
	case 4800:
		sgttyb.c_cflag |= B4800;
		break;
	case 9600:
		sgttyb.c_cflag |= B9600;
		break;
	case 19200:
		sgttyb.c_cflag |= B19200;
		break;
	case 38400:
		sgttyb.c_cflag |= B38400;
		break;
	default:
		printf("asy_speed: Unknown speed (%d)\n",speed);
		break;
	}

	if (ioctl(asy[dev].fd,TCSETAW,&sgttyb) < 0) {
		perror(asy[dev].tty);
		return -1;
	}

	return 0;
}
/* Send a buffer to serial transmitter */
asy_output(dev,buf,cnt)
unsigned dev;
char *buf;
unsigned short cnt;
{
#ifdef DEBUG
	int i;

	printf("asy_output(%d,%lx,%d):",dev,buf,cnt);
	for (i = 0; i < cnt; i++)
		printf(" %02x",uchar(buf[i]));

	printf("\n");
#endif

#ifdef SELECT
	prg_busy = 3;
#endif

	if (dev >= nasy || !stxrdy(dev))
		return -1;

	if (write(asy[dev].fd,buf,cnt) < cnt)
		return -1;

	return 0;
}
/* Receive characters from asynch line
 * Returns count of characters read
 */
int16
asy_recv(dev,buf,cnt)
int16 dev;
char *buf;
unsigned cnt;
{
	register struct asy *ap;
	unsigned tot = 0;
	int r;

	ap = &asy[dev];

	/* fill the read ahead buffer */
	if(ap->cnt == 0) {
#ifdef SELECT
		if (FD_ISSET(ap->fd,&port_set) && !FD_ISSET(ap->fd,&read_set))
			return 0;	/* select() indicates nothing there */

		FD_CLR(ap->fd,&read_set);
#endif

		ap->data = ap->buf;
		if ((r = read(ap->fd,ap->data,ap->bufsize)) <= 0)
			return 0;

		ap->cnt = r;

#ifdef DEBUG
		printf("asy_recv: read(%d) ",ap->cnt);
		for (r = 0; r < ap->cnt; r++)
			printf(" %02x",uchar(ap->data[r]));

		printf("\n");
#endif
	}
	/* fetch what you need with no system call overhead */
	if(cnt == 1) {		/* single byte copy, do it here */
		*buf = *ap->data++;
		ap->cnt--;
		tot++;
	} else {		/* multi-byte copy, left memcpy do the work */
		r = min(cnt,ap->cnt);
		memcpy(buf,ap->data,r);
		ap->cnt -= r;
		ap->data += r;
		tot += r;
	}

	return (tot);
}
int
stxrdy(dev)
int16 dev;
{
#ifdef SELECT
	struct timeval timeout;
	fd_set write_set;
	int fd;

	fd = asy[dev].fd;		/* the fd we want to check */

	if (FD_ISSET(fd,&port_set))	/* select() on this device? */
	{
		FD_ZERO(&write_set);	/* write_fd = only that fd */
		FD_SET(fd,&write_set);
		read_set = port_set;	/* read: all valid ports */
		timeout.tv_sec = timeout.tv_usec = 0;

		if (select(maxfd,&read_set,&write_set,NULL,&timeout) >= 0 &&
		    !FD_ISSET(fd,&write_set))
			return 0;	/* not ready */
	}
#endif
#if 0
	ioctl(asy[dev].fd,TCSBRK,1);	/* wait for output to drain */
#endif
	return 1;
}

/* find substring in another string */

char *strstr (s,f)
	char *s,*f;
{
	char *p;

	while ((p = strchr(s,*f)) != NULLCHAR)
	{
		if (!strncmp(p,f,strlen(f)))
			return p;

		s = p + 1;
	}

	return p;
}

#if (defined(M_I86MM))
/* Convert a pointer to a long integer, so that it can be printed in a
   compiler- and system-independent way.
   This function currently assumes that pointers are 16-bit (int) size.
 */
long ptr2long (p)
char *p;
{
	unsigned int rv = (unsigned int) p;

	return (long) rv;
}
#endif

/* interrupt disable and enable have no real meaning here */

char disable ()

{
	return 0;
}

void restore (ps)
	char ps;

{
}

/* this routine is supposed to give control to the system until something */
/* is to be done.  when select is unusable, wait a short period	*/

void eihalt ()

{
#ifdef SELECT
	prg_busy &= ~1;				/* apparently not active... */

	if (maxfd > 0)				/* select() supported? */
		return;				/* then no reason to wait */
#endif

	nap(100L);				/* when no select(), nap() */
}
