/*

cotty 0.4c

Simple command-line pseudo terminal manager:
allows to run coprocesses talking to each other thru their tty and/or pty.

Most useful to drive from scripts programs that want a tty, as in
	cotty -d -- pppd silent 192.168.0.1:192.168.0.2 \
		-- ssh -t root@remote pppd
This particular use has been obsoleted under Linux
(but probably not under the various free BSDs and proprietary Unices),
as it can be done without cotty with
	pppd pty 'ssh -t root@remote pppd' silent 192.168.0.1:192.168.0.2
Other uses of cotty remain, as called by fwprc, or by my lispm script.
See the Firewall-Piercing mini-HOWTO.

Copyright (c) 1998-2001 François-René Rideau DDa(.ng-Vu~ Ba^n <fare@tunes.org>

Many thanks to master hacker Robert Ehrlich <Robert.Ehrlich@inria.fr>
for his kind and insightful advice and code snippet.
Thanks to Vladimir Geogjaev <vg@wave.sio.rssi.ru> for his codeful suggestions.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2, or (at your option)
any later version.

Actually, you may also redistribute it under the bugroff license
(at your option).

Relevant URLs:
http://fare.tunes.org/files/fwprc/
http://www.linuxdoc.org/HOWTO/mini/Firewall-Piercing.html
http://www.geocities.com/SoHo/Cafe/5947/bugroff.html

$Id: cotty.c,v 1.16 2001/10/01 16:12:01 fare Exp $
*/

#define COTTY_VERSION "0.4c"

/* INSTALLATION

Here is a sample installation script. YMMV.

gunzip < cotty-*.c.gz > cotty.c
$EDITOR cotty.c		# if you want to modify the configuration
			# see CONFIGURATION below
gcc -Os -fomit-frame-pointer -Wall -W -s cotty.c -o cotty
			# if it fails to compile on your setup,
			# then edit the configuration and retry
install -s -m 755 -o root -g root cotty /usr/local/bin/

if GNU install is not available, try the following instead:
strip cotty
cp cotty /usr/local/bin/

*/

/* INVOCATION

e.g.
cotty -- chat -f stupid.questions -- chat -f stupid.answers
(commands are run each in a tty with data from any one being sent to the other)

or
cotty ++ command using -- as separator ++ other command
(same, allowing for use of -- in command)

or
cotty lAm3_SePaRaT0R stupid command lAm3_SePaRaT0R stupider command
(actually, the first non-option is considered as the separator).
(Option -n specifies the same "normal" behavior as lack of option).

or
cotty -c -- telnet remote.host -- sh -c 'exec minicom -o -p $COTTY_TTY'
(second command is run on current terminal instead of the second pseudo tty)

or
cotty -1 command
(only one command is run, on a terminal whose master is connected
to cotty's current stdin and stdout)

or
cotty -p command
(only one command is run, controlling a pseudo tty master,
and the name of the slave tty being printed on stdout;
cotty exits immediately afterwards. Mostly same as pty-redir.c)

or
cotty -d -- program on tty -- program on pty
two commands are run, first one on the tty, the second one on the pty.
(should have been the other way round, but who cares enough to break existing
documentation? If someone wants to clean the issue, add a new option).

or Use The Source, Luke! to see how it all works

or see the fwprc program and the Firewall-Piercing mini-HOWTO

Every command is forked with environment variable COTTY_COTTY being defined
to be the other command's TTY and COTTY_TTY its own TTY. With option -c,
the second command has to explicitly do something using COTTY_TTY. With
option -1, only the name of the cotty-taken tty is given, in COTTY_TTY
for the first command, and in COTTY_COTTY for the second command.

Remark that commands are executed with options as given to cotty. They are
NOT implicitly sent to a shell for interpretation, although you can explicitly
use sh -c '... $COTTY_TTY ...' as a valid command to be spawned by cotty.
In particular, putting whole commands inside quotes will NOT work.
BAD: cotty -- "pppd 1.2.3.4:1.2.3.5" -- "ssh -t foo@bar pppd"
GOOD, but overly complex: cotty -- sh -c "$LOCAL_PPPD" -- sh -c "$SSH_COMMAND"
GOOD: cotty -- pppd 1.2.3.4:1.2.3.5 -- ssh -t foo@bar pppd
Also, in a remote command, be sure that you specify the full path for any
command that is not in the standard path, as ssh or rlogin won't setup your
usual path for you.
*/

