/* Copyright (C), 2007 by Stephen Hurd */

/* $Id: conn_pty.c,v 1.41 2020/05/02 22:52:53 deuce Exp $ */

#ifdef __unix__

#include <signal.h>   // kill()
#include <sys/wait.h> // WEXITSTATUS
#include <unistd.h>   /* _POSIX_VDISABLE - needed when termios.h is broken */

#if defined(__FreeBSD__)
 #include <libutil.h> // forkpty()
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DARWIN__)
 #include <util.h>
#elif defined(__linux__)
 #include <pty.h>
#elif defined(__QNX__)
 #if 0
  #include <unix.h>
 #else
  #define NEEDS_FORKPTY
 #endif
#endif

#ifdef NEEDS_FORKPTY
 #include <grp.h>
#endif

#include <termios.h>

/*
 * Control Character Defaults
 */
#ifndef CTRL
 #define CTRL(x) (x & 037)
#endif
#ifndef CEOF
 #define CEOF CTRL('d')
#endif
#ifndef CEOL
 #ifndef _POSIX_VDISABLE
  #define CEOL 0xff /* XXX avoid _POSIX_VDISABLE */
 #else
  #define CEOL _POSIX_VDISABLE
 #endif
#endif
#ifndef CERASE
 #define CERASE 0177
#endif
#ifndef CERASE2
 #define CERASE2 CTRL('h')
#endif
#ifndef CINTR
 #define CINTR CTRL('c')
#endif
#ifndef CSTATUS
 #define CSTATUS CTRL('t')
#endif
#ifndef CKILL
 #define CKILL CTRL('u')
#endif
#ifndef CMIN
 #define CMIN 1
#endif
#ifndef CSWTCH
 #define CSWTCH 0
#endif
#ifndef CQUIT
 #define CQUIT 034 /* FS, ^\ */
#endif
#ifndef CSUSP
 #define CSUSP CTRL('z')
#endif
#ifndef CTIME
 #define CTIME 0
#endif
#ifndef CDSUSP
 #define CDSUSP CTRL('y')
#endif
#ifndef CSTART
 #define CSTART CTRL('q')
#endif
#ifndef CSTOP
 #define CSTOP CTRL('s')
#endif
#ifndef CLNEXT
 #define CLNEXT CTRL('v')
#endif
#ifndef CDISCARD
 #define CDISCARD CTRL('o')
#endif
#ifndef CWERASE
 #define CWERASE CTRL('w')
#endif
#ifndef CREPRINT
 #define CREPRINT CTRL('r')
#endif
#ifndef CEOT
 #define CEOT CEOF
#endif

/* compat */
#ifndef CBRK
 #define CBRK CEOL
#endif
#ifndef CRPRNT
 #define CRPRNT CREPRINT
#endif
#ifndef CFLUSH
 #define CFLUSH CDISCARD
#endif

#ifndef CSWTC
 #ifndef _POSIX_VDISABLE
  #define CSWTC 0xff /* XXX avoid _POSIX_VDISABLE */
 #else
  #define CSWTC _POSIX_VDISABLE
 #endif
#endif

#ifndef TTYDEF_IFLAG
 #ifndef IMAXBEL
  #define TTYDEF_IFLAG (BRKINT | ICRNL | IXON | IXANY)
 #else
  #define TTYDEF_IFLAG (BRKINT | ICRNL | IMAXBEL | IXON | IXANY)
 #endif
#endif
#ifndef TTYDEF_OFLAG
 #define TTYDEF_OFLAG (OPOST | ONLCR)
#endif
#ifndef TTYDEF_LFLAG
 #define TTYDEF_LFLAG (ECHO | ICANON | ISIG | IEXTEN | ECHOE | ECHOKE | ECHOCTL)
#endif
#ifndef TTYDEF_CFLAG
 #define TTYDEF_CFLAG (CREAD | CS8 | HUPCL)
#endif

#include <stdlib.h>
#include <xpprintf.h>

