[net.sources.mac] UW v2.10, part 3 of 3

jdb@mordor.UUCP (John Bruner) (11/10/85)

: This is a shar archive.  Extract with sh, not csh.
cat << '!Funky!Stuff!'
This is the UW distribution, part 3 of 3.  If you do not receive
all three parts, DO NOT post to "net.sources.mac".  Instead, please
send mail to me (John Bruner) at one of the following addresses:
	jdb@s1-c.ARPA
	seismo!mordor!jdb
	ucbvax!dual!mordor!jdb
	decvax!decwrl!mordor!jdb
!Funky!Stuff!
echo x - Makefile
cat > Makefile << '!Funky!Stuff!'
CC	=	/bin/cc
CFLAGS	=	-O

all:		uw uwtool

uw:		uw.o openpty.o
	$(CC) -o uw uw.o openpty.o

uwtool:		uwtool.o openpty.o
	$(CC) -o uwtool uwtool.o openpty.o
!Funky!Stuff!
echo x - openpty.h
cat > openpty.h << '!Funky!Stuff!'
/*
 *	This file defines the "ptydesc" structure which is returned
 *	by the routine "openpty".
 */

struct ptydesc {
	int		pt_pfd;		/* file descriptor of master side */
	int		pt_tfd;		/* file descriptor of slave side */
	char		*pt_pname;	/* master device name */
	char		*pt_tname;	/* slave device name */
};
!Funky!Stuff!
echo x - uw.h
cat > uw.h << '!Funky!Stuff!'
/*
 *	uw command bytes
 *
 * Copyright 1985 by John D. Bruner.  All rights reserved.  Permission to
 * copy this program is given provided that the copy is not sold and that
 * this copyright notice is included.
 *
 *
 * Two types of information are exchanged through the 7-bit serial line:
 * ordinary data and command bytes.  Command bytes are preceeded by
 * an IAC byte.  IAC bytes and literal XON/XOFF characters (those which
 * are not used for flow control) are sent by a CB_FN_CTLCH command.
 * Characters with the eighth bit set (the "meta" bit) are prefixed with
 * a CB_FN_META function.
 *
 * The next most-significant bit in the byte specifies the sender and
 * recipient of the command.  If this bit is clear (0), the command byte
 * was sent from the host computer to the Macintosh; if it is set (1)
 * the command byte was sent from the Macintosh to the host computer.
 * This prevents confusion in the event that the host computer
 * (incorrectly) echos a command back to the Macintosh.
 *
 * The remaining six bits are partitioned into two fields.  The low-order
 * three bits specify a window number from 1-7 (window 0 is reserved for
 * other uses) or another type of command-dependent parameter.  The next
 * three bits specify the operation to be performed by the recipient of
 * the command byte.
 *
 * Note that the choice of command bytes prevents the ASCII XON (021) and
 * XOFF (023) characters from being sent as commands.  CB_FN_ISELW commands
 * are only sent by the Macintosh (and thus are tagged with the CB_DIR_MTOH
 * bit).  Since XON and XOFF data characters are handled via CB_FN_CTLCH,
 * this allows them to be used for flow control purposes.
 */
 
#define	IAC		0001		/* interpret as command */
#define	CB_DIR		0100		/* command direction: */
#define	CB_DIR_HTOM	0000		/*	from host to Mac */
#define	CB_DIR_MTOH	0100		/*	from Mac to host */
#define	CB_FN		0070		/* function code: */
#define	CB_FN_NEWW	0000		/*	new window */
#define	CB_FN_KILLW	0010		/*	kill (delete) window */
#define	CB_FN_ISELW	0020		/*	select window for input */
#define	CB_FN_OSELW	0030		/*	select window for output */
#define	CB_FN_META	0050		/*	add meta to next data char */
#define	CB_FN_CTLCH	0060		/*	low 3 bits specify char */
#define	CB_FN_MAINT	0070		/*	maintenance functions */
#define	CB_WINDOW	0007		/* window number mask */
#define	CB_CC		0007		/* control character specifier: */
#define	CB_CC_IAC	1		/*	IAC */
#define	CB_CC_XON	2		/*	XON */
#define	CB_CC_XOFF	3		/*	XOFF */
#define	CB_MF		0007		/* maintenance functions: */
#define	CB_MF_ENTRY	0		/*	beginning execution */
#define	CB_MF_EXIT	7		/*	execution terminating */
#define	NWINDOW		7		/* maximum number of windows */
!Funky!Stuff!
echo x - openpty.c
cat > openpty.c << '!Funky!Stuff!'
/*
 *	openpty - open a pseudo-terminal
 *
 * The first time that the routine is called, the device directory is
 * searched and a list of all candidate pseudo-terminals is compiled.
 * Candidates are defined to be those entries in "/dev" whose names
 * (1) are the same length as PTY_PROTO and (2) start with the
 * initial string PTY_PREFIX.  Further, the master and slave sides
 * must both exist.
 *
 * openpty() attempts to find an unused pseudo-terminal from the list
 * of candidates.  If one is found, the master and slave sides are
 * opened and the file descriptors and names of these two devices are
 * returned in a "ptydesc" structure.  (The address of this structure
 * is supplied by the caller.  Zero is returned if openpty() was
 * successful, -1 is returned if no pty could be found.
 */

#include <sys/types.h>
#include <sys/dir.h>
#include <fcntl.h>
#include <strings.h>
#include "openpty.h"

#define	DEV_DIR		"/dev"		/* directory where devices live */
#define	PT_INDEX	(sizeof DEV_DIR)	/* location of 'p' in "pty" */

#define	PTY_PROTO	"ptyp0"		/* prototype for pty names */
#define	PTY_PREFIX	"pty"		/* prefix required for name of pty */

struct ptyinfo {
	struct ptyinfo	*pi_next;
	char		*pi_pty;
	char		*pi_tty;
};

static struct ptyinfo *ptylist;

extern char *malloc();