/* HISTORY
   19980604	cotty 0.1
	Initial release of a working version.
	pty scanning (thanks to Robert Ehrlich <Robert.Ehrlich@inria.fr>
	for initial code).
	select-based proxy (thanks to vntty authors for code snippet).
	stty_raw. user-defined separator. heavy debug mode.
	signal handling to kill the whole family of cotty-related processes.
	nullfds to prevent problems when some of fd=0,1,2 not open.
   19980623	cotty 0.2
	fork-based proxy (thanks to Robert Ehrlich <Robert.Ehrlich@inria.fr>
	for insightful suggestion).
   19980718	cotty 0.2b
   19980809	cotty 0.3
	Source reformatted in linux kernel C style.
	Skeletal support for further portability to other platforms than linux.
	Most functions, including main(), split into smaller inline functions.
	Handling of tty opening failure when pty successfully open.
	Multiple running modes: -n -c -1 -d (thanks to Vladimir Geogjaev
	<vg@wave.sio.rssi.ru> for suggestion, initial code, debugging).
	History added. version() and usage() added.
   19990118	cotty 0.3a
	support for UNIX98 PTYs.
   20000420	cotty 0.3b
	TCSADRAIN instead of TCSANOW
   20001102	cotty 0.4
	parse_command_line was missing support for -d direct mode. Fixed.
	stty_raw was incorrectly setting output speed to 0. Fixed.
	(normal pseudo-speed is 38400; 0 has special semantics.)
	A few comments and messages added while debugging.
	PTY_UNIX98 is now the default.
	-p will exit immediately after printing argument.
	try to find OPEN_MAX in linux/limits.h instead of limits.h
   20001119	cotty 0.4a
	Half-assed modifications so as to get cotty to work on Slowaris 2.8.
	Most notably renamed linux-specific PTY_UNIX98 to PTY_UNIX98_LINUX,
	and implemented a (hopefully) standards-compliant PTY_UNIX98 mode.
	Discovered cfmakeraw under glibc, but there seems to be no working
	equivalent (even done manually) under ScumOS; thus, implementation of
	stty_raw on ScumOS 5.8 is still very unsatisfactory.
	Thanks to Erik van Oosten <e.vanoosten@chello.nl> for feedback.
   20010125	cotty 0.4b
	Port to FreeBSD 4.x thanks to Jeff Ito <jeffi@rcn.com>.
   20010930	cotty 0.4c
	The FreeBSD port worked on OpenBSD 2.9 by just enabling the #ifdef.
*/

/* NOTES
   Though this program comes with no warranty whatsoever,
   it has been carefully designed and implemented from the beginning
   with a strong concern for robustness, with the hope that it be usable
   in a (possibly setuid root) IP connection script
   (see fwprc and the Firewall-Piercing mini-HOWTO for examples).
   So as to be as robust as possible, we strive to catch all possible errors
   or weird conditions. C just sucks at that. POSIX makes it worse.
   If you find a bug or weakness or security hole in the program,
   please do tell so we can fix it.
*/

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/* CONFIGURATION
*/

/* Choose what pty/tty opening/naming interface is to be used. */
#undef PTY_BSD_64	/* BSD naming for 64 ptys */
#undef PTY_BSD_256	/* linux-extended BSD naming for 256 ptys */
#undef PTY_UNIX98_LINUX	/* UNIX98 ptys, using Linux internals, for old glibc */
#define PTY_UNIX98	/* UNIX98 ptys, the portable X/Open way */

/* Choose either one of the twos proxies */
#define USE_FORK
#undef USE_SELECT

