[alt.sources] mtty - mimic a tty - a public domain pseudo tty utility

maart@cs.vu.nl (Maarten Litmaath) (07/25/90)

: This is a shar archive.  Extract with sh, not csh.
: This archive ends with exit, so do not worry about trailing junk.
: --------------------------- cut here --------------------------
PATH=/bin:/usr/bin:/usr/ucb
echo Extracting 'data_flow.c'
sed 's/^X//' > 'data_flow.c' << '+ END-OF-FILE ''data_flow.c'
X#include	"extern.h"
X
X
Xvoid	data_flow(pty, tty, death)
Xint	pty, tty, *death;
X{
X	fd_set	rfds, wfds;
X	int	width, flags, left = 0, status = 0;
X
X
X	width = getdtablesize();
X	(void) signal(SIGPIPE, SIG_IGN);
X
X	flags = fcntl(pty, F_GETFL, 0);
X	flags |= FNDELAY;
X	fcntl(pty, F_SETFL, flags);
X
X	for (;;) {
X		FD_ZERO(&rfds);
X		FD_ZERO(&wfds);
X		FD_SET(death[0], &rfds);
X		if (Do_stdin) {
X			/*
X			 * If there is nothing left from a previous read from
X			 * stdin, wait for data to arrive on stdin, else wait
X			 * till the pty becomes writable again.
X			 * left < 0 denotes an error has occurred.
X			 */
X			if (left == 0)
X				FD_SET(0, &rfds);
X			else if (left > 0)
X				FD_SET(pty, &wfds);
X		}
X		if (Do_stdout && status == 0)
X			FD_SET(pty, &rfds);
X		if (select(width, &rfds, &wfds, (fd_set *) 0,
X			(struct timeval *) 0) < 0) {
X			/*
X			 * `impossible'
X			 */
X			fprintf(stderr, "%s: select: %s\n", Prog,
X				strerror(errno));
X			exit(1);
X		}
X		/*
X		 * If our parent has detected the death of the other child,
X		 * death[0] will be ready for reading.
X		 */
X		if (FD_ISSET(death[0], &rfds))
X			status = do_stdout(pty, 1);
X		if (FD_ISSET(pty, &rfds))
X			status = do_stdout(pty, 0);
X		if (FD_ISSET(pty, &wfds) || FD_ISSET(0, &rfds))
X			left = do_stdin(pty, tty);
X	}
X}
+ END-OF-FILE data_flow.c
chmod 'u=rw,g=r,o=r' 'data_flow.c'
set `wc -c 'data_flow.c'`
count=$1
case $count in
1255)	:;;
*)	echo 'Bad character count in ''data_flow.c' >&2
		echo 'Count should be 1255' >&2
esac
echo Extracting 'do_child.c'
sed 's/^X//' > 'do_child.c' << '+ END-OF-FILE ''do_child.c'
X#include	"extern.h"
X
X
Xvoid	do_child(pty, tty, name, message, argp)
Xint	pty, tty, *message;
Xchar	*name, **argp;
X{
X	int	fd;
X	char	c;
X
X
X	/*
X	 * Wait for a message that all is well, i.e. our parent has been
X	 * able to create another pipe and to fork again.
X	 */
X	close(message[1]);
X	if (read(message[0], &c, 1) != 1)
X		exit(1);
X	close(message[0]);
X
X	close(pty);
X	if (Do_stdin)
X		dup2(tty, 0);
X	if (Do_stdout)
X		dup2(tty, 1);
X	if (Do_stderr)
X		dup2(tty, 2);
X	close(tty);
X
X	if (Mode == INTERACTIVE) {
X		if ((fd = open("/dev/tty", 1)) >= 0) {
X			/*
X			 * Relinquish controlling tty.
X			 */
X			ioctl(fd, TIOCNOTTY, (caddr_t) 0);
X			close(fd);
X		} else
X			setpgrp(0, 0);
X		/*
X		 * Acquire new controlling tty.
X		 * Our process group has been set to 0, either implicitly
X		 * by the TIOCNOTTY ioctl(), or explicitly by the setpgrp().
X		 * When we reopen the pseudo terminal, it will become our
X		 * controlling tty and our process group gets set to the tty
X		 * process group.  If the latter had not been set yet, it
X		 * will have been set to our process id first.
X		 */
X		if ((fd = open(name, 2)) < 0) {
X			fprintf(stderr, "%s: open %s failed: %s\n",
X				Prog, name, strerror(errno));
X			exit(1);
X		}
X		close(fd);
X	}
X
X	execvp(*argp, argp);
X	fprintf(stderr, "%s: cannot exec %s: %s\n",
X		Prog, *argp, strerror(errno));
X	exit(1);
X}
+ END-OF-FILE do_child.c
chmod 'u=rw,g=r,o=r' 'do_child.c'
set `wc -c 'do_child.c'`
count=$1
case $count in
1323)	:;;
*)	echo 'Bad character count in ''do_child.c' >&2
		echo 'Count should be 1323' >&2
esac
echo Extracting 'do_parent.c'
sed 's/^X//' > 'do_parent.c' << '+ END-OF-FILE ''do_parent.c'
X#include	"extern.h"
X
X
Xvoid	do_parent(pty, tty, message)
Xint	pty, tty, *message;
X{
X	union	wait	w, dummy;
X	int	child, n, death[2];
X	char	c = '!';
X
X
X	close(message[0]);
X
X	if (pipe(death) < 0) {
X		fprintf(stderr, "%s: cannot create a pipe: %s\n",
X			Prog, strerror(errno));
X		exit(1);
X	}
X
X	switch (child = fork()) {
X	case -1:
X		fprintf(stderr, "%s: cannot fork: %s\n",
X			Prog, strerror(errno));
X		exit(1);
X	case 0:
X		close(message[1]);
X		close(death[1]);
X		data_flow(pty, tty, death);
X	}
X
X	/*
X	 * Send a message to the other child, indicating all is well.
X	 */
X	write(message[1], &c, 1);
X	close(message[1]);
X	close(death[0]);
X
X	close(pty);
X	close(tty);
X
X	while ((n = wait(&w)) != Pid)
X		if (n == child)
X			child = -1;
X	/*
X	 * To signal the child that the process has died, we close our side
X	 * of the pipe.  A read() in the child will return 0 immediately.
X	 * Then wait for the child to finish the IO.
X	 */
X	if (child != -1) {
X		close(death[1]);
X		while (wait(&dummy) != child)
X			;
X	}
X
X	exit(w.w_termsig ? w.w_termsig + 0200 : w.w_retcode);
X}
+ END-OF-FILE do_parent.c
chmod 'u=rw,g=r,o=r' 'do_parent.c'
set `wc -c 'do_parent.c'`
count=$1
case $count in
1043)	:;;
*)	echo 'Bad character count in ''do_parent.c' >&2
		echo 'Count should be 1043' >&2
