[net.sources.mac] uw re-posting, part 1 of 3

jdb@mordor.UUCP (John Bruner) (08/16/85)

: This is a shar archive.  Extract with sh, not csh.
echo x - README
cat > README << '!Funky!Stuff!'
The program "uw" is a multiple-window terminal emulator for the
Macintosh(tm) computer.  It is designed for use with a 4.2BSD
UNIX(tm) system.  Up to seven independent windows may be created,
each of which is connected to a pseudo-terminal on the UNIX machine.
A server program which runs on the UNIX host multiplexes the input
and output for these terminal sessions onto a single RS-232 serial
line.

"uw" requires the following:

	a Macintosh
	a 4.2BSD UNIX host
	the "uw" program for the Macintosh
	the "uw" server for the UNIX host
	the "uwtool" program for the UNIX host (optional)
	the RAM serial driver (resource SERD, ID=1) (optional)

At the present time, "uw" emulates a Lear Siegler ADM-31 terminal.
(The ADM-31 is upward-compatible with the ADM-3A terminal.)  It is
capable of absolute and relative cursor positioning, line and
character insertion/deletion, and erase to end-of-line and
end-of-page.

All portions of "uw" are copyrighted 1985 by John D. Bruner.
Permission to copy is given provided that the copy is not sold
and the copyright notices are included.

"uw" was designed to use the RAM serial driver from the 2/85 software
supplement.  Since this driver is proprietary Apple software, it is
not distributed with "uw".  For this reason, "uw" will use the ROM
serial driver if the RAM serial driver is not present, and it will
attempt to provide flow control itself.  The flow control provided
in this fashion is not as effective as that which the RAM serial
driver provides, so use of the RAM driver is recommended if it is
available.

The distribution includes:

	uw.h	   - include file (describes the simple serial protocol)
	uw.c	   - source for the 4.2BSD server program
	uwtool.c   - source for a 4.2BSD "tool" program
	openpty.c  - source for a utility routine to find and open pty's
	openpty.h  - include file (describes a structure used by openpty())
	Makefile   - to be used to compile the 4.2BSD programs
	uw.hqx	   - Binhex 4.0 binary for the Macintosh
	uw.doc.hqx - Binhex 4.0 MacPaint documentation

Macintosh is a trademark of Apple Computer.
UNIX is a trademark of American Telephone and Telegraph.
VAX is a trademark of Digital Equipment Corporation.
!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 <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, 2)) >= 0) {
			if ((pt->pt_tfd = open(pi->pi_tty, 2)) >= 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	NFDS	20			/* max number of file descriptors */

#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() */

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[NFDS];		/* mapping from fd's to windows */
struct window *curwin[2];		/* current input and output windows */

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:",
	NULL
};

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

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

extern char *mktemp();
extern char *getenv();
extern done(), cwait(), onalarm();
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.
	 */

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

	/*
	 * 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.
	 */

	selmask[0].sm_rd = (1<<0)|(1<<sd);
	selmask[0].sm_wt = 0;
	selmask[0].sm_ex = selmask[0].sm_rd | 2;
	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; selmask[1].sm_rd >>= 1, fd++) {
			if (selmask[1].sm_rd&(1<<0)) {
				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;
				selmask[0].sm_rd |= (1<<fd);
				(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;
{
	register int mask;

	/*
	 * Kill window "w".  Notify the Macintosh that it is gone if
	 * "notify" is nonzero.
	 */

	(void)close(w->w_fd);
	mask = ~(1<<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;
	selmask[0].sm_rd &= mask;
	selmask[0].sm_wt &= mask;
	selmask[0].sm_ex &= mask;
	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;
	selmask[0].sm_rd |= (1<<pt.pt_pfd);
	(void)strncpy(w->w_tty, pt.pt_tname, sizeof w->w_tty);

	while ((pid=vfork()) < 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", 2), (int)TIOCNOTTY, (char *)0);
		(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;
		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)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!
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