/* When using USE_FORK, you can define a path for an external cat to exec,
   or #undef CAT_PATH to use an internal version. For some unknown reason,
   /bin/cat doesn't seem to work correctly for our purposes, so this feature
   is experimental. I'd very much appreciate explanations as to why it
   doesn't work... (most likely some buffering is done that shouldn't;
   maybe some stty would help),
*/
/*#define CAT_PATH "/bin/cat"*/
#define CAT_PATH "cat"
#undef CAT_PATH

/* Define if cotty should try to reset the pty and tty to canonical state */
/* If not, and the kernel doesn't either, it's up to your application. */
#define USE_STTY_RAW

/* enable or disable debugging */
#define DEBUG
#undef DEBUG

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/* system-dependent include files and definitions */
/*
If you're not running one of the supported systems below, you lose.
But then, since this program is GPL, you gain the right to modify the program
so it works on yer fav'rite platform, and send me the diff's!
Supported systems:

GNU/Linux
FreeBSD, NetBSD, OpenBSD
Solaris 2 (without termios resetting)
*/

#if defined(__linux__)
/* Works on Linux with GLIBC 2. Not tested in other settings. */
#define _XOPEN_SOURCE
#define _GNU_SOURCE
#include <sys/ioctl.h>
#include <sys/types.h>
#include <linux/limits.h>
#define LEAVE_PROCESS_GROUP() setpgrp()

#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
/* Reported to work on FreeBSD 4.x and OpenBSD 2.9 */
#define PTY_BSD_64
#undef PTY_BSD_256
#undef PTY_UNIX98_LINUX
#undef PTY_UNIX98
#define LEAVE_PROCESS_GROUP() setpgid(0,0)

#elif defined(__sun__) && defined(__svr4__)
/* Minimally tested on Solaris 2.8 (SunOS 5.8) */
#define NEED_CFMAKERAW /* This glibc function was not present */
#undef USE_STTY_RAW /* couldn't get it to work properly. */
#define LEAVE_PROCESS_GROUP() setpgrp()

#else
#  error Unknown Operating System.
#  error You must modify the source right above this error to port cotty.
#endif

/**** NO LUSER-SERVICEABLE PARTS BELOW ****/
/* That said, if you're reading this, you're probably a hacker,
   not just a mere luser, so go ahead proudly, although you
   hopefully won't have to modify much anything.
*/

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/* system-independent include files and definitions */
/* if one of them isn't in your system,
   then it will have to be moved to system-dependent parts,
   and/or conditionally included depending on system-dependent flags.
*/
#include <signal.h>
#include <unistd.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/wait.h>
#include <limits.h>

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */

#if defined(USE_SELECT)
#  define XXFORK(x)
#  define XXSELECT(x) x
#elif defined(USE_FORK)
#  define XXFORK(x) x
#  define XXSELECT(x)
#else
#  error Neither USE_FORK nor USE_SELECT defined!!!
#endif

#ifdef DEBUG
#define DBG(x) x
#else
#define DBG(x)
#endif

#define MSG(x...) DBG(errprintf(x))

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
#define errprintf(msg...) fprintf(stderr, msg)
#define abort_with(code,msg...) do { \
	errprintf(msg); \
	exit(code); \
	} while (0)


/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/*
 * PTY handling.
 * We ain't do no locking or grantpt().
 * Maybe we should? That'll be for cotty 0.4!
 */

#if defined(PTY_BSD_64) || defined(PTY_BSD_256)
#define PTY_BSD
static char ttydev[] = "/dev/ptyLN";
/*		        0123456789	*/

#if defined(PTY_BSD_256)
static char letter[] = "pqrstuvwxyzabcde";
#elif defined(PTY_BSD_64)
static char letter[] = "pqrs";
#endif

static char num [] =   "0123456789abcdef";
#define LETTER 8
#define NUM 9
#define TYPE 5
#define TTYLEN 11