esac
echo Extracting 'do_stdin.c'
sed 's/^X//' > 'do_stdin.c' << '+ END-OF-FILE ''do_stdin.c'
X#include	"extern.h"
X
X
Xint	do_stdin(pty, tty)
Xint	pty, tty;
X{
X	static	char	buf[MAXBUF], *leftbuf;
X	static	int	left = 0, first = 1;
X	static	void	reset_tty();
X	int	pass2 = 0;
X
X
X	for (;;) {
X		if (left < 0 || left > 0
X			&& (left = to_pty(pty, &leftbuf, left)) != 0 || pass2)
X			return left;
X
X		leftbuf = buf;
X		if ((left = read(0, buf, MAXBUF)) < 0)
X			fprintf(stderr, "%s: read stdin: %s\n",
X				Prog, strerror(errno));
X		else if (left == 0) {
X			if (Mode == INTERACTIVE && first) {
X				reset_tty(tty);
X				first = 0;
X			}
X			*buf = Tc[Mode].t_eofc;
X			left = 1;
X		}
X		pass2 = 1;
X	}
X}
X
X
Xstatic	void	reset_tty(tty)
Xint	tty;
X{
X	ioctl(tty, TIOCGETP, (caddr_t) &Sg[INTERACTIVE]);
X	if (Sg[INTERACTIVE].sg_flags & (RAW | CBREAK)) {
X		Sg[INTERACTIVE].sg_flags &= ~(RAW | CBREAK);
X		ioctl(tty, TIOCSETN, (caddr_t) &Sg[INTERACTIVE]);
X	}
X	ioctl(tty, TIOCGETC, (caddr_t) &Tc[INTERACTIVE]);
X	if (Tc[INTERACTIVE].t_eofc == -1 || Tc[INTERACTIVE].t_eofc == 0) {
X		Tc[INTERACTIVE].t_eofc = Eofc;
X		ioctl(tty, TIOCSETC, (caddr_t) &Tc[INTERACTIVE]);
X	}
X}
+ END-OF-FILE do_stdin.c
chmod 'u=rw,g=r,o=r' 'do_stdin.c'
set `wc -c 'do_stdin.c'`
count=$1
case $count in
1034)	:;;
*)	echo 'Bad character count in ''do_stdin.c' >&2
		echo 'Count should be 1034' >&2
esac
echo Extracting 'do_stdout.c'
sed 's/^X//' > 'do_stdout.c' << '+ END-OF-FILE ''do_stdout.c'
X#include	"extern.h"
X
X
Xint	do_stdout(pty, child_died)
Xint	pty, child_died;
X{
X	char	buf[MAXBUF];
X	int	n;
X
X
X	for (;;) {
X		if ((n = read(pty, buf, (int) sizeof buf)) <= 0)
X			exit(!child_died);
X		if (write(1, buf, n) < 0) {
X			if (errno == EPIPE) {
X				if (child_died)
X					exit(1);
X				kill(Pid, SIGPIPE);
X			} else
X				fprintf(stderr, "%s: write stdout: %s\n",
X					Prog, strerror(errno));
X			return -1;
X		}
X		if (!child_died)
X			break;
X	}
X	return 0;
X}
+ END-OF-FILE do_stdout.c
chmod 'u=rw,g=r,o=r' 'do_stdout.c'
set `wc -c 'do_stdout.c'`
count=$1
case $count in
451)	:;;
*)	echo 'Bad character count in ''do_stdout.c' >&2
		echo 'Count should be 451' >&2
esac
echo Extracting 'extern.c'
sed 's/^X//' > 'extern.c' << '+ END-OF-FILE ''extern.c'
X#include	"extern.h"
X
X#undef	CTRL
X#define		CTRL(x)		((x) & 037)
X#undef	DEL
X#define		DEL		0177
X
Xint	Lw[] = {
X	(LLITOUT | LPASS8),
X	(LCRTERA | LCRTKIL | LCTLECH)
X};
Xstruct	sgttyb	Sg[] = {
X	{ B9600, B9600, -1, -1, 0 },
X	{ B9600, B9600, CTRL('H'), CTRL('U'), ECHO | CRMOD | ANYP | XTABS }
X};
Xstruct	tchars	Tc[] = {
X	{ -1, -1, -1, -1, CTRL('D'), -1 },
X	{ DEL, CTRL('\\'), CTRL('S'), CTRL('Q'), CTRL('D'), -1 }
X};
Xstruct	ltchars	Ltc[] = {
X	{ -1, -1, -1, -1, -1, -1 },
X	{ CTRL('Z'), CTRL('Y'), CTRL('R'), CTRL('O'), CTRL('W'), CTRL('V') }
X};
Xstruct	winsize	Win = {
X	24, 80, 0, 0
X};
Xchar	Eofc = -1;
Xint	Mode = !INTERACTIVE;
Xint	Do_stdin = 0, Do_stdout = 0, Do_stderr = 0;
Xint	Pid;
Xchar	*Prog;
+ END-OF-FILE extern.c
chmod 'u=rw,g=r,o=r' 'extern.c'
set `wc -c 'extern.c'`
count=$1
case $count in
684)	:;;
*)	echo 'Bad character count in ''extern.c' >&2
		echo 'Count should be 684' >&2