#include "bbslist.h"
#include "ciolib.h"
#include "conn.h"
#include "fonts.h"
#include "syncterm.h"
#include "uifcinit.h"
#include "window.h"
extern int default_font;

#ifdef NEEDS_CFMAKERAW

void
cfmakeraw(struct termios *t)
{
	t->c_iflag &= ~(IMAXBEL | IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
	t->c_oflag &= ~OPOST;
	t->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
	t->c_cflag &= ~(CSIZE | PARENB);
	t->c_cflag |= CS8;
}

#endif

#ifdef NEEDS_FORKPTY

static int
login_tty(int fd)
{
	(void)setsid();
	if (!isatty(fd))
		return -1;
	(void)dup2(fd, 0);
	(void)dup2(fd, 1);
	(void)dup2(fd, 2);
	if (fd > 2)
		(void)close(fd);
	return 0;
}

 #ifdef NEEDS_DAEMON

/****************************************************************************/

/* Daemonizes the process                                                   */

/****************************************************************************/
int
daemon(int nochdir, int noclose)
{
	int fd;

	switch (fork()) {
		case -1:
			return -1;
		case 0:
			break;
		default:
			_exit(0);
	}

	if (setsid() == -1)
		return -1;

	if (!nochdir)
		(void)chdir("/");

	if (!noclose && ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1)) {
		(void)dup2(fd, STDIN_FILENO);
		(void)dup2(fd, STDOUT_FILENO);
		(void)dup2(fd, STDERR_FILENO);
		if (fd > 2)
			(void)close(fd);
	}
	return 0;
}

 #endif /* ifdef NEEDS_DAEMON */

static int
openpty(int *amaster, int *aslave, char *name, struct termios *termp, struct winsize *winp)
{
	char          line[] = "/dev/ptyXX";
	const char   *cp1, *cp2;
	int           master, slave, ttygid;
	struct group *gr;

	if ((gr = getgrnam("tty")) != NULL)
		ttygid = gr->gr_gid;
	else
		ttygid = -1;

	for (cp1 = "pqrsPQRS"; *cp1; cp1++) {
		line[8] = *cp1;
		for (cp2 = "0123456789abcdefghijklmnopqrstuv"; *cp2; cp2++) {
			line[5] = 'p';
			line[9] = *cp2;
			if ((master = open(line, O_RDWR, 0)) == -1) {
				if (errno == ENOENT)
					break; /* try the next pty group */
			}
			else {
				line[5] = 't';
				(void)chown(line, getuid(), ttygid);
				(void)chmod(line, S_IRUSR | S_IWUSR | S_IWGRP);

                                /* Hrm... SunOS doesn't seem to have revoke
                                 *  (void) revoke(line); */
				if ((slave = open(line, O_RDWR, 0)) != -1) {
					*amaster = master;
					*aslave = slave;
					if (name)
						strcpy(name, line);
					if (termp)
						(void)tcsetattr(slave,
						    TCSAFLUSH, termp);
					if (winp)
						(void)ioctl(slave, TIOCSWINSZ,
						    (char *)winp);
					return 0;
				}
				(void)close(master);
			}
		}
	}
	errno = ENOENT; /* out of ptys */
	return -1;
}

static int
forkpty(int *amaster, char *name, struct termios *termp, struct winsize *winp)
{
	int master, slave, pid;

	if (openpty(&master, &slave, name, termp, winp) == -1)
		return -1;
	switch (pid = FORK()) {
		case -1:
			return -1;
		case 0:
                        /*
                         * child
                         */
			(void)close(master);
			login_tty(slave);
			return 0;
	}

        /*
         * parent
         */
	*amaster = master;
	(void)close(slave);
	return pid;
}

#endif /* NEED_FORKPTY */

int        master;
int        child_pid;
static int status;

#ifdef __BORLANDC__
 #pragma argsused
#endif