static inline int primitive_get_master_pty(int mode)
{
	static int li = 0, ni = -1;
	int fd;

	MSG("pgmp %d %d\n",li,ni);
	ttydev[TYPE] = 'p';
	while (letter[li] != 0) {
		ttydev[LETTER] = letter[li];
		while (num[++ni] != 0) {
			ttydev[NUM] = num[ni];
			MSG("trying %s...",ttydev);
			fd = open(ttydev, mode);
			if (fd >= 0) {
				MSG(" good!\n");
				return fd;
			}
			MSG(" bad!\n");
		}
		ni = -1;
		li++ ;
	}
	li = 0;
	return -1;
}

/* pfdp, tfdp, pfnp, tfnp are pointers to recipients for function results:
   pty fd, tty fd, pty name, tty name.
   The latter two must have space for TTYLEN characters */
static inline int get_pty_tty(int* pfdp, int* tfdp, char* pfnp, char* tfnp)
{
	int pfd,tfd;

	MSG("gpt\n");
	while ( (pfd = primitive_get_master_pty(O_RDWR)) != -1 ) {
		if (pfnp) { strcpy(pfnp,ttydev); }
		ttydev[TYPE] = 't' ;
		MSG("trying %s... ",ttydev);
		tfd = open(ttydev,O_RDWR);
		if (tfd != -1) {
			MSG("good!\n");
			if (tfnp) { strcpy(tfnp,ttydev); }
			if (pfdp) { *pfdp = pfd; }
			if (tfdp) { *tfdp = tfd; }
			return 0;
		} else {
			MSG("bad!\n");
			close(pfd);
		}
	}
	return -1;
}

#elif defined(PTY_UNIX98_LINUX)

#define MAX_PTSNUM 9999

static char ptydev[] = "/dev/ptmx";
static char ttydev[] = "/dev/pts/XXXX";
/*        0123456789012*/
#define NUM 9
#define TTYLEN 14

/* pfdp, tfdp, pfnp, tfnp are pointers to recipients for function results:
   pty fd, tty fd, pty name, tty name.
   The latter two must have space for TTYLEN characters */
static inline int get_pty_tty(int* pfdp, int* tfdp, char* pfnp, char* tfnp)
{
  int pfd,tfd;
  int ptsnum, res;
  static const int unlock = 0;
  const int mode = O_RDWR;

  MSG("gpt\n");

  pfd = open(ptydev, mode);
  if (pfd == -1) {
    goto early_fail;
  }

  /* unlock the pty. Is this really necessary??? */
  res = ioctl(pfd, TIOCSPTLCK, &unlock);
  if ( res == -1 ) {
    goto fail;
  }

  res = ioctl(pfd, TIOCGPTN, &ptsnum);
  if ( (res == -1) || (ptsnum < 0) || (ptsnum > MAX_PTSNUM) ) {
    goto fail;
  }
  sprintf(ttydev+NUM,"%d",ptsnum);

  MSG("trying %s... ",ttydev);
  tfd = open(ttydev,O_RDWR);
  if (tfd == -1) {
    goto fail;
  }

  MSG("done!\n");
  if (pfnp) { strcpy(pfnp,ptydev); }
  if (tfnp) { strcpy(tfnp,ttydev); }
  if (pfdp) { *pfdp = pfd; }
  if (tfdp) { *tfdp = tfd; }
  return 0;

fail:
  close(pfd);
early_fail:
  MSG("failed!\n");
  return -1;
}

#elif defined(PTY_UNIX98)

#define MAX_PTSNUM 9999

static char ptydev[] = "/dev/ptmx";
/* 14 should be enough for /dev/pts/XXXX ; 32 is plenty  */
#define TTYLEN 32

/* pfdp, tfdp, pfnp, tfnp are pointers to recipients for function results:
   pty fd, tty fd, pty name, tty name.
   The latter two must have space for TTYLEN characters */