esac
echo Extracting 'main.c'
sed 's/^X//' > 'main.c' << '+ END-OF-FILE ''main.c'
X#include	"extern.h"
X
X
Xstatic	char	Usage[] =
X		"Usage: %s [-i] [-012] [-E \\ooo] [-V] [--] command\n";
X
X
Xstatic	void	usage()
X{
X	fprintf(stderr, Usage, Prog);
X	exit(2);
X}
X
X
Xstatic	void	geteofc(p)
Xchar	*p;
X{
X	int	i;
X
X
X	if (!p || sscanf(p, "\\%o", &i) != 1)
X		usage();
X	if (i == -1 || i == 0) {
X		fprintf(stderr, "%s: the EOF character cannot be -1 or 0\n",
X			Prog);
X		exit(2);
X	}
X	Eofc = i;
X}
X
X
Xmain(argc, argv)
Xint	argc;
Xchar	**argv;
X{
X	int	stop = 0, pty, tty, message[2];
X	char	name[PTYMAXNAME], **argp, *p, c;
X
X
X	Prog = argv[0];
X
X	for (argp = &argv[1]; !stop && *argp && **argp == '-'; ++argp)
X		for (p = *argp + 1; c = *p; ++p)
X			switch (c) {
X			case 'E':
X				geteofc(*++argp);
X				break;
X			case 'V':
X				printf("%s\n", Version);
X				exit(0);
X			case 'i':
X				Mode = INTERACTIVE;
X				break;
X			case '0':
X				Do_stdin = 1;
X				break;
X			case '1':
X				Do_stdout = 1;
X				break;
X			case '2':
X				Do_stderr = 1;
X				break;
X			case '-':
X				stop = 1;
X				break;
X			default:
X				usage();
X			}
X
X	if (!(Do_stdin | Do_stdout | Do_stderr)) {
X		Do_stdin = Do_stdout = 1;		/* default */
X		if (Mode == INTERACTIVE)
X			Do_stderr = 1;
X	}
X
X	if (!*argp)
X		usage();
X
X	if ((pty = getpty(&tty, name)) < 0) {
X		fprintf(stderr, "%s: cannot allocate a pty.  Exit.\n", Prog);
X		exit(1);
X	}
X
X	set_modes(tty);
X
X	if (pipe(message) < 0) {
X		fprintf(stderr, "%s: cannot create a pipe: %s\n",
X			Prog, strerror(errno));
X		exit(1);
X	}
X
X	switch (Pid = fork()) {
X	case -1:
X		fprintf(stderr, "%s: cannot fork: %s\n",
X			Prog, strerror(errno));
X		exit(1);
X	case 0:
X		do_child(pty, tty, name, message, argp);
X	}
X
X	do_parent(pty, tty, message);
X}
+ END-OF-FILE main.c
chmod 'u=rw,g=r,o=r' 'main.c'
set `wc -c 'main.c'`
count=$1
case $count in
1614)	:;;
*)	echo 'Bad character count in ''main.c' >&2
		echo 'Count should be 1614' >&2
esac
echo Extracting 'set_modes.c'
sed 's/^X//' > 'set_modes.c' << '+ END-OF-FILE ''set_modes.c'
X#include	"extern.h"
X
X
Xvoid	set_modes(tty)
Xint	tty;
X{
X	int	fd, devtty;
X
X
X	if (Mode == INTERACTIVE) {
X		if ((fd = devtty = open("/dev/tty", 0)) < 0)
X			fd = Do_stdin ? Do_stdout ? 2 : 1 : 0;
X		if (ioctl(fd, TIOCLGET, (caddr_t) &Lw[INTERACTIVE]) == 0) {
X			ioctl(fd, TIOCGETC, (caddr_t) &Tc[INTERACTIVE]);
X			ioctl(fd, TIOCGLTC, (caddr_t) &Ltc[INTERACTIVE]);
X			ioctl(fd, TIOCGETP, (caddr_t) &Sg[INTERACTIVE]);
X			Sg[INTERACTIVE].sg_flags |= ECHO;
X			Sg[INTERACTIVE].sg_flags &= ~(RAW | CBREAK);
X			ioctl(fd, TIOCGWINSZ, (caddr_t) &Win);
X			ioctl(tty, TIOCSWINSZ, (caddr_t) &Win);
X			if (devtty >= 0)
X				close(devtty);
X		}
X	} else
X		ioctl(tty, TIOCEXCL, (caddr_t) 0);
X		/*
X		 * Put the tty into exclusive use.  There is a race
X		 * condition, but this ioctl() is better than nothing,
X		 * I guess.  Opening the master should set the access mode
X		 * of the slave (via the third argument to open(2))!
X		 * Furthermore opening the tty side before the pty side
X		 * should be impossible.
X		 */
X
X	if (Eofc != -1)
X		Tc[Mode].t_eofc = Eofc;
X	else
X		Eofc = Tc[Mode].t_eofc;
X
X	ioctl(tty, TIOCLSET, (caddr_t) &Lw[Mode]);
X	ioctl(tty, TIOCSETC, (caddr_t) &Tc[Mode]);
X	ioctl(tty, TIOCSLTC, (caddr_t) &Ltc[Mode]);
X	ioctl(tty, TIOCSETP, (caddr_t) &Sg[Mode]);
X}
+ END-OF-FILE set_modes.c
chmod 'u=rw,g=r,o=r' 'set_modes.c'
set `wc -c 'set_modes.c'`
count=$1
case $count in
1245)	:;;
*)	echo 'Bad character count in ''set_modes.c' >&2
		echo 'Count should be 1245' >&2
