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) <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)