static inline int get_pty_tty(int* pfdp, int* tfdp, char* pfnp, char* tfnp)
{
	int pfd,tfd;
	int res;
	char *ttydev;

	MSG("gpt\n");

	/* get the master */
	pfd = open(ptydev, O_RDWR);
	if (pfd == -1) {
		goto early_fail;
	}

	/* grant the slave */
	res = grantpt(pfd);
	if (res == -1) {
		goto fail;
	}

	/* unlock the pty */
	res = unlockpt(pfd);
	if ( res == -1 ) {
		goto fail;
 	}
	
	ttydev = ptsname(pfd);
	if ( (ttydev == NULL) || (strlen(ttydev) > TTYLEN) ) {
		goto fail;
	}

	MSG("trying %s... ",ttydev);
	tfd = open(ttydev,O_RDWR);
	if (tfd == -1) {
		goto fail;
	}

	MSG("done!\n");
	if (pfnp) { strcpy(pfnp,ptydev); }
	if (tfnp) { strcpy(tfnp,ttydev); }
	if (pfdp) { *pfdp = pfd; }
	if (tfdp) { *tfdp = tfd; }
	return 0;

 fail:
	close(pfd);
 early_fail:
	MSG("failed!\n");
	return -1;
}

#else
#  error No PTY handling specified.
#  error Define one of PTY_BSD_64, PTY_BSD_256, PTY_UNIX98_LINUX, PTY_UNIX98.
#endif

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */

static void drop_tty (void)
{
	int fd;

	close(0);
	close(1);
	/*close(2);*/
	fd = open("/dev/tty",O_RDWR);
	if (fd != -1) {
		ioctl(fd, TIOCNOTTY);
		close(fd);
	}
	LEAVE_PROCESS_GROUP();
}

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */

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

static inline void stty_raw (int fd)
{
	static struct termios tios;
	static int err;

	err = tcgetattr(fd, &tios);
	if (err) {
		perror("stty_raw(get)");
		MSG("error getting fd %d\n",fd);
	}
	cfmakeraw(&tios);
	err = tcsetattr(fd, TCSADRAIN, &tios);
	if (err) {
		perror("stty_raw(set)");
		MSG("error setting fd %d\n",fd);
	}
}

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */

static volatile pid_t cpid[4];
static volatile int ncpid = 0;

static void add_cpid (pid_t x)
{
	MSG("register pid: %d\n",x);
	/* XXX: here, block signals */
	cpid[ncpid++] = x ;
	/* XXX: here, restore signals */
}
static void killall (void)
{
	int i;

	for (i=0;i<ncpid;i++) {
		kill(cpid[i],SIGTERM);
		kill(cpid[i],SIGHUP);
		killpg(cpid[i],SIGTERM);
		killpg(cpid[i],SIGHUP);
	}
}

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/* signal handlers */

static void sigchld (int i)
{
	int	pid, status;

	(void)i; /* tell GCC we use the argument. */

	while ( (pid = waitpid(-1,&status,WNOHANG)) > 0 ) {
		MSG("pid %d exited with status %d\n",pid,status);
#if 1
		killall();
		exit(WEXITSTATUS(status));
#endif
	}
}

static void terminate (int i)
{
	killall();
	exit(-i);
}

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/* fork()-based version of the proxy */

#ifdef USE_FORK

#ifndef CAT_PATH
/* This is really how cat works. My, I do hate C! */
#define CATBUFSZ 4096
static volatile void cat (void)
{
	char buf[CATBUFSZ];
	static ssize_t n, p, q;
	while (1) {
		n = read(0,buf,CATBUFSZ);
		if (n != -1) {
			for (p=0;p<n;p+=q) {
				q = write(1,buf+p,n-p);
				if (q == -1) { q = 0 ; }
			}
		}
	}
}
#else
/* For some reason, /bin/cat(1) does buffering in a way
   that doesn't seem to work with my regression test suite,
   so don't try defining CAT_PATH :( */
static volatile void cat (void)
{
	execlp(CAT_PATH,NULL);
	perror("execlp");
	abort_with(errno,"cannot exec " CAT_PATH "\n");
}
#endif

static inline void proxy1 (int i, int o)
{
	int j;
	pid_t p;
	
	MSG("forking proxy1 (%d -> %d)\n",i,o);
	p = fork();
	switch (p) {
	case -1:
		perror("fork");
		killall();
		abort_with(-errno,
			   "cannot fork proxy1 (%d -> %d)\n", i, o);
	case 0:
		MSG("proxy1 (%d -> %d) reshuffling fds\n",i,o);
		dup2(i,0);
		dup2(o,1);
		for (j=3;j<OPEN_MAX;j++) {
			close(j);
		}
		MSG("proxy1 (%d -> %d): cat\n",i,o);
		cat();
		break;
	default: /* parent */
		add_cpid(p);
	}
}