esac
echo Extracting 'to_pty.c'
sed 's/^X//' > 'to_pty.c' << '+ END-OF-FILE ''to_pty.c'
X#include	"extern.h"
X
X
X/*
X * Try to write `n' bytes to `pty', starting at `*buf'.
X * Increment `*buf' with the number of bytes actually written.
X * Return the number of bytes still to be written, -1 on error.
X */
Xint	to_pty(pty, buf, n)
Xint	pty, n;
Xchar	**buf;
X{
X	register char	*p;
X	register int	m;
X	char	*q;
X	int	backslash;
X
X
X	for (backslash = 0; n > 0 && !backslash; *buf += m, n -= m) {
X		/*
X		 * Write 1 line at a time.
X		 */
X		for (p = *buf, m = n; m > 0 && *p++ != '\n'; m--)
X			;
X		if (*--p == '\\') {
X			for (q = p; p > *buf && *--p == '\\'; )
X				;
X			m = q - p;
X			if (*p == '\\')
X				m++;
X			/*
X			 * Only if the number of backslashes ending the
X			 * block is uneven, we have to watch out: the tty
X			 * driver translates a backslash-EOF sequence to a
X			 * literal EOF character.
X			 */
X			p = (m & 1) ? q : q + 1;
X			backslash = 1;
X		} else
X			p++;
X		if ((m = write(pty, *buf, p - *buf)) < 0)
X			return (errno == EWOULDBLOCK) ? n : -1;
X	}
X
X	/*
X	 * If the last chunk didn't end in a `break',
X	 * write an EOF character if we're not interactive.
X	 */
X	if (*--p != '\n' && *p != Eofc && Mode != INTERACTIVE)
X		if (write(pty, &Eofc, 1) < 0)
X			return (errno == EWOULDBLOCK) ? n : -1;
X
X	/*
X	 * Write the final backslash.
X	 */
X	if (backslash) {
X		if (write(pty, *buf, 1) < 0)
X			return (errno == EWOULDBLOCK) ? n : -1;
X		*buf += 1;
X		n -= 1;
X	}
X
X	return n;
X}
+ END-OF-FILE to_pty.c
chmod 'u=rw,g=r,o=r' 'to_pty.c'
set `wc -c 'to_pty.c'`
count=$1
case $count in
1366)	:;;
*)	echo 'Bad character count in ''to_pty.c' >&2
		echo 'Count should be 1366' >&2
esac
echo Extracting 'version.c'
sed 's/^X//' > 'version.c' << '+ END-OF-FILE ''version.c'
Xchar	Version[] =
X	"@(#)mtty 1.1 90/07/24 Maarten Litmaath @ VU Dept. of CS, Amsterdam";
+ END-OF-FILE version.c
chmod 'u=rw,g=r,o=r' 'version.c'
set `wc -c 'version.c'`
count=$1
case $count in
88)	:;;
*)	echo 'Bad character count in ''version.c' >&2
		echo 'Count should be 88' >&2
esac
echo Extracting 'strerror.c'
sed 's/^X//' > 'strerror.c' << '+ END-OF-FILE ''strerror.c'
Xchar	*strerror(n)
Xint	n;
X{
X	static	char	buf[32];
X	extern	int	sys_nerr;
X	extern	char	*sys_errlist[];
X
X
X	if ((unsigned) n < sys_nerr)
X		return sys_errlist[n];
X	(void) sprintf(buf, "Unknown error %d", n);
X	return buf;
X}
+ END-OF-FILE strerror.c
chmod 'u=rw,g=r,o=r' 'strerror.c'
set `wc -c 'strerror.c'`
count=$1
case $count in
217)	:;;
*)	echo 'Bad character count in ''strerror.c' >&2
		echo 'Count should be 217' >&2
esac
echo Extracting 'getpty.c'
sed 's/^X//' > 'getpty.c' << '+ END-OF-FILE ''getpty.c'
X#include	<sys/file.h>
X#include	<errno.h>
X#include	<stdio.h>
X
Xstatic	char	pty_name[] = "/dev/ptyPQ";
X
X#define		T_INDEX		(sizeof pty_name - 6)
X#define		P_INDEX		(sizeof pty_name - 3)
X#define		Q_INDEX		(sizeof pty_name - 2)
X
Xstruct	range {
X	char	lower, upper;
X};
Xstatic	struct	range	P_range[] = {
X	{ 'p', 'z' },
X	{ 'P', 'Z' },
X	{ 'a', 'o' },
X	{ 'A', 'O' },
X};
Xstatic	struct	range	Q_range[] = {
X	{ '0', '9' },
X	{ 'a', 'f' },
X	{ 'A', 'F' },
X	{ 'g', 'z' },
X	{ 'G', 'Z' },
X};
Xextern	int	errno;
X
X
X#define		SIZE(a)		(sizeof(a) / sizeof((a)[0]))
X
X#ifdef	DEBUG
X#define 	FPRINTF(x)	fprintf x
X#else	/* DEBUG */
X#define 	FPRINTF(x)
X#endif	/* DEBUG */
X#if	defined(DEBUG) || defined(LIST)
Xextern	char	*strerror();
X#endif	/* defined(DEBUG) || defined(LIST) */
X
X
Xint	getpty(tty, s)
Xint	*tty;
Xchar	*s;
X{
X	register char	P, P_max;
X	register struct range	*pr;
X	int	pty;
X	extern	int	try_pty();
X
X
X	strcpy(s, pty_name);
X	for (pr = P_range; pr < &P_range[SIZE(P_range)]; ++pr) {
X		for (P = pr->lower, P_max = pr->upper; P <= P_max; ++P) {
X			s[P_INDEX] = P;
X			switch (pty = try_pty(tty, s)) {
X			case -1:
X				continue;
X			case -2:
X				break;
X			default:
X				return pty;
X			}
X			break;
X		}
X	}
X	return -1;
X}
X
X
Xstatic	int	try_pty(tty, s)
Xint	*tty;
Xchar	*s;
X{
X	extern	int	errno;
X	register char	Q, Q_max;
X	register struct range	*qr;
X	int	pty;
X
X
X	for (qr = Q_range; qr < &Q_range[SIZE(Q_range)]; ++qr) {
X		for (Q = qr->lower, Q_max = qr->upper; Q <= Q_max; ++Q) {
X			s[Q_INDEX] = Q;
X#ifdef	LIST
X			fprintf(stderr, "%s", s);
X			if (access(s, 0) == 0) {
X				fprintf(stderr, "\n");
X				continue;
X			}
X			fprintf(stderr, ": %s\n", strerror(errno));
X			if (Q == qr->lower && qr == Q_range)
X				return -2;
X			break;
X#else	/* LIST */
X			if ((pty = open(s, 2)) >= 0) {
X
X				FPRINTF((stderr, "opening %s succeeded\n", s));
X
X				s[T_INDEX] = 't';
X				if (access(s, R_OK | W_OK) == 0
X					&& (*tty = open(s, 2)) >= 0) {
X					FPRINTF((stderr,
X						"opening %s succeeded\n", s));
X					return pty;
X				}
X
X				FPRINTF((stderr, "opening %s failed: %s\n",
X					s, strerror(errno)));
X
X				close(pty);
X				s[T_INDEX] = 'p';
X				continue;
X			}
X			FPRINTF((stderr, "opening %s failed: %s\n",
X				s, strerror(errno)));
X			if (errno == ENOENT) {
X				if (Q == qr->lower && qr == Q_range)
X					return -2;
X				break;
X			}
X#endif	/* LIST */
X		}
X	}
X	return -1;
X}
X
X
X#if	defined(DEBUG) || defined(LIST)
X
Xmain()
X{
X	char	buf[sizeof pty_name];
X	int	pty, tty;
X
X	pty = getpty(&tty, buf);
X}
X
X#endif	/* defined(DEBUG) || defined(LIST) */
X
X#ifdef	STRERROR
X
Xchar	*strerror(errno)
X{
X	extern	int	sys_nerr;
X	extern	char	*sys_errlist[];
X	static	char	buf[32];
X
X	if ((unsigned) errno < sys_nerr)
X		return sys_errlist[errno];
X	sprintf(buf, "Unknown error %d", errno);
X	return buf;
X}
X
X#endif	/* STRERROR */
+ END-OF-FILE getpty.c
chmod 'u=rw,g=r,o=r' 'getpty.c'
set `wc -c 'getpty.c'`
count=$1
case $count in
2730)	:;;
*)	echo 'Bad character count in ''getpty.c' >&2
		echo 'Count should be 2730' >&2