void
pty_input_thread(void *args)
{
	fd_set rds;
	int    rd;
	size_t buffered;
	int    i;
	struct timeval tv;
	size_t bufsz = 0;

	SetThreadName("PTY Input");
	conn_api.input_thread_running = 1;
	while (master != -1 && !conn_api.terminate) {
		if ((i = waitpid(child_pid, &status, WNOHANG)))
			break;
		if (bufsz < BUFFER_SIZE) {
			FD_ZERO(&rds);
			FD_SET(master, &rds);
			tv.tv_sec = 0;
			if (bufsz)
				tv.tv_usec = 0;
			else
				tv.tv_usec = 100000;
			rd = select(master + 1, &rds, NULL, NULL, &tv);
			if (rd == -1) {
				if (errno == EBADF)
					break;
				rd = 0;
			}
			if (rd == 1) {
				rd = read(master, conn_api.rd_buf + bufsz, conn_api.rd_buf_size - bufsz);
				if (rd <= 0)
					break;
				bufsz += rd;
			}
		}
		if (bufsz > 0) {
			assert_pthread_mutex_lock(&(conn_inbuf.mutex));
			conn_buf_wait_free(&conn_inbuf, 1, 100);
			buffered = conn_buf_put(&conn_inbuf, conn_api.rd_buf, bufsz);
			memmove(conn_api.rd_buf, &conn_api.rd_buf[buffered], bufsz - buffered);
			bufsz -= buffered;
			assert_pthread_mutex_unlock(&(conn_inbuf.mutex));
		}
	}
	conn_api.terminate = true;
	conn_api.input_thread_running = 2;
}

#ifdef __BORLANDC__
 #pragma argsused
#endif

void
pty_output_thread(void *args)
{
	fd_set wds;
	int    wr;
	int    ret;
	int    sent;
	struct timeval tv;

	SetThreadName("PTY Output");
	conn_api.output_thread_running = 1;
	while (master != -1 && !conn_api.terminate) {
		if (waitpid(child_pid, &status, WNOHANG))
			break;
		assert_pthread_mutex_lock(&(conn_outbuf.mutex));
		ret = 0;
		wr = conn_buf_wait_bytes(&conn_outbuf, 1, 100);
		if (wr) {
			wr = conn_buf_get(&conn_outbuf, conn_api.wr_buf, conn_api.wr_buf_size);
			assert_pthread_mutex_unlock(&(conn_outbuf.mutex));
			sent = 0;
			while (master != -1 && sent < wr && !conn_api.terminate) {
				FD_ZERO(&wds);
				FD_SET(master, &wds);
				tv.tv_sec = 0;
				tv.tv_usec = 100000;
				ret = select(master + 1, NULL, &wds, NULL, &tv);
				if (ret == -1) {
					if (errno == EBADF)
						break;
					ret = 0;
				}
				if (ret == 1) {
					ret = write(master, conn_api.wr_buf + sent, wr - sent);
					if (ret <= 0) {
						ret = -1;
						break;
					}
					sent += ret;
				}
			}
		}
		else {
			assert_pthread_mutex_unlock(&(conn_outbuf.mutex));
		}
		if (ret == -1)
			break;
	}
	conn_api.terminate = true;
	conn_api.output_thread_running = 2;
}