static inline void proxy (int n, int *fd)
{
	int i;
	
	/* just fork the proxy halves */
	for (i=0;i+1<n;i+=2) {
		proxy1 (fd[i],fd[i+1]);
	}
}

#endif


/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/* select()-based version of the proxy */
#if defined(USE_SELECT)

/* Here, we use select(2)-on-read to do byte-per-byte pty-to-pty proxy.
   To achieve buffering, the proxy would have to use non-blocking I/O
   Another way to run the proxy and achieve buffering,
   would be to not use select(2), but rather to fork
   two processes, each being a stupid cat < ptyX > ptyY;
   this may or not require subtle signal handling */

static void proxy (int n, int *fd)
{
	static fd_set readfds;
#if 1
	static char buf[1];
#else
	static char buf[1024];
/*
Most programs don't work with straightforward buffering.
The solution to achieve buffering would be to use non-blocking I/O,
and use select() to detect when write is possible...
Looks pretty complicated. Try USE_FORK instead!
*/
#endif
	int i;
	
	for (;;) {
		FD_ZERO(&readfds);
		for (i=0;i<n;i+=2) {
			FD_SET(fd[i],&readfds);
		}
		if (select(32, &readfds, 0, 0, 0) <= 0) {
#if 1
			perror("select");
			exit(1);
#endif
		}
		
		for (i=0;i+1<n;i+=2) {
			if (FD_ISSET(fd[i],&readfds)) {
				/* data from source number i */
				n = read(fd[i], buf, sizeof(buf));
				MSG("read that from [%d]: ",fd[i]);
				DBG(write(2,buf,n));
				MSG("\n");
				write(fd[i+1], buf, n);
				MSG("wrote it to [%d]\n",fd[i+1]);
			}
		}
	}
}

#endif

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
enum cotty_mode_t {
	normal,		/* -n (default), two tty's with process attached */
	keep_current,	/* -c two tty's, second process not attached */
	one_shot,	/* -1 one tty with process, controlled by current io */
	pty_only,	/* -p one pty with process, tty name printed */
	direct		/* -d one pty with process, one tty with process */
};

/* Number of TTYs to open depending on mode */
static inline int num_tty (enum cotty_mode_t mode) 
{
	switch (mode) {
	case one_shot:
	case pty_only:
	case direct:
		return 1;
	case normal:
	case keep_current:
	}
	return 2;
}

/* Number of processes to fork depending on mode */
static inline int num_process (enum cotty_mode_t mode) 
{
	switch (mode) {
	case one_shot:
	case pty_only:
		return 1;
	case normal:
	case keep_current:
	case direct:
	}
	return 2;
}

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
static char *argv0;
static char *sep;
static int arg[2];
static enum cotty_mode_t mode = normal;  /* options */
static int pfd[2];
static int tfd[2];
static char pty_name[2][TTYLEN];
static char tty_name[2][TTYLEN];
static char env_tty[TTYLEN+10];		/* "COTTY_TTY=/dev/ttyp??" */
static char env_cotty[TTYLEN+12];	/* "COTTY_COTTY=/dev/ttyp??" */
static int nullfd[3];
static int proxyfds[4];

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
static inline void shuffle_fds (int process, enum cotty_mode_t mode)
{
	int j;
	
	MSG("[%d] reshuffling fds\n",process);
	switch (mode) {
	case keep_current:
		/* first process like normal; second ain't do nothing */
		if (process) {
			/* return; */ /* do not close pty/tty pair??? */
			break;
		}
	case pty_only:
	case one_shot:
		/* only called once, but otherwise like normal */
	case direct:
	case normal:
		/* drop current stdio */
		drop_tty();
		/* attach the tty to stdio */
		MSG("[%d] %d -> %d\n",process,tfd[process],0);
		dup2(tfd[process],0);
		MSG("[%d] %d -> %d\n",process,tfd[process],1);
		dup2(0,1);
		break;
	}

	/* otherwise close allocated pty/tty pair(s) */
	for (j=0;j<num_tty(mode);j++) {
		close(pfd[j]);
		close(tfd[j]);
	}
}