esac
echo Extracting 'extern.h'
sed 's/^X//' > 'extern.h' << '+ END-OF-FILE ''extern.h'
X#include	<sys/param.h>
X#include	<sys/time.h>
X#include	<sys/wait.h>
X#include	<sys/file.h>
X#include	<errno.h>
X#include	<signal.h>
X#include	<sys/ioctl.h>
X#include	<stdio.h>
X
X
X#ifndef	CANBSIZ
X#define		CANBSIZ		256
X#endif	/* !CANBSIZ */
X#define		MAXBUF		(CANBSIZ - 1)
X#define		PTYMAXNAME	32
X#define		INTERACTIVE	1
X
X
Xextern	int	Lw[];
Xextern	struct	sgttyb	Sg[];
Xextern	struct	tchars	Tc[];
Xextern	struct	ltchars	Ltc[];
Xextern	struct	winsize	Win;
Xextern	char	Eofc;
Xextern	int	Mode;
Xextern	int	Do_stdin, Do_stdout, Do_stderr;
Xextern	int	Pid;
Xextern	char	*Prog, Version[];
Xextern	int	errno;
X
Xextern	char	*strerror();
Xextern	int	getpty(), do_stdin(), do_stdout(), to_pty();
Xextern	void	set_modes(), do_child(), do_parent(), data_flow();
+ END-OF-FILE extern.h
chmod 'u=rw,g=r,o=r' 'extern.h'
set `wc -c 'extern.h'`
count=$1
case $count in
725)	:;;
*)	echo 'Bad character count in ''extern.h' >&2
		echo 'Count should be 725' >&2
esac
echo Extracting 'notty.c'
sed 's/^X//' > 'notty.c' << '+ END-OF-FILE ''notty.c'
X#include	<sys/types.h>
X#include	<sys/ioctl.h>
X#include	<stdio.h>
X
X/*
X * Relinquish controlling tty.  Exec args.
X */
X
Xmain(argc, argv)
Xint	argc;
Xchar	**argv;
X{
X	int	fd;
X
X
X	if (argc < 2) {
X		fprintf(stderr, "Usage: %s command\n", argv[0]);
X		exit(2);
X	}
X	if ((fd = open("/dev/tty", 1)) >= 0) {
X		ioctl(fd, TIOCNOTTY, (caddr_t) 0);
X		close(fd);
X		/*
X		 * Now prevent our process group getting set to that of
X		 * the first tty opened by the command.
X		 */
X		setpgrp(0, getpid());
X	}
X	execvp(argv[1], &argv[1]);
X	perror(argv[1]);
X	exit(1);
X}
+ END-OF-FILE notty.c
chmod 'u=rw,g=r,o=r' 'notty.c'
set `wc -c 'notty.c'`
count=$1
case $count in
538)	:;;
*)	echo 'Bad character count in ''notty.c' >&2
		echo 'Count should be 538' >&2