static
char *
devname(name)
char *name;
{
	register char *fullname;

	/*
	 * Construct the full name of a device in DEV_DIR.  Returns
	 * NULL if it failed (because malloc() failed).
	 */

	fullname = malloc((unsigned)(sizeof DEV_DIR + 1 + strlen(name)));
	if (fullname != NULL) {
		(void)strcpy(fullname, DEV_DIR);
		(void)strcat(fullname, "/");
		(void)strcat(fullname, name);
	}
	return(fullname);
}

static
isapty(dp)
struct direct *dp;
{
	static struct ptyinfo *pi;

	/*
	 * We don't care about the gory details of the directory entry.
	 * Instead, what we really want is an array of pointers to
	 * device names (with DEV_DIR prepended).  Therefore, we create
	 * this array ourselves and tell scandir() to ignore every
	 * directory entry.
	 *
	 * If malloc() fails, the current directory entry is ignored.
	 */

	if (pi == NULL &&
	    (pi = (struct ptyinfo *)malloc((unsigned)sizeof *pi)) == NULL)
		return(0);
		
	if (strlen(dp->d_name) == sizeof PTY_PROTO - 1 &&
	    strncmp(dp->d_name, PTY_PREFIX, sizeof PTY_PREFIX - 1) == 0) {
		pi->pi_pty = devname(dp->d_name);
		if (pi->pi_pty == NULL)
			return(0);
		pi->pi_tty = malloc((unsigned)(strlen(pi->pi_pty) + 1));
		if (pi->pi_tty == NULL) {
			free(pi->pi_pty);
			return(0);
		}
		(void)strcpy(pi->pi_tty, pi->pi_pty);
		pi->pi_tty[PT_INDEX] = 't';
		if (access(pi->pi_pty, 0) == 0 && access(pi->pi_tty, 0) == 0) {
			pi->pi_next = ptylist;
			ptylist = pi;
			pi = NULL;
		} else {
			free(pi->pi_pty);
			free(pi->pi_tty);
		}
	}
	return(0);
}

openpty(pt)
struct ptydesc *pt;
{
	register struct ptyinfo *pi;
	static int fail;
	auto struct direct **dirlist;
	extern char *re_comp();
	extern int alphasort();

	/*
	 * If the regular expression PTY_PROTO is bad, scandir() fails, or
	 * no possible pty's are found, then "fail" is set non-zero.  If
	 * "fail" is non-zero then the routine bombs out immediately.
	 * Otherwise, the list of candidates is examined starting with the
	 * entry following the last one chosen.
	 */

	if (fail)
		return(-1);

	if (!ptylist) {		/* first time */
		if (scandir(DEV_DIR, &dirlist, isapty, alphasort) < 0 ||
		    ptylist == NULL) {
			fail = 1;
			return(-1);
		}
		for (pi=ptylist; pi->pi_next; pi=pi->pi_next)
			;
		pi->pi_next = ptylist;	/* make the list circular */
	}

	pi = ptylist;
	do {
		if ((pt->pt_pfd = open(pi->pi_pty, O_RDWR)) >= 0) {
			if ((pt->pt_tfd = open(pi->pi_tty, O_RDWR)) >= 0) {
				ptylist = pi->pi_next;
				pt->pt_pname = pi->pi_pty;
				pt->pt_tname = pi->pi_tty;
				return(0);
			} else
				(void)close(pt->pt_pfd);
		}
		pi = pi->pi_next;
	} while (pi != ptylist);
	return(-1);
}
!Funky!Stuff!
echo x - uw.c
cat > uw.c << '!Funky!Stuff!'
#

/*
 *	uw - UNIX windows program for the Macintosh (VAX end)
 *
 * Copyright 1985 by John D. Bruner.  All rights reserved.  Permission to
 * copy this program is given provided that the copy is not sold and that
 * this copyright notice is included.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/uio.h>
#include <signal.h>
#include <setjmp.h>
#include <errno.h>
#include <fcntl.h>
#include <strings.h>
#include <stdio.h>
#include "uw.h"
#include "openpty.h"

#define	MAXENV	128			/* maximum environment size */

#define	W_IN	0
#define	W_OUT	1

#define	XON	0021			/* ASCII XON */
#define	XOFF	0023			/* ASCII XOFF */
#define	RUB	0177			/* ASCII RUBOUT */

#define	META	0200			/* "meta" bit for whatever it's worth */

#define	RCV_OK	(-1)			/* see recvcmd() */
#define	RCV_META (-2)			/* see recvcmd() */

#ifndef FD_SET
#define	FD_SET(n,p)	((p)->fds_bits[0] |= (1 << (n)))
#define	FD_CLR(n,p)	((p)->fds_bits[0] &= ~(1 << (n)))
#define	FD_ISSET(n,p)	((p)->fds_bits[0] & (1 << (n)))
#define	FD_ZERO(p)	((p)->fds_bits[0] = 0)
#endif

typedef int fildes_t;			/* file descriptor data type */
typedef int nwin_t;			/* window index data type */

struct window {
	fildes_t	w_fd;
	char		w_tty[32];
};

struct window window[NWINDOW];		/* window data structures */
struct window **fdmap;			/* mapping from fd's to windows */
struct window *curwin[2];		/* current input and output windows */
fildes_t nfds;				/* number of file descriptors */

char portal[] = "/tmp/uwXXXXXX";	/* UNIX-domain network address */


/* The following are added to the environment of all child processes */

char env_uwin[64] = "UWIN=";
char *env[] = { 
	env_uwin,
	"TERM=adm31",
	"TERMCAP=adm31:cr=^M:do=^J:nl=^J:al=\\EE:am:le=^H:bs:ce=\\ET:cm=\\E=%+ %+ :cl=^Z:cd=\\EY:co#80:dc=\\EW:dl=\\ER:ei=\\Er:ho=^^:im=\\Eq:li#24:mi:nd=^L:up=^K:MT:km:so=\\EG1:se=\\EG0:",
	NULL
};

int ctlch[] = { -1, IAC, XON, XOFF, -1, -1, -1, -1 };	/* CTL char mapping */