static inline void set_environment (int process, enum cotty_mode_t mode)
{
	(void)mode; /* tell GCC we use the argument. */
	sprintf(env_tty,"COTTY_TTY=%s",tty_name[process]);
	putenv(env_tty);
	sprintf(env_cotty,"COTTY_COTTY=%s",tty_name[1-process]);
	putenv(env_cotty);
}

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/* Display version */
static void version (FILE* o)
{
	fprintf(o,"cotty " COTTY_VERSION "\n");
}

/* Display usage */
static void usage (FILE* o)
{
	version(o);
	fprintf(o,
		"Usage: %s [-ncd] -sep- command1 -sep- command2\n"
		"or     %s -[1p] command\n"
		"or     %s [-h?]\n"
		"There ain't TFM to R, so UTSL for details. Sorry.\n",
		argv0,argv0,argv0);
}

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/* Parse command line */
static inline void parse_command_line (int argc, char **argv)
{
	char option, *optstr;

	MSG("mode = default (normal)\n");
	argv0 = argv[0];
	arg[0] = 1;
	while (arg[0]<argc) {
		optstr = argv[arg[0]];
		if (!(optstr[0]=='-'
		      && (option=optstr[1])!=0
		      && optstr[2]==0)) {
			goto no_more_options;
		}
		switch(option){
		case 'n':
			MSG("mode = normal\n");
			mode = normal;
		next_option:
			arg[0]++;
			break;
		case 'c':
			MSG("mode = keep_current\n");
			mode = keep_current;
			goto next_option;
		case '1':
			MSG("mode = one_shot\n");
			mode = one_shot;
			goto next_option;
		case 'p':
			MSG("mode = pty_only\n");
			mode = pty_only;
			goto next_option;
		case 'd':
			MSG("mode = direct\n");
			mode = direct;
			goto next_option;
		case 'v':
			MSG("mode = version\n");
			version(stdout);
		exit_0:
			exit(0);
		case 'h':
		case '?':
			MSG("mode = help\n");
			usage(stdout);
			goto exit_0;
		default:
			goto no_more_options;
		}
	}
 no_more_options:
	if ((argc-(arg[0]-1)) < ((num_process(mode)==1)?2:5)) {
		errprintf("Too few arguments.\n");
	bad_usage:
		usage(stderr);
		exit(1);
	}
	if (num_process(mode)==1) {
		arg[1] = argc;
	} else {
		sep = argv[arg[0]];
		arg[0]++;
		MSG("separator is argv[%d]=%s\n",arg[0],argv[arg[0]]);

		for (arg[1]=arg[0];arg[1]<argc;arg[1]++) {
			MSG("strcmp(strcmp(sep,argv[%d])=%d %s %s\n",
			    arg[1],strcmp(sep,argv[arg[1]]),sep,argv[arg[1]]);
			if (!strcmp(sep,argv[arg[1]])) {
				MSG("other separator at argv[%d]\n",arg[1]);
				break;
			}
		}
		if ( (arg[1] == arg[0]) || (arg[1] >= argc - 1) ) {
			MSG("arg[0]=%d, arg[1]=%d, argc=%d\n",
			    arg[0],arg[1],argc);
			errprintf("Two commands required.\n");
			goto bad_usage;
		}
		argv[arg[1]++] = NULL;
	}
	MSG("found arg[0]=%d\n",arg[0]);
	MSG("found arg[1]=%d\n",arg[1]);
}


/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/* allocate pty/tty pair(s) */