esac
echo Extracting 'frontend.c'
sed 's/^X//' > 'frontend.c' << '+ END-OF-FILE ''frontend.c'
X#include	<sys/types.h>
X#include	<signal.h>
X#include	<sys/ioctl.h>
X#include	<stdio.h>
X
X
Xchar	*Prog;
X
X
Xvoid	usage()
X{
X	fprintf(stderr, "Usage: %s [-ec]\n", Prog);
X	exit(1);
X}
X
X
Xmain(argc, argv)
Xint	argc;
Xchar	**argv;
X{
X	register char	esc = '~', susp, eof, killc, intr, quit;
X	char	c, prev = '\n';
X	int	save = 0;
X	struct	sgttyb	osg, nsg;
X	struct	tchars	tc;
X	struct	ltchars	ltc;
X
X
X	Prog = argv[0];
X
X	if (argc > 2)
X		usage();
X	if (argc == 2 && (argv[1][0] != '-' || argv[1][1] != 'e'
X		|| !(esc = argv[1][2]) || argv[1][3]))
X		usage();
X
X	ioctl(0, TIOCGETP, (caddr_t) &osg);
X	killc = osg.sg_kill;
X	nsg = osg;
X	nsg.sg_flags |= RAW;
X	nsg.sg_flags &= ~ECHO;
X	ioctl(0, TIOCSETN, (caddr_t) &nsg);
X	ioctl(0, TIOCGETC, (caddr_t) &tc);
X	intr = tc.t_intrc;
X	quit = tc.t_quitc;
X	eof = tc.t_eofc;
X	ioctl(0, TIOCGLTC, (caddr_t) &ltc);
X	susp = ltc.t_suspc;
X
X	signal(SIGPIPE, SIG_IGN);
X
X#define		BOL(c)		(c == '\n' || c == '\r' || c == killc \
X				|| c == eof || c == intr || c == quit \
X				|| c == susp)
X
X	while (read(0, &c, 1) == 1) {
X		if (c == '.' || c == susp) {
X			if (save) {
X				if (c == '.')
X					break;
X				ioctl(0, TIOCSETN, (caddr_t) &osg);
X				kill(0, SIGTSTP);
X				ioctl(0, TIOCSETN, (caddr_t) &nsg);
X				prev = '\n';
X				continue;
X			}
X		} else if (c == esc) {
X			if (BOL(prev)) {
X				prev = esc;
X				save = 1;
X				continue;
X			}
X		}
X		if (save) {
X			if (write(1, &prev, 1) != 1)
X				break;
X			save = 0;
X		}
X		if (write(1, &c, 1) != 1)
X			break;
X		prev = c;
X	}
X
X	ioctl(0, TIOCSETN, (caddr_t) &osg);
X}
+ END-OF-FILE frontend.c
chmod 'u=rw,g=r,o=r' 'frontend.c'
set `wc -c 'frontend.c'`
count=$1
case $count in
1497)	:;;
*)	echo 'Bad character count in ''frontend.c' >&2
		echo 'Count should be 1497' >&2
esac
echo Extracting 'irsh'
sed 's/^X//' > 'irsh' << '+ END-OF-FILE ''irsh'
X#!/bin/sh
X
Xtest $# != 1 && {
X	echo "Usage: `basename $0` machine" >&2
X	exit 2
X}
X
Xcase ${SHELL-sh} in
X*csh)
X	term="set term=$TERM"
X	;;
X*)
X	term="TERM=$TERM; export TERM"
Xesac
X
Xfrontend | rsh $1 "$term;" exec mtty -i ${SHELL-sh}
X
X# `rsh' may exit first, making `frontend' a child of init;
X# if so, it will be killed when it attempts to read from or reset the
X# tty while the job control shell already has taken it over
X
Xstty -raw echo
+ END-OF-FILE irsh
chmod 'u=rwx,g=rx,o=rx' 'irsh'
set `wc -c 'irsh'`
count=$1
case $count in
433)	:;;
*)	echo 'Bad character count in ''irsh' >&2
		echo 'Count should be 433' >&2
esac
echo Extracting 'myscript'
sed 's/^X//' > 'myscript' << '+ END-OF-FILE ''myscript'
X#!/bin/sh
X
Xopen='>'
X
Xcase $1 in
X-a)
X	open='>>'
X	shift
Xesac
X
Xfile=${1-myscript.out}
X
Xeval exec 3$open '$file'
X
Xecho "Script started, file is $file"
X
X(echo -n 'Script started on '; date) >&3
Xexec 3>&-
X
Xstty raw -echo
Xmtty -i ${SHELL-sh} | tee -a $file
Xstty -raw echo
X
X(echo ''; echo -n 'Script done on '; date) >> $file
X
Xecho "Script done, file is $file"
+ END-OF-FILE myscript
chmod 'u=rwx,g=rx,o=rx' 'myscript'
set `wc -c 'myscript'`
count=$1
case $count in
353)	:;;
*)	echo 'Bad character count in ''myscript' >&2
		echo 'Count should be 353' >&2
esac
echo Extracting 'Makefile'
sed 's/^X//' > 'Makefile' << '+ END-OF-FILE ''Makefile'
X# Makefile for mtty and notty
X
X# if your system doesn't have a strerror(3) function,
X# uncomment the next 2 lines
XSTRERROR =	strerror.o
XDSTRERROR =	-DSTRERROR
X
XSRCS =		data_flow.c do_child.c do_parent.c do_stdin.c do_stdout.c \
X		extern.c main.c set_modes.c to_pty.c version.c strerror.c \
X		getpty.c extern.h notty.c frontend.c irsh myscript Makefile \
X		mtty.1 Implementation Sketch README notty.1
X
XOBJS =		data_flow.o do_child.o do_parent.o do_stdin.o do_stdout.o \
X		extern.o main.o set_modes.o to_pty.o version.o $(STRERROR) \
X		getpty.o
X
Xmtty:		$(OBJS)
X		$(CC) $(OBJS) -o mtty
X
Xfrontend:
X		$(CC) frontend.c -o frontend
X
Xptylist:	getpty.c
X		$(CC) -DLIST $(DSTRERROR) getpty.c -o ptylist
X
Xptytest:	getpty.c
X		$(CC) -DDEBUG $(DSTRERROR) getpty.c -o ptytest
X
Xnotty:
X		$(CC) notty.c -o notty
X
Xshar:
X		shar $(SRCS) > mtty.sh
X
Xtar:
X		tar cvf mtty.t $(SRCS)
X
Xdata_flow.o:	extern.h
Xdo_child.o:	extern.h
Xdo_parent.o:	extern.h
Xdo_stdout.o:	extern.h
Xdo_stdin.o:	extern.h
Xextern.o:	extern.h
Xmain.o:		extern.h
Xset_modes.o:	extern.h
Xto_pty.o:	extern.h
+ END-OF-FILE Makefile
chmod 'u=rw,g=r,o=r' 'Makefile'
set `wc -c 'Makefile'`
count=$1
case $count in
1043)	:;;
*)	echo 'Bad character count in ''Makefile' >&2
		echo 'Count should be 1043' >&2