int
pty_connect(struct bbslist *bbs)
{
	struct winsize ws;
	struct termios ts;
	char          *termcap;
	int            cols, rows, pixelc, pixelr;
	int            cp;
	int            i;

	ts.c_iflag = TTYDEF_IFLAG;
	ts.c_oflag = TTYDEF_OFLAG;
	ts.c_lflag = TTYDEF_LFLAG;
	ts.c_cflag = TTYDEF_CFLAG;
	for (i = 0; i < (sizeof(ts.c_cc) / sizeof(ts.c_cc[0])); i++)
		ts.c_cc[i] = _POSIX_VDISABLE;
#ifdef VMIN
	ts.c_cc[VMIN] = CMIN;
#endif
#ifdef VSWTCH
	ts.c_cc[VSWTCH] = CSWTCH;
#endif
#ifdef VSWTC
	ts.c_cc[VSWTC] = CSWTC;
#endif
#ifdef VEOF
	ts.c_cc[VEOF] = CEOF;
#endif
#ifdef VEOL
	ts.c_cc[VEOL] = CEOL;
#endif
#ifdef VEOL2
	ts.c_cc[VEOL] = CEOL;
#endif
#ifdef VERASE
	ts.c_cc[VERASE] = CTRL('h');
#endif
#ifdef VWERASE
	ts.c_cc[VWERASE] = CWERASE;
#endif
#ifdef VINTR
	ts.c_cc[VINTR] = CINTR;
#endif
#ifdef VKILL
	ts.c_cc[VKILL] = CKILL;
#endif
#ifdef VQUIT
	ts.c_cc[VQUIT] = CQUIT;
#endif
#ifdef VSTART
	ts.c_cc[VSTART] = CSTART;
#endif
#ifdef VSTOP
	ts.c_cc[VSTOP] = CSTOP;
#endif
#ifdef VSUSP
	ts.c_cc[VSUSP] = CSUSP;
#endif
#ifdef VREPRINT
	ts.c_cc[VREPRINT] = CREPRINT;
#endif
#ifdef VDISCARD
	ts.c_cc[VDISCARD] = CDISCARD;
#endif
#ifdef VLNEXT
	ts.c_cc[VLNEXT] = CLNEXT;
#endif
#ifdef VEOL2
	ts.c_cc[VEOL2] = CEOL;
#endif
#ifdef VERASE2
	ts.c_cc[VERASE2] = CTRL('h');
#endif
#ifdef VDSUSP
	ts.c_cc[VDSUSP] = CDSUSP;
#endif
#ifdef VSTATUS
	ts.c_cc[VSTATUS] = CSTATUS;
#endif
	cfsetspeed(&ts, 115200);

	get_term_win_size(&cols, &rows, &pixelc, &pixelr, &bbs->nostatus);
	ws.ws_col = cols;
	ws.ws_row = rows;
	ws.ws_xpixel = pixelc;
	ws.ws_ypixel = pixelr;

	cp = getcodepage();
	child_pid = forkpty(&master, NULL, &ts, &ws);
	switch (child_pid) {
		case -1:
			return -1;
		case 0: /* Child */
			setenv("TERM", settings.TERM, 1);
			termcap = xp_asprintf("syncterm|SyncTERM"
			        ":co#%d:li#%d"
			        ":ND:am:da:ut:it#8"
			        "%s"
			        ":@7=\\E[K:AL=\\E[%%dL:DC=\\E[%%dP"
			        ":DL=\\E[%%dM:DO=\\E[%%dB:F1=\\E[23~:F2=\\E[24~:IC=\\E[%%d@"
			        ":LE=\\E[%%dD:MC=\\E[69h\\E[s\\E[69l"
			        ":ML=\\E[?69h\\E[%%i%%d;0s\\E?69l:RA=\\E[7l:RI=\\E[%%dC"
			        ":SA=\\E[?7h:SF=\\E[%%dS:SR=\\E[%%dT:UP=\\E[%%dA"
			        ":Zm=\\E[69h\\E[%%i%%d;%%ds\\E[69l:Zn=\\E[69h\\E[0;%%i%%ds\\E[69l"
			        ":ac=}\\234|\\330{\\322+\\020,\\021l\\332m\\300k\\277j\\331u\\264t\\303v\\301w\\302q\\304x\\263n\\305`^Da\\260f\\370g\\361~\\371.^Y-^Xh\\261i^U0\\333y\\363z\\362"
			        ":al=\\E[L:bl=^G:bt=\\E[Z:cb=\\E[1K:cd=\\E[J:ce=\\E[K"
			        ":ch=\\E[%%i%%dG:cl=\\E[2J:cm=\\E[%%i%%d;%%dH:cr=^M"
			        ":cs=\\E[%%i%%d;%%dr:ct=\\E[3g:dc=\\E[P:dl=\\E[M:do=^J:ec=\\E[%%dX"
			        ":ho=\\E[H:i1=\\Ec:ic=\\E[@:k1=\\E[11~:k2=\\E[12~"
			        ":k3=\\E[13~:k4=\\E[14~:k5=\\E[15~:k6=\\E[17~:k7=\\E[18~"
			        ":k8=\\E[19~:k9=\\E[20~:k;=\\E[21~:kB=\\E[Z:kD=\\177:kN=\\E[U"
			        ":kP=\\E[V:kb=^H:kd=\\E[B:kl=\\E[D:kr=\\E[C:ku=\\E[A:le=\\E[D"
			        ":mb=\\E[5m:md=\\E[1m:me=\\E[m:nd=\\E[C:nw=^M^J"
			        ":r1=\\E[c:rc=\\E[u"
			        ":sc=\\E[s:se=\\E[m:sf=\\E[S:so=\\E[0;1;7m:sr=\\E[T:st=\\E[H"
			        ":ta=^I:up=\\E[A:ve=\\E[?25h:vi=\\E[?25l:vs=\\E[?25h:"
			        ,
			        ws.ws_col,
			        ws.ws_row
			        ,
			        cio_api.options
			        & CONIO_OPT_PALETTE_SETTING ? ":cc:Co#256:pa#32767:AB=\\E[48;5;%dm:AF=\\E[38;5;%dm" : ":Co#8:pa#64::AB=\\E[4%dm:AF=\\E[3%dm");
			setenv("TERMCAP", termcap, 1);
			xp_asprintf_free(termcap);
			termcap = xp_asprintf("%d", ws.ws_col);
			setenv("COLUMNS", termcap, 1);
			xp_asprintf_free(termcap);
			termcap = xp_asprintf("%d", ws.ws_row);
			setenv("LINES", termcap, 1);
			xp_asprintf_free(termcap);
			setenv("MM_CHARSET", ciolib_cp[cp].name, 1);
			if (bbs->addr[0]) {
				execl("/bin/sh", "/bin/sh", "-c", bbs->addr, (char *)0);
			}
			else {
				if (getenv("SHELL"))
					execl(getenv("SHELL"), getenv("SHELL"), (char *)0);
			}
			exit(1);
	}

	if (!create_conn_buf(&conn_inbuf, BUFFER_SIZE))
		return -1;
	if (!create_conn_buf(&conn_outbuf, BUFFER_SIZE)) {
		destroy_conn_buf(&conn_inbuf);
		return -1;
	}
	if (!(conn_api.rd_buf = (unsigned char *)malloc(BUFFER_SIZE))) {
		destroy_conn_buf(&conn_inbuf);
		destroy_conn_buf(&conn_outbuf);
		return -1;
	}
	conn_api.rd_buf_size = BUFFER_SIZE;
	if (!(conn_api.wr_buf = (unsigned char *)malloc(BUFFER_SIZE))) {
		free(conn_api.rd_buf);
		destroy_conn_buf(&conn_inbuf);
		destroy_conn_buf(&conn_outbuf);
		return -1;
	}
	conn_api.wr_buf_size = BUFFER_SIZE;

	_beginthread(pty_output_thread, 0, NULL);
	_beginthread(pty_input_thread, 0, NULL);

	return 0;
}

int
pty_close(void)
{
	uint64_t start;
	char   garbage[1024];
	int oldmaster;

	conn_api.terminate = true;
	start = xp_fast_timer64();
	kill(child_pid, SIGHUP);
	while (waitpid(child_pid, &status, WNOHANG) == 0) {
                /* Wait for 10 seconds */
		if (xp_fast_timer64() - start >= 10)
			break;
		SLEEP(1);
	}
	kill(child_pid, SIGKILL);
	waitpid(child_pid, &status, 0);

	oldmaster = master;
	master = -1;
	while (conn_api.input_thread_running == 1 || conn_api.output_thread_running == 1) {
		conn_recv_upto(garbage, sizeof(garbage), 0);
		SLEEP(1);
	}
	master = oldmaster;
	destroy_conn_buf(&conn_inbuf);
	destroy_conn_buf(&conn_outbuf);
	FREE_AND_NULL(conn_api.rd_buf);
	FREE_AND_NULL(conn_api.wr_buf);
	close(master);
	return 0;
}

#endif /* __unix__ */
