[comp.sources.amiga] unix window server

ain@j.cc.purdue.edu (Patrick White) (01/25/88)

Program Name:	uw  (unix window server)
Submitted By:	I snarfed it from simtel20 and posted it.
Summary:	unix (server) end of the unix windows protocol.
Poster Boy:  Pat White  (ain@j.cc.purdue.edu)
Tested.

NOTES:
   I know a lot of people are going to want this, so I've posted it.. this
is the version on simtel20 with the 110K of macintosh stuff removed.
   It is suggested that the "env" array in uw.c be changed so the server
dosen't force the termcap to a adm31 as the Amiga dosen't use the adm31
termcap.

-- Pat White   (co-moderator comp.sources/binaries.amiga)
UUCP: j.cc.purdue.edu!ain  BITNET: PATWHITE@PURCCVM   PHONE: (317) 743-8421
U.S.  Mail:  320 Brown St. apt. 406,    West Lafayette, IN 47906

========================================


#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	README
#	Makefile
#	openpty.c
#	uw.c
#	uwtool.c
#	openpty.h
#	uw.h
# This archive created: Mon Jan 25 09:35:02 1988
# By:	Patrick White (PUCC Land, USA)
echo shar: extracting README '(4555 characters)'
cat << \SHAR_EOF > README
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,
a DEC VT52, and a Tektronix 4010.

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 Apple 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:

	README	    - this file
	Makefile    - to be used to compile the 4.2BSD programs
	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())
	macmouse.ml - mlisp functions for use with (Unipress) Emacs
	uw.hqx	    - Binhex 4.0 binary for the Macintosh
	uw.doc.hqx  - Binhex 4.0 MacWrite documentation


Changes to UW
-------------

This is UW version 2.10.  The last distributed version was 1.6.

A number of changes have been made to the Macintosh portion of UW.
Some of the more significant ones are

    1)	Configuration files:  A number of parameters such as the
	baud rate, parity, number of stop bits, keyboard mapping,
	etc. can be saved to and reloaded from a configuration
	file.

    2)	Terminal emulations:  In addition to an ADM31 emulation,
	UW also can emulate the VT52 and Tektronix 4010.  (The
	VT52 emulation is provisional and may be replaced by a
	VT100 emulation in a future version of UW.)  The terminal
	emulation is selectable on a per-window basis.

    3)	Clipboard:  Desk accessories may use all of the standard
	clipboard functions.  Text may be copied from a UW window
	onto the clipboard, and text in the clipboard may be
	pasted into a window.

    4)	Mouse clicks:  As an alternative to the clipboard, mouse
	(up/down) events within a window may be translated into an
	escape sequence and transmitted as input data to the host.
	This provides a rudimentary facility for (e.g.) EMACS to
	use mouse input.  The use of mouse-to-host or clipboard
	copying is selectable on a per-window basis.

    5)	Multiple fonts:  In addition to the standard 9-point Monaco
	font, UW will also use a 7-point font.  The font size is
	selectable on a per-window basis.

    6)	Inverse video:  Inverse video was not implemented in the
	previous version of UW.  It is now supported for the ADM31.

    7)	Window renaming:  Window titles may be changed (on the
	Macintosh).

In conjunction with (4) above, Chris Kent at Purdue (cak@purdue)
has written mlisp code for Goslings Emacs which interprets the
mouse-event encoding that UW can optionally provide.  He has
given his permission for his file "macmouse.ml" to be included
in this distribution.

There are no changes in the Mac-host interface (although some plans
are being made in this area).  Thus, one of the most-requested
features, file transfer, is not implemented (yet).

Of course, bugs in the previous version have been fixed (most notably
some that dealt with scrolling).  Almost certainly new bugs have been
introduced to take the place of those which were expunged.

Since some of the changes are rather significant (and a couple,
like the method of selecting text to copy to the clipboard, are
slightly non-standard), the updated documentation is recommended
reading for all UW users.

UW is much larger than it was before.  Extensive use of the
Tektronix 4010 emulation will almost certainly exhaust memory
on a 128K Macintosh.
SHAR_EOF
if test 4555 -ne "`wc -c README`"
then
echo shar: error transmitting README '(should have been 4555 characters)'
fi
echo shar: extracting Makefile '(156 characters)'
cat << \SHAR_EOF > Makefile
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
SHAR_EOF
if test 156 -ne "`wc -c Makefile`"
then
echo shar: error transmitting Makefile '(should have been 156 characters)'
fi
echo shar: extracting openpty.c '(3914 characters)'
cat << \SHAR_EOF > openpty.c
/*
 *	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);
}
SHAR_EOF
if test 3914 -ne "`wc -c openpty.c`"
then
echo shar: error transmitting openpty.c '(should have been 3914 characters)'
fi
echo shar: extracting uw.c '(17598 characters)'
cat << \SHAR_EOF > uw.c
#

/*
 *	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);
}
SHAR_EOF
if test 17598 -ne "`wc -c uw.c`"
then
echo shar: error transmitting uw.c '(should have been 17598 characters)'
fi
echo shar: extracting uwtool.c '(3073 characters)'
cat << \SHAR_EOF > uwtool.c
#

/*
 *	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);
}
SHAR_EOF
if test 3073 -ne "`wc -c uwtool.c`"
then
echo shar: error transmitting uwtool.c '(should have been 3073 characters)'
fi
echo shar: extracting openpty.h '(308 characters)'
cat << \SHAR_EOF > openpty.h
/*
 *	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 */
};
SHAR_EOF
if test 308 -ne "`wc -c openpty.h`"
then
echo shar: error transmitting openpty.h '(should have been 308 characters)'
fi
echo shar: extracting uw.h '(2697 characters)'
cat << \SHAR_EOF > uw.h
/*
 *	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 */
SHAR_EOF
if test 2697 -ne "`wc -c uw.h`"
then
echo shar: error transmitting uw.h '(should have been 2697 characters)'
fi
#	End of shell archive
exit 0