esac
echo Extracting 'mtty.1'
sed 's/^X//' > 'mtty.1' << '+ END-OF-FILE ''mtty.1'
X.\" Uses -man.
X.TH MTTY 1 Jul\ 24\ 1990
X.SH NAME
Xmtty \- mimic a tty and execute a command
X.SH SYNOPSIS
X.B mtty
X[
X.B \-i
X] [
X.B \-012
X] [
X.BI "\-E \e" ooo
X] [
X.B \-V
X] [
X.B \-\-
X]
X.I command
X.SH DESCRIPTION
X.I Mtty
Xallocates a \fIpseudo terminal\fR (see \fIpty\fR(4)) and executes the given
X.I command
Xwith (a subset of) its standard input, output and error output connected to
Xthe pseudo terminal: default \fIstdin\fR and \fIstdout\fR, and under
Xthe \-\fIi\fR option \fIstderr\fR as well.  \fIMtty\fR then waits
Xfor \fIcommand\fR to finish and reports
Xthe exit status in its own exit status.  If \fIcommand\fR died due to a signal,
Xthe exit status of \fImtty\fR will be the signal number plus 0200.
X.PP
XDefault the pseudo terminal operates in non-interactive mode.  In this mode
Xall special processing of input and output characters is turned off, except
Xfor the EOF character on input.  Although the pseudo tty actually has been put
Xinto \fIcanonical\fR mode, it behaves as if it were in \fIraw\fR mode, in the
Xsense that every character written on the standard input of \fImtty\fR is
Xmade available to \fIcommand\fR immediately.  Non-interactive operation is
Xtypically used in pipelines in which \fIfully\fR (\fIblock\fR-) \fIbuffered
XIO\fR is undesirable: \fImtty\fR
Xtries to lure the application into using \fIline-buffered\fR output.  Normally
Xthis scheme will be successful, because the \fIstdio\fR(3) library
Xautomatically selects line-buffering when the standard output is a terminal.
X.SH OPTIONS
X.TP
X.B \-i
XSelect interactive mode: the pseudo terminal will be made the \fIcontrolling
Xterminal\fR (see \fItty\fR(4)) for \fIcommand\fR.  The terminal modes,
Xincluding for example the special characters like interrupt and quit, will be
Xcopied from the controlling tty of \fImtty\fR itself (if possible), with the
Xfollowing exception: the pseudo tty will always be put into canonical
Xecho mode, i.e. its \fBRAW\fR and \fBCBREAK\fR bits will be turned off and
Xits \fBECHO\fR bit will be turned on.
X.TP
X.B \-012
XConnect only the specified \fIfile descriptors\fR to the pseudo tty.
X.TP
X.BI "\-E \e" ooo
XSet the EOF character of the pseudo tty to the byte whose octal representation
Xis `\fIooo\fR' (default ^D).
X.TP
X.B \-V
XPrint the version number and exit.
X.SH EXAMPLES
XThe following Bourne shell command will hang without producing any output at
Xall:
X.sp
X.RS
X.nf
Xtail -f /etc/passwd | tr a A |
Xwhile read line
Xdo
X	echo $line
Xdone
X.fi
X.RE
X.sp
XExplanation:
X.IP \-
Xbecause the output of `\fItr\fR' is a pipe, it will be block-buffered
X.IP \-
Xthe last 10 lines of /etc/passwd together contain fewer bytes than the
Xblocksize used by the \fIstdio\fR(3) library
X.IP \-
Xnow the \fIwhile\fR loop must wait until the output buffer of `\fItr\fR' fills
Xup, which will not happen unless `\fItr\fR' receives enough extra input (or
XEOF), which cannot happen while `\fItail\fR' is waiting for /etc/passwd to
Xgrow.
X.PP
XTo lure `\fItr\fR' into using line-buffered output we can use \fImtty\fR:
X.sp
X.RS
X.nf
Xtail -f /etc/passwd | mtty tr a A |
Xwhile read line
Xdo
X	echo $line
Xdone
X.fi
X.RE
X.sp
XSince only the output of `\fItr\fR' needs to be connected to a pseudo tty,
Xwe could have invoked \fImtty\fR with the \-\fI1\fR option.
X.PP
XHow \fImtty\fR can be used interactively is shown in the next example.
XThe following commands could be the heart of an alternative implementation
Xof \fIscript\fR(1):
X.sp
X.RS
X.nf
Xstty raw \-echo
Xmtty \-i ${SHELL\-sh} | tee typescript
Xstty \-raw echo
X.fi
X.RE
X.SH BUGS
XBecause of problems too complicated to discuss here, \fImtty\fR will close the
Xpseudo tty channel as soon as \fIcommand\fR has finished, i.e. a \fIbackground
Xprocess\fR started by \fIcommand\fR will not be able to use the pseudo tty any
Xlonger.
X.PP
XThough in non-interactive operation the pseudo tty will be put into
X\fIexclusive use\fR by means of a \fBTIOCEXCL\fR \fIioctl\fR(), there is
Xcurrently no guarantee that the channel will be exclusive indeed: there is a
Xrace condition and the tty might have been open already before \fImtty\fR
Xhad even started.
X.SH AUTHOR
XMaarten Litmaath @ VU Dept. of CS, Amsterdam
+ END-OF-FILE mtty.1
chmod 'u=rw,g=r,o=r' 'mtty.1'
set `wc -c 'mtty.1'`
count=$1
case $count in
4096)	:;;
*)	echo 'Bad character count in ''mtty.1' >&2
		echo 'Count should be 4096' >&2
esac
echo Extracting 'Implementation'
sed 's/^X//' > 'Implementation' << '+ END-OF-FILE ''Implementation'
XSome notes on the implementation of mtty.
X-----------------------------------------
XAs a read() from the pty (master) side of a pty-tty pair will return -1 with
Xerrno set to EIO, as soon as the last file descriptor to the tty (slave) side
Xhas been closed, the master has to keep the tty side open himself all the
Xtime!  But that means he cannot detect the slave side closing its last fd to
Xthe tty: as the master still has the tty open, a read() from the pty will not
Xreturn.  My solution is to let the master first fork() off the command, then
Xfork() off the IO handler, and wait() for both of them itself.  De IO handler
Xselect()s on 3 file descriptors (at most): stdin, the pty and a pipe set up
Xbetween the master and itself.  If stdin is selected, data are read from fd 0
Xand written to the pty to become input for the command.  If the pty is
Xselected for reading, data read from it will be written on stdout: the output
Xfrom the command is passed on.  If the pipe is selected for reading, it is a
Xsignal from the master that the command has finished, so the IO handler can
Xread any remaining data from the pty and write them to stdout.
XWe cannot let the master handle the IO, informed of the command's death by
Xa SIGCHLD: if stdout is a `slow' device and the master would just be writing
Xto it when the signal arrives, output would be lost; to `hold' the signal
Xbefore each write() and releasing it immediately afterward would be gross.
XBy letting the IO handler take care of all IO (including stdin), the master
Xcan step out of the way: it can get swapped out while wait()ing for its two
Xchildren to finish.
XOne more thing: if there should be no activity (when stdin is empty, or the
Xpty has been filled up with data yet unread by the command, and when there is
Xno unread output from the command), the IO handler will not take up cpu cycles,
Xbut instead will hang on the select().  This fact is not so trivial as it
Xseems; it involves non-blocking IO.
X
XBugs in the pty(4) driver.
X--------------------------
XThere's no distinction between the tty side being opened for reading and
Xit being opened for writing: only the fact that it is open counts when
Xdetermining if a read or write on the pty side should fail.
X
XWhen the last tty file descriptor is closed any unread output is lost!
XThe next read() on the pty fd should return the remaining output instead
Xof an IO error!
X
XOne should be able to open the pty side for reading and writing separately.
X
XAccording to pty(4):
X
X     Input flow control is  automatically  performed;  a  process
X     that  attempts  to  write  to  the controller device will be
X     blocked if too much unconsumed data is buffered on the slave
X     device.
X
XThis does not work if the slave device is in canonical mode: extra input
Xcharacters are thrown away.
X
XTIOCSETN on the tty slave flushes input!
X
XAnyone can open the tty slave; the mode of the tty side should be set
Xaccording to the third argument of open(2)!
+ END-OF-FILE Implementation
chmod 'u=rw,g=r,o=r' 'Implementation'
set `wc -c 'Implementation'`
count=$1
case $count in
2949)	:;;
*)	echo 'Bad character count in ''Implementation' >&2
		echo 'Count should be 2949' >&2
esac
echo Extracting 'Sketch'
sed 's/^X//' > 'Sketch' << '+ END-OF-FILE ''Sketch'
X
X		  +------------------+
X		  |                  |
X		  |                  v
X	+------+  |  +--------+     +------+
X	|      | <-- |        | <-- |      | <-- real stdin
X	| proc |     |tty  pty|     |master|
X	|      | --> |        | --> |      | --> real stdout
X	+------+  ^  +--------+     +------+
X		  |                  |
X		  |                  |
X		  +------------------+
X
X1) If the process dies, we must deselect stdin and drain the output from the
X   process, present on the read side of the pty.
X
X2) If EOF is detected on stdin, we write the tty's EOF character to the write
X   side of the pty to signal EOF to the process.
+ END-OF-FILE Sketch
chmod 'u=rw,g=r,o=r' 'Sketch'
set `wc -c 'Sketch'`
count=$1
case $count in
627)	:;;
*)	echo 'Bad character count in ''Sketch' >&2
		echo 'Count should be 627' >&2
esac
echo Extracting 'README'
sed 's/^X//' > 'README' << '+ END-OF-FILE ''README'
XCurrently mtty is BSD-oriented.  I have tested it on SunOS 4.0.3c.
X
XCheck the Makefile regarding strerror(3).
X
XYou should check if on your system ptys are allocated `the BSD way'.
XTry `make ptylist ptytest' and run the 2 resulting programs.
X
XI have included 2 examples of how mtty can be used: `myscript' is an
Xalternative implementation of `script'; `irsh' starts an `interactive rsh',
Xi.e. it is if you rlogin to the remote machine, only you will not get logged
Xin the utmp file (interesting!), and a `.rhosts' file is required.
X`frontend.c' is the source of `frontend' (how unexpected!), used by `irsh'.
X
X`notty.c' is included as a bonus!  See the manual.
+ END-OF-FILE README
chmod 'u=rw,g=r,o=r' 'README'
set `wc -c 'README'`
count=$1
case $count in
659)	:;;
*)	echo 'Bad character count in ''README' >&2
		echo 'Count should be 659' >&2
