mtty - mimic a tty - a public domain pseudo tty utility
Maarten Litmaath
maart at cs.vu.nl
Wed Jul 25 06:50:38 AEST 1990
: 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) <c);
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)
More information about the Alt.sources
mailing list