static inline void allocate_pty_tty_pairs ()
{
	static int res;
	static int i,j;

	/* be sure that 0,1,2 are in use */
	/* (this assumes the OS allocates lowest fd first) */
	for (i=0;i<3;i++) {
		nullfd[i] = open("/dev/null",O_RDONLY);
	}
	
	/* Get pty/tty pair(s) */
	for (i=0;i<num_tty(mode);i++) {
		MSG("Getting pty/tty pair %d.\n",i);
		res = get_pty_tty(&pfd[i],&tfd[i],
				 pty_name[i],tty_name[i]);
		if (res == -1) {
			abort_with(2,"Can't find free pty/tty pair\n");
		}
		MSG("found[%d] %s pfd=%d and %s tfd=%d\n",i,
		    pty_name[i],pfd[i],tty_name[i],tfd[i]);

#ifdef USE_STTY_RAW
		/* reset tty settings, if need be */
		MSG("Resetting master pseudo-tty settings.\n");
		stty_raw(pfd[i]);
		MSG("Resetting slave tty settings.\n");
		stty_raw(tfd[i]);
#endif
	}
	switch (mode) {
	case pty_only:
		printf("%s\n",tty_name[0]);
		strcpy(tty_name[1],tty_name[0]);
		strcpy(tty_name[0],pty_name[0]);
		close(tfd[0]);
		tfd[0]=pfd[0];
		pfd[0]=-1;
		pfd[1]=-1;
		tfd[1]=-1;
		pty_name[0][0] = 0;
		pty_name[1][0] = 0;
		break;
	case one_shot:
		pfd[1]=-1;
		tfd[1]=-1;
		tty_name[1][0] = 0;
		pty_name[1][0] = 0;
		break;
	case direct:
		pfd[1]=tfd[0];
		tfd[1]=pfd[0];
		strcpy(tty_name[1],pty_name[0]);
		strcpy(pty_name[1],tty_name[0]);
		break;
	case normal:
	case keep_current:
	}
	/* we don't need those null fds anymore */
	for (j=0;j<3;j++) {
		close(nullfd[j]);
	}
}

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/* fork the commands */

static inline void fork_commands (char **argv)
{
	static int i;
	static pid_t p;

	for (i=0;i<num_process(mode);i++) {
		MSG("forking command[%d]\n",i);
		p = fork();
		switch (p) {
		case -1:
			perror("fork");
			killall();
			abort_with(errno,"%s: cannot fork process [%d] (%s)\n",
				   argv0, i, argv[arg[i]]);
		case 0: /* i-th forked process */
			MSG("forked process [%d]\n",i);
			shuffle_fds(i,mode);
			set_environment(i,mode);
			MSG("Executing command [%d]: %s\n",i,argv[arg[i]]);
#if 1
			dup2(0,2);
#endif
			execvp(argv[arg[i]],&argv[arg[i]]);
			perror("execvp");
			abort_with(errno,"cannot exec command %d\n",i);
		default: /* parent */
			add_cpid(p);
		}
	}
}

/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/* run the proxy */

static inline void run_proxy (void)
{
	signal(SIGTERM, terminate);
	signal(SIGHUP, terminate);
	signal(SIGINT, terminate);
	signal(SIGCHLD, sigchld);
	sigchld(0);

	switch (mode) {
	case pty_only:
		exit(0);
	case direct:
		return;
	case one_shot:
		close(tfd[0]);
		proxyfds[0]=0;
		proxyfds[1]=pfd[0];
		proxyfds[2]=pfd[0];
		proxyfds[3]=1;
		break;
	case keep_current:
		drop_tty();
	case normal:
		close(tfd[0]);
		close(tfd[1]);
		proxyfds[0]=pfd[1];
		proxyfds[1]=pfd[0];
		proxyfds[2]=pfd[0];
		proxyfds[3]=pfd[1];
	}
	proxy(4,proxyfds);
}


/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/* entry point and main stuff */

extern int main (int argc, char **argv)
{
	MSG("cotty " COTTY_VERSION " started.\n");

	parse_command_line(argc,argv);
	allocate_pty_tty_pairs();
	fork_commands(argv);
	run_proxy();
	wait(NULL);
	terminate(0);
	return 0;
}
	
/* ------>8------>8------>8------>8------>8------>8------>8------>8------ */
/* EOF */