struct selmask {
	struct fd_set	sm_rd;
	struct fd_set	sm_wt;
	struct fd_set	sm_ex;
} selmask[2];

extern char *mktemp();
extern char *getenv();
extern char *malloc();
extern done(), cwait();
extern int errno;

main(argc, argv)
char **argv;
{
	register fildes_t fd, sd;
	register struct window *w;
	struct sockaddr sa;

	/*
	 * Make sure we don't accidentally try to run this inside itself.
	 */
	if (getenv("UWIN")) {
		fprintf(stderr, "%s is already running\n", *argv);
		exit(1);
	}

	/*
	 * Close all file descriptors except 0, 1, and 2.
	 */
	nfds = getdtablesize();
	for (fd=3; fd < nfds; fd++)
		(void)close(fd);

	/*
	 * Allocate "fdmap".  We can't do this at compile time because
	 * we get the number of file descriptors from getdtablesize().
	 */
	if (!(fdmap=(struct window **)malloc(nfds*sizeof(struct window *)))) {
		fprintf(stderr, "cannot allocate 'fdmap' array\n");
		exit(1);
	}

	/*
	 * Mark all windows closed.
	 */

	for (w=window; w < window+NWINDOW; w++)
		w->w_fd = -1;


	/*
	 * Create a UNIX-domain network address, and put its name into
	 * the environment so that descendents can contact us with new
	 * window requests.
	 */

	(void)strncat(env_uwin, mktemp(portal), sizeof env_uwin - 1);
	setenv(env);

	if ((sd=socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
		perror("socket");
		exit(1);
	}
	sa.sa_family = AF_UNIX;
	(void)strncpy(sa.sa_data, portal, sizeof sa.sa_data-1);
	sa.sa_data[sizeof sa.sa_data-1] = '\0';
	if (bind(sd, &sa, sizeof sa.sa_family + strlen(sa.sa_data)) < 0) {
		perror("bind");
		exit(1);
	}
	if (fcntl(sd, F_SETFL, FNDELAY))
		perror("fcntl(sd, F_SETFL, FNDELAY)");


	/*
	 * Ignore interrupts, quits, and terminal stops.  Clean up and exit
	 * if a hangup or termination is received.  Also catch changes in
	 * child status (so that we can wait for them).  Set up the terminal
	 * modes.
	 */

	(void)signal(SIGHUP, done);
	(void)signal(SIGINT, SIG_IGN);
	(void)signal(SIGQUIT, SIG_IGN);
	(void)signal(SIGTERM, done);
	(void)signal(SIGTSTP, SIG_IGN);
	(void)signal(SIGCHLD, cwait);

	tmode(1);


	/*
	 * Tell the Macintosh to initialize.  Initialize the current input
	 * and output windows to NULL.
	 */

	xmitcmd(CB_FN_MAINT|CB_MF_ENTRY);
	curwin[W_IN] = curwin[W_OUT] = NULL;

	
	/*
	 * Initialize the "select" masks, create window 1 (to start
	 * things off) and wait for input.  When input is available,
	 * process it.
	 */

	FD_ZERO(&selmask[0].sm_rd);
	FD_ZERO(&selmask[0].sm_wt);
	FD_ZERO(&selmask[0].sm_ex);

	FD_SET(0, &selmask[0].sm_rd);
	FD_SET(sd, &selmask[0].sm_rd);

	if (newwind(window) == 0)
		xmitcmd(CB_FN_NEWW|1);

	while (1) {
		selmask[1] = selmask[0];
		if (select(nfds, &selmask[1].sm_rd, &selmask[1].sm_wt,
		    &selmask[1].sm_ex, (struct timeval *)0) < 0) {
			if (errno == EINTR)
				continue;
			perror("select");
			done(1);	/* for now -- fix this! */
		}
		for (fd=0; fd < nfds; fd++) {
			if (FD_ISSET(fd, &selmask[1].sm_rd)) {
				if (fd < 2)
					mrecv();
				else if (fd == sd)
					netrecv(sd);
				else
					mxmit(fd);
			}
		}
	}
}

mrecv()
{
	register int len;
	register char *cp, *cq;
	auto int nready;
	char ibuf[512], obuf[512];
	static int seen_iac, seen_meta;

	/*
	 * The received bytestream is examined.  Non-command bytes are
	 * written to the file descriptor corresponding to the current
	 * "input" window (relative to the Macintosh -- the window the
	 * user types input to).
	 */

	if (ioctl(0, (int)FIONREAD, (char *)&nready) < 0) {
		perror("FIONREAD");
		return;
	}

	cq = obuf;
	while (nready > 0) {
		if (nready > sizeof ibuf)
			len = read(0, ibuf, sizeof ibuf);
		else
			len = read(0, ibuf, nready);
		if (len <= 0) {
			perror("read");
			return;
		}
		for (cp=ibuf; cp < ibuf+len; cp++) {
			if (seen_iac) {
				if ((*cp&CB_DIR) == CB_DIR_MTOH) {
					if (cq > obuf) {
						(void)write(curwin[W_IN]->w_fd,
							    obuf, cq-obuf);
						cq = obuf;
					}
					switch (*cq = recvcmd(*cp)) {
					case RCV_OK:
						break;
					case RCV_META:
						seen_meta = 1;
						break;
					default:
						if (seen_meta) {
							seen_meta = 0;
							*cq |= META;
						}
						if (curwin[W_IN])
							cq++;
						break;
					}
				}
				seen_iac = 0;
			} else if (*cp == IAC)
				seen_iac++;
			else if (curwin[W_IN]) {
				if (seen_meta) {
					seen_meta = 0;
					*cq++ = *cp|META;
				} else
					*cq++ = *cp;
				if (cq >= obuf+sizeof obuf) {
					(void)write(curwin[W_IN]->w_fd,
						    obuf, cq-obuf);
					cq = obuf;
				}
			}
		}
		nready -= len;
	}
	if (cq > obuf)
		(void)write(curwin[W_IN]->w_fd, obuf, cq-obuf);
}

recvcmd(cmd)
char cmd;
{
	register int nwin, fn;
	register struct window *w;

	/*
	 * Perform the function the Mac is asking for.  There are three
	 * broad categories of these functions: RCV_META, which tells
	 * the caller that the next character is a "meta" character;
	 * an ASCII data character, which is passed back to the caller
	 * for proper handling; and RCV_OK, which means that this routine
	 * has done everything which was required to process the command.
	 */

	fn = cmd&CB_FN;
	switch (fn) {
	case CB_FN_NEWW:
	case CB_FN_KILLW:
	case CB_FN_ISELW:
		nwin = cmd&CB_WINDOW;
		if (!nwin)
			break;
		w = &window[nwin-1];

		switch (fn) {
		case CB_FN_NEWW:
			if (w->w_fd < 0 && newwind(w) < 0)
				xmitcmd(CB_FN_KILLW|nwin);
			break;

		case CB_FN_KILLW:
			killwind(w, 0);
			break;

		case CB_FN_ISELW:
			if (w->w_fd >= 0)
				curwin[W_IN] = w;
			break;
		}
		break;

	case CB_FN_META:
		return(RCV_META);

	case CB_FN_CTLCH:
		return(ctlch[cmd&CB_CC]);

	case CB_FN_MAINT:
		if ((cmd&CB_MF) == CB_MF_EXIT)
			done(0);
		/*NOTREACHED*/
	}
	return(RCV_OK);
}

xmitcmd(cmd)
char cmd;
{
	static char cmdbuf[2] = { IAC, '\0' };

	/*
	 * Transmit the command "cmd" to the Macintosh.  The byte is ORed
	 * with the host-to-Mac direction indicator.
	 */

	cmdbuf[1] = cmd|CB_DIR_HTOM;
	(void)write(1, cmdbuf, sizeof cmdbuf);
}

netrecv(sd)
register fildes_t sd;
{
	register struct window *w;
	register int cnt;
	struct msghdr msg;
	auto int fd;
	struct iovec iov;
	struct stat st1, st2;
	static int unity = 1;
	char buf[256];


	/*
	 * main() calls this routine when there is a message waiting on
	 * the UNIX-domain socket.  The message's access rights are
	 * expected to contain the file descriptor for the "master" side
	 * of a pseudo-tty.  The message contains the name of the pty.
	 * The sender is expected to start up a process on the slave side
	 * of the pty.  This allows the host end to create windows which
	 * run something other than the shell.
	 */

	fd = -1;

	iov.iov_base = (caddr_t)buf;
	iov.iov_len = sizeof buf - 1;

	msg.msg_name = (caddr_t)0;
	msg.msg_namelen = 0;
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_accrights = (caddr_t)&fd;
	msg.msg_accrightslen = sizeof fd;

	if ((cnt=recvmsg(sd, &msg, 0)) < 0)
		perror("recvmsg");

	if (msg.msg_accrightslen > 0 && fd >= 0) {
		/*
		 * We can't trust the process which connected to us, so
		 * we verify that it really passed us a pseudo-tty's
		 * file descriptor by checking the device name and its
		 * inode number.  [Of course, if someone else wants to
		 * hand us a terminal session running under their uid....]
		 */
		buf[cnt] = 0;
		if (strncmp(buf, "/dev/pty", sizeof "/dev/pty" - 1) ||
		    fstat(fd, &st1) < 0 || stat(buf, &st2) < 0 ||
		    st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) {
			(void)close(fd);
			return;
		}
		/*
		 * OK, we believe the sender.  We allocate a window and
		 * tell the Macintosh to create that window on its end.
		 */
		buf[5] = 't';		/* switch to "/dev/ttyp?" */
		for (w=window; w < window+NWINDOW; w++) {
			if (w->w_fd < 0) {
				w->w_fd = fd;
				fdmap[fd] = w;
				FD_SET(fd, &selmask[0].sm_rd);
				(void)strncpy(w->w_tty, buf, sizeof w->w_tty-1);
				xmitcmd(CB_FN_NEWW|(w-window+1));
				break;
			}
		}
		/*
		 * If we have no free windows, then we close the file
		 * descriptor (which will terminate the slave process).
		 */
		if (w == window+NWINDOW)
			(void)close(fd);
		/*
		 * Set non-blocking I/O on the master side.
		 */
		(void)ioctl(fd, (int)FIONBIO, (char *)&unity);
	}
}

mxmit(fd)
register fildes_t fd;
{
	register char *cp, *cq, i;
	register int len;
	char ibuf[32], obuf[32];

	/*
	 * Copy input from file "fd" to the Macintosh.  Be sure to convert
	 * any embedded IAC characters.
	 *
	 * Note that the input/output buffers should NOT be very large.
	 * It is undesirable to perform large reads and effectively
	 * "lock out" all other file descriptors.  The chosen size
	 * should preserve a reasonable amount of efficiency.
	 */

	if (fdmap[fd]) {
		curwin[W_OUT] = fdmap[fd];
		xmitcmd(CB_FN_OSELW|(fdmap[fd]-window+1));
		cq = obuf;
		if ((len = read(fd, ibuf, sizeof ibuf)) < 0 &&
		    errno != EWOULDBLOCK) {
			killwind(fdmap[fd], 1);
			return;
		}
		for (cp=ibuf; cp < ibuf+len; cp++) {
			if (*cp&META) {
				if (cq > obuf) {
					(void)write(1, obuf, cq-obuf);
					cq = obuf;
				}
				xmitcmd(CB_FN_META);
				*cp &= ~META;
			}
			i = -1;
			if (*cp == RUB || *cp < ' ') {
				i = sizeof ctlch;
				while (i >= 0 && ctlch[i] != *cp)
					i--;
			}
			if (i >= 0) {
				if (cq > obuf) {
					(void)write(1, obuf, cq-obuf);
					cq = obuf;
				}
				xmitcmd(CB_FN_CTLCH|i);
			} else {
				*cq++ = *cp;
				if (cq >= obuf+sizeof obuf) {
					(void)write(1, obuf, cq-obuf);
					cq = obuf;
				}
			}
		}
	} else
		(void)read(fd, ibuf, sizeof ibuf);
	if (cq > obuf)
		(void)write(1, obuf, cq-obuf);
}

killwind(w, notify)
register struct window *w;
{
	/*
	 * Kill window "w".  Notify the Macintosh that it is gone if
	 * "notify" is nonzero.
	 */

	FD_CLR(w->w_fd, &selmask[0].sm_rd);
	FD_CLR(w->w_fd, &selmask[0].sm_wt);
	FD_CLR(w->w_fd, &selmask[0].sm_ex);
	(void)close(w->w_fd);
	fdmap[w->w_fd] = NULL;
	w->w_fd = -1;
	if (curwin[W_IN] == w)
		curwin[W_IN] = NULL;
	if (curwin[W_OUT] == w)
		curwin[W_OUT] = NULL;
	if (notify)
		xmitcmd(CB_FN_KILLW|(w-window+1));
}

newwind(w)
register struct window *w;
{
	register int pid;
	register char *shell;
	register int fd;
	auto struct ptydesc pt;
	static int unity = 1;
	extern char *getenv();

	/*
	 * Create a new window using "w".
	 */

	if (openpty(&pt) < 0)
		return(-1);	/* better recovery is needed */

	(void)ioctl(pt.pt_pfd, (int)FIONBIO, (char *)&unity);
	fdmap[pt.pt_pfd] = w;
	w->w_fd = pt.pt_pfd;
	FD_SET(pt.pt_pfd, &selmask[0].sm_rd);
	(void)strncpy(w->w_tty, pt.pt_tname, sizeof w->w_tty);

	/*
	 * We must spawn a new process with fork(), not vfork(), because
	 * some implementations (e.g. Sun UNIX) manipulate static
	 * variables within the signal() subroutine.  If we vfork()
	 * the parent will die when the first SIGCHLD arrives.  The
	 * data+bss size is fairly small, so this isn't too expensive.
	 */
	while ((pid=fork()) < 0)
		sleep(5);
	if (!pid) {
		(void)signal(SIGHUP, SIG_DFL);
		(void)signal(SIGINT, SIG_DFL);
		(void)signal(SIGQUIT, SIG_DFL);
		(void)signal(SIGTERM, SIG_DFL);
		(void)signal(SIGTSTP, SIG_IGN);
		(void)signal(SIGCHLD, SIG_DFL);
		(void)ioctl(open("/dev/tty",O_RDWR), (int)TIOCNOTTY, (char *)0);
		(void)close(open(pt.pt_tname, O_RDONLY)); /* set new ctrl tty */
		(void)setuid(getuid());		/* shouldn't need this */
		if (!(shell = getenv("SHELL")))
			shell = "/bin/sh";
		(void)dup2(pt.pt_tfd, 0);
		(void)dup2(0, 1);
		(void)dup2(0, 2);
		for (fd=3; fd < nfds; fd++)
			(void)close(fd);
		tmode(0);	/* HACK! */
		execl(shell, shell, (char *)0);
		_exit(1);
	}

	(void)close(pt.pt_tfd);
	return(0);
}

tmode(f)
{
	static struct sgttyb ostty, nstty;
	static struct tchars otchars, ntchars;
	static struct ltchars oltchars, nltchars;
	static int olmode, nlmode;
	static saved;

	/*
	 * This routine either saves the current terminal modes and then
	 * sets up the terminal line or resets the terminal modes (depending
	 * upon the value of "f").  The terminal line is used in "cbreak"
	 * mode with all special characters except XON/XOFF disabled.  The
	 * hated (by me) LDECCTQ mode is required for the Macintosh to
	 * handle flow control properly.
	 */

	if (f == 1) {
		if (ioctl(0, (int)TIOCGETP, (char *)&ostty) < 0) {
			perror("ioctl((int)TIOCGETP)");
			done(1);
		}
		if (ioctl(0, (int)TIOCGETC, (char *)&otchars) < 0) {
			perror("ioctl((int)TIOCGETC)");
			done(1);
		}
		if (ioctl(0, (int)TIOCGLTC, (char *)&oltchars) < 0) {
			perror("ioctl((int)TIOCGLTC)");
			done(1);
		}
		if (ioctl(0, (int)TIOCLGET, (char *)&olmode) < 0) {
			perror("ioctl((int)TIOCLGET)");
			done(1);
		}
		nstty = ostty;
		nstty.sg_erase = nstty.sg_kill = -1;
		nstty.sg_flags |= CBREAK|TANDEM;
		nstty.sg_flags &= ~(RAW|CRMOD|ECHO|LCASE|XTABS);
		ntchars.t_intrc = ntchars.t_quitc = -1;
		ntchars.t_eofc = ntchars.t_brkc = -1;
		ntchars.t_startc = XON;
		ntchars.t_stopc = XOFF;
		nltchars.t_suspc = nltchars.t_dsuspc = -1;
		nltchars.t_rprntc = nltchars.t_flushc = -1;
		nltchars.t_werasc = nltchars.t_lnextc = -1;
		nlmode = olmode | LDECCTQ;
		if (ioctl(0, (int)TIOCSETN, (char *)&nstty) < 0) {
			perror("ioctl((int)TIOCSETN)");
			done(1);
		}
		if (ioctl(0, (int)TIOCSETC, (char *)&ntchars) < 0) {
			perror("ioctl((int)TIOCSETC");
			done(1);
		}
		if (ioctl(0, (int)TIOCSLTC, (char *)&nltchars) < 0) {
			perror("ioctl((int)TIOCSLTC");
			done(1);
		}
		if (ioctl(0, (int)TIOCLSET, (char *)&nlmode) < 0) {
			perror("ioctl((int)TIOCLSET)");
			done(1);
		}
		saved = 1;
	} else if (saved) {
		(void)ioctl(0, (int)TIOCSETP, (char *)&ostty);
		(void)ioctl(0, (int)TIOCSETC, (char *)&otchars);
		(void)ioctl(0, (int)TIOCSLTC, (char *)&oltchars);
		(void)ioctl(0, (int)TIOCLSET, (char *)&olmode);
	}
}

done(s)
{
	register fildes_t fd;

	/*
	 * Clean up and exit.  It is overkill to close all of the file
	 * descriptors, but it causes no harm.  After we are sure that
	 * our UNIX-domain network connection is closed we remove the
	 * entry that it created (as a side effect) in the filesystem.
	 *
	 * We also restore the terminal modes.
	 */
	
	/*xmitcmd(CB_FN_MAINT|CB_MF_EXIT);*/
	for (fd=3; fd < nfds; fd++)
		(void)close(fd);
	(void)unlink(portal);
	tmode(0);
	exit(s);
}

cwait()
{
	union wait status;
	struct rusage rusage;

	/*
	 * Collect dead children.  We don't use the information that
	 * wait3() returns.  (Someday we might.)
	 */
	
	while (wait3(&status, WNOHANG, &rusage) > 0)
		;
}


static char *earray[MAXENV+1];

setenv(env)
char **env;
{
	register char **ep1, **ep2, *cp;
	char **ep3;
	extern char **environ;


	/*
	 * Merge the set of environment strings in "env" into the
	 * environment.
	 */

	/*
	 * The first time through, copy the environment from its
	 * original location to the array "earray".  This makes it a
	 * little easier to change things.
	 */

	if (environ != earray){
		ep1=environ;
		ep2=earray;
		while(*ep1 && ep2 <= earray+MAXENV)
			*ep2++ = *ep1++;
		*ep2++ = NULL;
		environ = earray;
	}


	/*
	 * If "env" is non-NULL, it points to a list of new items to
	 * be added to the environment.  These replace existing items
	 * with the same name.
	 */

	if (env){
		for(ep1=env; *ep1; ep1++){
			for(ep2=environ; *ep2; ep2++)
				if (!envcmp(*ep1, *ep2))
					break;
			if (ep2 < earray+MAXENV) {
				if (!*ep2)
					ep2[1] = NULL;
				*ep2 = *ep1;
			}
		}
	}


	/* Finally, use an insertion sort to put things in order. */

	for(ep1=environ+1; cp = *ep1; ep1++){
		for(ep2=environ; ep2 < ep1; ep2++)
			if (envcmp(*ep1, *ep2) < 0)
				break;
		ep3 = ep2;
		for(ep2=ep1; ep2 > ep3; ep2--)
			ep2[0] = ep2[-1];
		*ep2 = cp;
	}
}


static
envcmp(e1, e2)
register char *e1, *e2;
{
	register d;

	do {
		if (d = *e1 - *e2++)
			return(d);
	} while(*e1 && *e1++ != '=');
	return(0);
}
!Funky!Stuff!
echo x - uwtool.c
cat > uwtool.c << '!Funky!Stuff!'
#

/*
 *	uwtool
 *
 * Copyright 1985 by John D. Bruner.  All rights reserved.  Permission to
 * copy this program is given provided that the copy is not sold and that
 * this copyright notice is included.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/uio.h>
#include <strings.h>
#include <signal.h>
#include <stdio.h>
#include "openpty.h"

#define	NFDS	20			/* max number of file descriptors */

typedef int fildes_t;

extern char *getenv();

main(argc, argv)
char **argv;
{
	register int pid;
	register fildes_t sd;
	auto fildes_t fd;
	char *portal, *shell;
	int lmode;
	struct sgttyb sg;
	struct tchars tc;
	struct ltchars ltc;
	struct iovec iov;
	struct msghdr msg;
	struct sockaddr sa;
	auto struct ptydesc pt;

	/*
	 * Close all file descriptors except 0, 1, and 2.
	 */

	for (fd=3; fd < NFDS; fd++)
		(void)close(fd);

	/*
	 * Get the terminal configuration for this tty.
	 */
	(void)ioctl(0, (int)TIOCGETP, (char *)&sg);
	(void)ioctl(0, (int)TIOCGETC, (char *)&tc);
	(void)ioctl(0, (int)TIOCGLTC, (char *)&ltc);
	(void)ioctl(0, (int)TIOCLGET, (char *)&lmode);

	/*
	 * Create a UNIX-domain socket.
	 */

	if (!(portal=getenv("UWIN"))) {
		fprintf(stderr,
		    "You must run %s under the window manager\n", *argv);
		exit(1);
	}

	if ((sd=socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
		perror("socket");
		exit(1);
	}
	sa.sa_family = AF_UNIX;
	(void)strncpy(sa.sa_data, portal, sizeof sa.sa_data-1);
	sa.sa_data[sizeof sa.sa_data-1] = '\0';


	/*
	 * Obtain a pseudo-tty.
	 */

	if (openpty(&pt) < 0) {
		fprintf(stderr, "Can't obtain a pseudo-tty for a new window\n");
		exit(1);
	}


	/* 
	 * Fork a child process using this pseudo-tty.  Initialize the
	 * terminal modes on the pseudo-tty to match those of the parent
	 * tty.
	 */

	while ((pid=vfork()) < 0)
		sleep(5);
	if (!pid) {
		(void)setuid(getuid());/* in case it's setuid-root by mistake */
		(void)signal(SIGTSTP, SIG_IGN);
		(void)ioctl(open("/dev/tty", 2), (int)TIOCNOTTY, (char *)0);
		(void)close(open(pt.pt_tname, 0)); /*set new ctrl tty */
		(void)dup2(pt.pt_tfd, 0);
		(void)dup2(0, 1);
		(void)dup2(0, 2);
		for (fd=3; fd < NFDS; fd++)
			(void)close(fd);
		(void)ioctl(0, (int)TIOCSETN, (char *)&sg);
		(void)ioctl(0, (int)TIOCSETC, (char *)&tc);
		(void)ioctl(0, (int)TIOCSLTC, (char *)&ltc);
		(void)ioctl(0, (int)TIOCLSET, (char *)&lmode);
		if (argc == 1) {
			if (!(shell=getenv("SHELL")))
				shell = "/bin/sh";
			execl(shell, shell, (char *)0);
			perror(shell);
		} else {
			execvp(argv[1], argv+1);
			perror(argv[1]);
		}
		_exit(1);
	}


	/*
	 * Pass the file descriptor to the window server and exit.
	 */

	iov.iov_base = pt.pt_pname;
	iov.iov_len = strlen(pt.pt_pname);
	msg.msg_name = (caddr_t)&sa;
	msg.msg_namelen = sizeof sa.sa_family + strlen(sa.sa_data);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_accrights = (caddr_t)&pt.pt_pfd;
	msg.msg_accrightslen = sizeof pt.pt_pfd;
	if (sendmsg(sd, &msg, 0) < 0) {
		perror("sendmsg");
		exit(1);
	}

	exit(0);
}
!Funky!Stuff!
echo x - macmouse.ml
cat > macmouse.ml << '!Funky!Stuff!'
; $Header: /c/cak/lib/mlisp/RCS/macmouse.ml,v 1.5 85/11/05 14:01:44 cak Rel $
; 
; Macintosh mouse routines for use with John Bruner's uw program.
; 	Chris Kent, Purdue University Fri Oct 25 1985
; 	Copyright 1985 by Christopher A. Kent. All rights reserved.
; 	Permission to copy is given provided that the copy is not
; 	sold and this copyright notice is included.
; 
; Provides a scroll bar/thumbing area in the unused scroll bar with the
; following features:
; 	click at line 1 does previous page
;	click at line 24 does next page
; 	click anywhere else "thumbs" to the relative portion of the buffer.
; 	shift-click at line 1 scrolls one line down
; 	shift-click at line 24 scrolls one line up
; 	shift-click elsewhere moves line to top of window
; 	option-shift-click elsewhere moves line to bottom of window
; 
; There is also basic positioning and kill-buffer support:
; 	click in a buffer moves dot there
; 	drag copies the dragged region to the kill buffer (mark is left
; 		at the beginning of the region.)
; 	shift-drag deletes the dragged region to the kill buffer
;   it is possible to use the scrolling and thumbing area to make the region
;   larger than a single screen; just click, scroll, release. Make sure
;   that the last scroll is just a down event; the up must be in the buffer.
;
; 	option-click yanks from the kill buffer, doesn't affect mark.
; 	option-shift-click similarly yanks from a named buffer.
; 

(declare-global
    #mouse-last-x		; x of last event
    #mouse-last-y		; y of last event
    #mouse-last-b		; buttons at last event
    #mouse-last-dot		; dot after last event
    #mouse-last-action		; whether last was scroll (1) or edit (2)
)

(defun
    (move-mac-cursor savest b x y up down lock shift option command saveb
	(setq savest stack-trace-on-error)
	(setq stack-trace-on-error 0)
	; decode everything
	(setq y (- (get-tty-character) 32))
	(setq x (- (get-tty-character) 32))
	(setq b (- (get-tty-character) 32))
	(setq saveb b)
	(setq command (% b 2))(setq b (/ b 2))	; command key
	(setq shift (% b 2))(setq b (/ b 2))	; shift 
	(setq lock (% b 2))(setq b (/ b 2))	; caps-lock
	(setq option (% b 2))(setq b (/ b 2))	; option
	(setq down (% b 2))(setq b (/ b 2))	; mouse down
	(setq up (% b 2))
	
	(if (= x 81)		; right margin -- move-dot-to-x-y is wrong
	    (progn 
		   (#mouse-scroll-region)
		   (setq #mouse-last-action 1))
	    (if (error-occurred 
		    (if (= #mouse-last-action 2)	; not if just scrolled
			(setq #mouse-last-dot (dot)))
		    (move-dot-to-x-y x y)
		    (backward-character)(forward-character)
		    (#mouse-edit-action)
		    (setq #mouse-last-action 2)
		)
		(progn 
		       (#mouse-scroll-region b x y)
		       (setq #mouse-last-action 1))
	    ))
	(setq stack-trace-on-error savest)
	(if (= down 1)
	    (progn 
		   (setq #mouse-last-x x)
		   (setq #mouse-last-y y)
		   (setq #mouse-last-b saveb))
	    (progn 
		   (setq #mouse-last-x 0)
		   (setq #mouse-last-y 0)
		   (setq #mouse-last-b 0)))
    )
    
    (#mouse-edit-action		; marking and editing actions on buttons:
				;   if no movement, nothing.
				;   if movement, put  mark at #mouse-last-dot,
				;      leave dot here,and edit.
				; editing (on upstrokes):
				;   unmodified, copy to kill buffer.
				;   SHIFTed, delete (cut) to kill buffer.
				; 
				; option-click yanks from kill buffer; 
				; shift-option-click from named buffer.
	(if (= saveb 16)
	    (#mouse-d))
	(if (= saveb 17)
	    (#mouse-dc))
	(if (= saveb 18)
	    (#mouse-ds))
	(if (= saveb 19)
	    (#mouse-dsc))
	(if (= saveb 20)
	    (#mouse-dl))
	(if (= saveb 21)
	    (#mouse-dlc))
	(if (= saveb 22)
	    (#mouse-dls))
	(if (= saveb 23)
	    (#mouse-dlsc))
	(if (= saveb 24)
	    (#mouse-do))
	(if (= saveb 25)
	    (#mouse-doc))
	(if (= saveb 26)
	    (#mouse-dos))
	(if (= saveb 27)
	    (#mouse-dosc))
	(if (= saveb 28)
	    (#mouse-dol))
	(if (= saveb 29)
	    (#mouse-dolc))
	(if (= saveb 30)
	    (#mouse-dols))
	(if (= saveb 31)
	    (#mouse-dolsc))
	(if (= saveb 32)
	    (#mouse-u))
	(if (= saveb 33)
	    (#mouse-uc))
	(if (= saveb 34)
	    (#mouse-us))
	(if (= saveb 35)
	    (#mouse-usc))
	(if (= saveb 36)
	    (#mouse-ul))
	(if (= saveb 37)
	    (#mouse-ulc))
	(if (= saveb 38)
	    (#mouse-uls))
	(if (= saveb 39)
	    (#mouse-ulsc))
	(if (= saveb 40)
	    (#mouse-uo))
	(if (= saveb 41)
	    (#mouse-uoc))
	(if (= saveb 42)
	    (#mouse-uos))
	(if (= saveb 43)
	    (#mouse-uosc))
	(if (= saveb 44)
	    (#mouse-uol))
	(if (= saveb 45)
	    (#mouse-uolc))
	(if (= saveb 46)
	    (#mouse-uols))
	(if (= saveb 47)
	    (#mouse-uolsc))
    )

    ; individual button bindings

    (#mouse-u			; up
     	(if (! (#mouse-click-p))
	    (progn 
		   (#mouse-set-region)
		   (Copy-region-to-kill-buffer)
	    ))
    )

    (#mouse-uc			; up/command
    )

    (#mouse-us			; up/shift
     	(if (! (#mouse-click-p))
	    (progn 
		   (#mouse-set-region)
		   (delete-to-killbuffer)
	    ))
    )

    (#mouse-usc			; up/shift/command
    )

    (#mouse-ul			; up/lock
    )

    (#mouse-ulc			; up/lock/command
    )

    (#mouse-uls			; up/lock/shift
    )

    (#mouse-ulsc		; up/lock/shift/command
    )

    (#mouse-uo			; up/option
     	(if (#mouse-click-p)
	    (yank-from-killbuffer)
	)
    )

    (#mouse-uoc			; up/option/command
    )

    (#mouse-uos			; up/option/shift
	(if (#mouse-click-p)	; click
	    (yank-buffer (get-tty-buffer "Insert contents of buffer: "))
	)
    )

    (#mouse-uosc		; up/option/shift
    )

    (#mouse-uol			; up/option/lock
    )

    (#mouse-uolc		; up/option/lock
    )

    (#mouse-uols		; up/option/lock/shift
    )

    (#mouse-uolsc		; up/option/lock/shift/command
    )
    
    (#mouse-d			; down
    )

    (#mouse-dc			; down/command
    )

    (#mouse-ds			; down/shift
    )

    (#mouse-dsc			; down/shift/command
    )

    (#mouse-dl			; down/lock
    )

    (#mouse-dlc			; down/lock/command
    )

    (#mouse-dls			; down/lock/shift
    )

    (#mouse-dlsc		; down/lock/shift/command
    )

    (#mouse-do			; down/option
    )

    (#mouse-doc			; down/option/command
    )

    (#mouse-dos			; down/option/shift
    )

    (#mouse-dosc		; down/option/shift
    )

    (#mouse-dol			; down/option/lock
    )

    (#mouse-dolc		; down/option/lock
    )

    (#mouse-dols		; down/option/lock/shift
    )

    (#mouse-dolsc		; down/option/lock/shift/command
    )

    (#mouse-set-region		; set the region to be from last dot to dot.
	(set-mark)
	(goto-character #mouse-last-dot)
	(exchange-dot-and-mark)
    )

    (#mouse-click-p clickp
     	(if (= (dot) #mouse-last-dot)
	    (setq clickp 1)
	    (setq clickp 0)
	))
    
    (#mouse-scroll-region	 ; out of range actions:
				;    left margin -- hard to generate, ignored
				;    right margin -- simulate scroll bar
				;      line 1 -- previous page
				;      line 24/25 -- next page
				;      other lines -- thumbing
				;    top margin -- previous page
				;    bottom margin -- next page
				; 
				; if shifted, deal with lines. 
				;    line 1 scrolls one line down
				;    line 24/25 scrolls one line up
				;    else line to top;  with option to bottom.
				;
				; if up stroke is in same place as down
				; stroke, don't do anything, so clicks in
				; the scroll region don't do the action
				; twice.
	(if (= down 1)
	    (if (= shift 1)
		(do-lines)
		(do-pages))
	)
	(if (& (= up 1)
	       (| (!= x #mouse-last-x) (!= y #mouse-last-y)))
	    (if (= shift 1)
		(do-lines)
		(do-pages)
	    )
	)
	(#mouse-set-region)
    )

    (do-pages			; large motions via pages and thumbing
	(if (| (= y 0) (= y 1) (= y 24) (= y 25))
	    (progn 
		   (if (| (= y 0) (= y 1))
		       (previous-page)
		       (Next-Page)
		   ))
	    (if (= x 81)
		(goto-percent (/ (* y 100) 25))
	    )
	))

    (do-lines			; fine control over lines
	(if (= x 81)
	    (if (| (= y 1) (= y 24) (= y 25))
		(if (| (= y 0) (= y 1))
		    (scroll-one-line-down)
		    (scroll-one-line-up)
		)
		(progn
		      (move-dot-to-x-y 1 y)
		      (if (= option 0)
			  (line-to-top-of-window)
			  (line-to-bottom-of-window))
		)
	    )
	)
    )

    (line-to-bottom-of-window nlines i
	(line-to-top-of-window)
	(setq i 0)
	(setq nlines (- (window-height) 1))
	(while (< i nlines)
	       (scroll-one-line-down)
	       (setq i (+ i 1))
	)
    )

    (goto-percent
       (goto-character (/ (* (buffer-size) (arg 1)) 100))
   )
)
    
(bind-to-key "move-mac-cursor" "\em")
!Funky!Stuff!
exit 0
: end of shell archive
-- 
  John Bruner (S-1 Project, Lawrence Livermore National Laboratory)
  MILNET: jdb@mordor [jdb@s1-c.ARPA]	(415) 422-0758
  UUCP: ...!ucbvax!dual!mordor!jdb 	...!seismo!mordor!jdb