esac
echo Extracting 'notty.1'
sed 's/^X//' > 'notty.1' << '+ END-OF-FILE ''notty.1'
X.\" Uses -man.
X.TH NOTTY 1 Jul\ 24\ 1990
X.SH NAME
Xnotty \- detach /dev/tty and execute a command
X.SH SYNOPSIS
X.B notty
X.I command
X.SH DESCRIPTION
X.I Notty
Xdetaches the \fIcontrolling tty\fR (see \fItty\fR(4)) and executes the
X\fIcommand\fR specified.
X.SH EXAMPLE
X.RS
X.nf
X# the file `pw' contains the password of `joe'
X# the file `cmds' contains shell commands,
X# to be executed by `joe'
Xnotty su joe cmds < pw
X.fi
X.RE
X.PP
XThe following form is wrong:
X.sp
X.RS
Xcat pw cmds | notty su joe
X.RE
X.sp
XAs `\fIsu\fR' uses the \fIstdio\fR(3) library to read the password from
Xstandard input, and as stdin is a pipe, it will be \fIblock-buffered\fR,
Xand more than 1 line will be read, so part of `\fIcmds\fR' is consumed
Xby `\fIsu\fR'.
X.PP
XThe \fImtty\fR(1) utility cannot help here: if `\fIsu\fR' discovers
Xstdin is a tty, it will put it into non-echo mode using a \fBTIOCSETP\fR
X\fIioctl\fR(), which may flush unread input.
X.SH AUTHOR
XMaarten Litmaath @ VU Dept. of CS, Amsterdam
+ END-OF-FILE notty.1
chmod 'u=rw,g=r,o=r' 'notty.1'
set `wc -c 'notty.1'`
count=$1
case $count in
971)	:;;
*)	echo 'Bad character count in ''notty.1' >&2
		echo 'Count should be 971' >&2
esac
exit 0
--
 "and with a sudden plop it lands on usenet.  what is it? omigosh, it must[...]
   be a new user! quick kill it before it multiplies!"      (Loren J. Miller)