#!mkcmd
# $Id: wrapw.m,v 1.41 2012/10/06 20:12:39 ksb Exp $
# The wrapper wrapper for forwarding a set of diversions to another context.
# This should use libevent, I think. --ksb
# We don't allow -N to ssh, becase we need to run a command
# ssh doesn't use options 3567890, dhjruyz, BEGHIJPQUWZ so we do
# We use -h, -V for the same things ssh does (help, version)
# we use -d, -t (for tty)
from '<stdlib.h>'
from '<fcntl.h>'
from '<sys/types.h>'
from '<sys/wait.h>'
from '<sys/socket.h>'
from '<sys/uio.h>'
from '<sys/un.h>'
from '<unistd.h>'
from '<signal.h>'
from '<string.h>'
from '<ctype.h>'
from '<sys/param.h>'
from '<sys/stat.h>'
from '<sysexits.h>'

from '"machine.h"'
from '"fdrights.h"'
from '"mkdtemp.h"'

require "util_errno.m" "util_ppm.m"
require "util_tmp.m" "util_divstack.m" "util_divconnect.m"
require "std_help.m" "std_version.m"
require "ptbccmd.m"

basename "wrapw" ""
getenv "WRAPW"

%i
extern char **environ;

static char rcsid[] =
	"$Id: wrapw.m,v 1.41 2012/10/06 20:12:39 ksb Exp $",
	acMyDefName[] = "wrapw",
	acMySuffix[] = "/wrapwXXXXXX";	/* mkdtemp suffix in $TMPDIR	*/
static char
	*pcMyTemp = (char *)0,		/* if we had to build one	*/
	*pcMySpace = (char *)0,		/* where we hide to work	*/
	*pcMySocket = (char *)0,	/* my UNIX domain socket	*/
	**ppcOldEnv;			/* the original environment	*/
static unsigned
	uOldSize;			/* the size of the orig env	*/

#if HAVE_ACCEPT_FILTER && defined(SO_ACCEPTFILTER)
static const char
	acAcceptFilter[] = "dataready";
#endif
static const char
	acProto[] = "0.1\n";	/* includes protocal \n terminator */
%%

key "divClient" 1 {
	"acMyDefName" 'rt'
}

augment action 'V' {
	user "Version();"
}

before {
	hidden named "Before"
	help "save the incoming environment"
}
boolean 'm' {			# sadly this conflicts with ssh -m mac_spec
	once track
	named "fMaster"
	help "manage output for descendant processes"
	boolean 'd' {
		named "fPublish"
		init "1"
		help "do not publish this level in the linked environment"
	}
	char* 'N' {		# conficts, but useless option for us
		named "pcBindHere"
		param "path"
		help "force a UNIX domain name for this service"
	}
	exclude 'IRVhW#'
	boolean 'E' {
		named "fWrapAll"
		help "wrap every instance of wrapw as well as the unwrapped diversions"
	}
	list {
		named "Master"
		param "utility"
		help "control process for task coordination"
	}
}
action 'R' {
	stop exclude "QIm"
	named "sync" from "<unistd.h>"
	update "/* stop option processing */"
	help "remember the wrapper environment for out-of-band transmission"
	list {
		named "Scribe"
		param "record"
		help "the name of the out-of-band file, none for stdout"
	}
}
char* 't' {
	named "pcTags"
	param "wrap"
	help "select diversion by socket (or file) path"
	exclude "m"
}
boolean 'Q' {
	named "fQuit"
	init '0'
	help "tell the enclosing persistent instance to finish"
	exclude "m"
}
boolean 'I' {
	named "fAugment"
	init '0'
	help "push wrappers onto the current environment stacks"
	exclude "m"
}
boolean 'W' {
	named "fWhole"
	init '0'
	help "include the whole environment, if possible"
	exclude "m"
}
list {
	named "Client"
	param "client"
	help "client command"
}

%c
#include "table.c"


/* Save the incoming environment for the future, in a sendable block.	(ksb)
 * We don't know who might change it, so copy the whole thing away.
 */
static void
Before(int i_)
{
	register unsigned uMem, uSpan;
	register char **ppc, **ppcSave, *pcMem;

	uMem = 0;
	for (ppc = environ, uSpan = 1; (char *)0 != *ppc; ++ppc, ++uSpan) {
		uMem += strlen(*ppc)+1;
	}
	uMem |= 15;
	uSpan |= 3;
	++uMem, ++uSpan;
	if ((char *)0 == (pcMem = malloc(uMem))) {
		fprintf(stderr, "%s: malloc: %lu: %s\n", progname, (unsigned long)uMem, strerror(errno));
		exit(EX_OSERR);
	}
	if ((char **)0 == (ppcSave = calloc(uSpan, sizeof(char *)))) {
		fprintf(stderr, "%s: calloc: %lu,%lu: %s\n", progname, (unsigned long)uSpan, (unsigned long)sizeof(char *), strerror(errno));
		exit(EX_OSERR);
	}
	ppcOldEnv = ppcSave;
	for (ppc = environ, uSpan = 1; (char *)0 != *ppc; ++ppc, ++uSpan) {
		*ppcSave++ = strcpy(pcMem, *ppc);
		pcMem += strlen(pcMem)+1;
	}
	*ppcSave = (char *)0;
	*pcMem++ = '\000';
	/* 1 is wrong, I think --ksb XXX */
	uOldSize = (ppcOldEnv == ppcSave) ? 1 : (pcMem - ppcOldEnv[0]);
}

/* Build a temp directory for my space					(ksb)
 */
static char *
MyPlace()
{
	register char *pcRet;
	extern char *mkdtemp(char *);

	if ((char *)0 == (pcRet = malloc(((strlen(pcTmpdir)+sizeof(acMySuffix))|7)+1))) {
		fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}
	(void)strcat(strcpy(pcRet, pcTmpdir), acMySuffix);
	return mkdtemp(pcRet);
}

/* When we are nested inside an sshw we can use the temp directory	(ksb)
 * it built to hide our service socket.  Otherwise we must build one
 * to hide us from the cold cruel world (eval dirname \$sshw_${sshw_link}).
 * We try to nest inside gtfw as well.
 */
static void
SafePlace(const char *pcInside)
{
	register char *pcEnv, *pcTail;
	register const char **ppc;
	static const char *apcNest[] = {
		"sshw_%s", "gtfw_%s", "sshw_%s", (char *)0
	};
	auto char acIndex[255+32+1];	/* 255 is MAXNAMLEN really */

	pcMyTemp = (char *)0;
	if ((char *)0 != (pcEnv = divCurrent((char *)0))) {
		/* try to buddy with an enclosing instance */
	}
	for (ppc = apcNest; (char *)0 == pcEnv && (char *)0 != *ppc; ++ppc) {
		snprintf(acIndex, sizeof(acIndex), *ppc, "link");
		if ((char *)0 != (pcEnv = getenv(acIndex)) && strlen(pcEnv) <= 32 && (isdigit(*pcEnv) || 'd' == *pcEnv)) {
			snprintf(acIndex, sizeof(acIndex), *ppc, pcEnv);
			pcEnv = getenv(acIndex);
		} else {
			pcEnv = (char *)0;
		}
	}
	if ((char *)0 == pcEnv || (char *)0 == (pcTail = strrchr(pcEnv, '/')) || pcEnv == pcTail) {
		pcMySpace = pcMyTemp = MyPlace();
		return;
	}
	*pcTail = '\000';
	pcMySpace = strdup(pcEnv);
	*pcTail = '/';
}

/* Make the server port for this server to listen on			(ksb)
 * stolen from ptyd, xclate, and others I've coded.
 */
static int
MakeServerPort(pcName)
char *pcName;
{
	register int sCtl, iRet, iListen;
	auto struct sockaddr_un run;
#if HAVE_ACCEPT_FILTER && defined(SO_ACCEPTFILTER)
	auto struct accept_filter_arg FA;
#endif

	if ((sCtl = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
		fprintf(stderr, "%s: socket: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}
#if HAVE_SUN_LEN
	run.sun_len = 0;	/* hush valgrind */
#endif
	run.sun_family = AF_UNIX;
	(void) strcpy(run.sun_path, pcName);
	if (-1 == bind(sCtl, (struct sockaddr *)&run, EMU_SOCKUN_LEN(pcName))) {
		return -1;
	}
	/* On some UNIX-like systems we need a sockopt set first -- sigh
	 */
	iRet = -1;
	for (iListen = 20*64; iListen > 5 && -1 == (iRet = listen(sCtl, iListen)) ; iListen >>= 1) {
		/* try less */
	}
	if (-1 == iRet) {
		fprintf(stderr, "%s: listen: %d: %s\n", progname, iListen, strerror(errno));
		exit(EX_OSERR);
	}
#if HAVE_ACCEPT_FILTER && defined(SO_ACCEPTFILTER)
	(void)memset(&FA, '\000', sizeof(FA));
	(void)strcpy(FA.af_name, acAcceptFilter);
	setsockopt(sCtl, SOL_SOCKET, SO_ACCEPTFILTER, &FA, sizeof(FA));
#endif
	(void)fcntl(sCtl, F_SETFD, 1);
	return sCtl;
}

/* Pass pcDirect = /tmp-dir/socket-name to contact
 * a ptbw-like service.
 */
int
MakeClientPort(const char *pcDirect)
{
	register int s, iFailed, iSlow;
	auto struct sockaddr_un run;

	iFailed = 0;
	for (iSlow = 0; ; iSlow ^= 1) {
		if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
			return -1;
		}
		memset((void *)& run, '\000', sizeof(run));
		run.sun_family = AF_UNIX;
		if (strlen(pcDirect) > sizeof(run.sun_path)-1) {
			errno = ENAMETOOLONG;
			goto failed;
		}
		(void)strcpy(run.sun_path, pcDirect);
		if (-1 != connect(s, (struct sockaddr *)&run, strlen(pcDirect)+2)) {
			(void)fcntl(s, F_SETFD, 1);
			break;
		}
		if (ECONNREFUSED != errno) {
			goto failed;
		}
		close(s);
		s = -1;
		/* try again */
		sleep(iSlow);
	}
	if (iFailed) {
		register int e;
		errno = EPFNOSUPPORT;
failed:
		e = errno;
		(void)close(s);
		errno = e;
		return -1;
	}
	return s;
}

/* We don't want to leave trash around the filesystem			(ksb)
 * Should be install with atexit before we call SafePlace or Bindings.
 */
static void
CleanUp(void)
{
	if ((char *)0 != pcMySocket) {
		(void)unlink(pcMySocket);
		pcMySocket = (char *)0;
	}
	if ((char *)0 != pcMyTemp) {
		(void)rmdir(pcMyTemp);
		pcMyTemp = (char *)0;
	}
}

/* Message passed as the (void *) target in calls to DiVersion		(ksb)
 */
struct _WMnode {
	int iwidth;
	const char *pcnote;
	int fsawactive;
	const char *pcdirect;
};

/* Format the info about a stacked (or diconnected) diversion		(ksb)
 * (if we get to level 0 return true).
 */
static int
DiVersion(const char *pcName, const char *pcTag, int fActive, void *pvMark)
{
	auto struct stat stSock;
	register struct _WMnode *pWM = pvMark;
	register char *pcTail;

	if ((const char *)0 == pcName) {
		pcName = "--";
	}
	if ((const char *)0 == pcTag) {
		printf("%s:%*s: missing from our environment", progname, pWM->iwidth, pcName);
	} else if (-1 != lstat(pcTag, & stSock)) {
		printf("%s:%*s: %s", progname, pWM->iwidth, pcName, pcTag);
	} else if (ENOTDIR != errno || (char *)0 == (pcTail = strrchr(pcTag, '/'))) {
		printf("%s:%*s: stat: %s: %s", progname, pWM->iwidth, pcName, pcTag, strerror(errno));
	} else {
		*pcTail = '\000';
		if (-1 == lstat(pcTag, & stSock) || S_IFSOCK != (S_IFMT & stSock.st_mode)) {
			printf("%s:%*s: lstat: %s: %s", progname, pWM->iwidth, pcName, pcTag, strerror(errno));
		} else {
			printf("%s:%*s: %s/%s", progname, pWM->iwidth, pcName, pcTag, pcTail+1);
		}
		*pcTail = '/';
	}
	if (fActive) {
		pWM->fsawactive = 1;
		printf("%s", pWM->pcnote);
	}
	if ((const char *)0 != pWM->pcdirect && 0 == strcmp(pcTag, pWM->pcdirect)) {
		pWM->pcdirect = (const char *)0;
		printf(" [-t]");
	}
	printf("\n");
	if (fActive && !isdigit(*pcName)) {
		return 1;
	}
	return '0' == pcName[0] && '\000' == pcName[1];
}

/* We output more than you'd want to know, good for debugging		(ksb)
 */
static void
Version()
{
	register const char *pcLevel;
	auto unsigned uOuter;
	auto struct _WMnode WMParam;

	printf("%s:  ptbc code: revision %s", progname, PTBacVersion);
	printf("%s:  table code: %s\n", progname, acTableId);
	printf("%s: safe directory template: %s\n", progname, acMySuffix+1);
#if HAVE_ACCEPT_FILTER && defined(SO_ACCEPTFILTER)
	printf("%s: accept filter: %s\n", progname, acAcceptFilter);
#endif
	(void)divNumber(& uOuter);
	divVersion(stdout);
	pcLevel = divCurrent((char *)0);
	WMParam.iwidth = (char *)0 == pcLevel ? 3 : strlen(pcLevel)+2;
	WMParam.pcnote = " [target]";
	WMParam.fsawactive = 0;
	WMParam.pcdirect = pcTags;
	if (0 == divSearch(DiVersion, (void *)& WMParam) && iDevDepth >= uOuter) {
		if ((const char *)0 != WMParam.pcdirect) {
			DiVersion("-t", WMParam.pcdirect, !WMParam.fsawactive, (void *)&WMParam);
		} else if (0 == iDevDepth) {
			printf("%s: no current diversions\n", progname);
		} else {
			printf("%s: depth -%d is too great for current stack of %u\n", progname, iDevDepth, uOuter);
		}
	} else if ((const char *)0 != WMParam.pcdirect) {
		DiVersion("-t", WMParam.pcdirect, !WMParam.fsawactive, (void *)&WMParam);
	} else if (!WMParam.fsawactive) {
		printf("%s: never saw the active diversion\n", progname);
	}
}

static char
	**ppcTableau = (char **)0,
	**ppcEnvClient = (char **)0,
	**ppcClientRestore = (char **)0;
static const char
	acIsLink[] = "_link=";

/* Support code for wrapw, we need to manage some strings
 * for each wrapper entry in the list build a map
 *   new-tail -> old socket name, if it pointed to a real socket,
 *		else if wrap-the-wrapped go ahead and wrap it,
 *		else just leave it in alone
 */
static void
SetupTableau(char **ppcList, const char *pcOurSocket)
{
	register int i, iCount;
	register char *pcCursor, *pcScan;
	register char **ppcT, *pcEq, *pcMem, **ppcE, **ppcC;
	register unsigned uUniq;
	auto char acSuffix[64];	/* big enough to hold an unsigned integer */

	if ((char **)0 == ppcList || (char *)0 == *ppcList) {
		static char *pcEmpty = (char *)0;
		ppcClientRestore = ppcTableau = ppcEnvClient = & pcEmpty;
		return;
	}
	for (iCount = 0; (char *)0 != ppcList[iCount++]; ) {
		/* count them */
	}

	i = (iCount|7)+1;
	if ((char **)0 == (ppcT = (char **)calloc(i, sizeof(char *))) || ((char **)0 == (ppcE = (char **)calloc(i, sizeof(char *)))) || ((char **)0 == (ppcC = (char **)calloc(i, sizeof(char *))))) {
		fprintf(stderr, "%s: calloc: %ldx%ld: %s\n", progname, (long)i, (long)sizeof(char *), strerror(errno));
		exit(EX_OSERR);
	}
	ppcTableau = ppcT;
	ppcEnvClient = ppcE;
	ppcClientRestore = ppcC;

	/* Remap the wrapper_{digits|'d'}=/existant/path and the like,
	 * skip the other things in the environement (and ourself)
	 * in the tableau	suffix:/the/old/path
	 * in the environ	wrapper_spec=/our_path/socket/suffix
	 */
	for (uUniq = 0, i = 0; (char *)0 != (pcScan = *ppcList); ++ppcList) {
		/* pass envs without a value as-is
		 */
		if ((char *)0 == (pcEq = strchr(pcScan, '='))) {
			*ppcE++ = pcScan;
			continue;
		}
		/* pass link records as-is, like "xclate_link=2"
		 */
		if ((char *)0 != (pcCursor = strstr(pcScan, acIsLink)) && pcCursor < pcEq) {
			*ppcE++ = pcScan;
			*ppcC++ = pcScan;
			continue;
		}
		pcCursor = pcScan;
		while (isalnum(*pcCursor)) {
			++pcCursor;
		}
		if (pcScan == pcCursor || '_' != *pcCursor) {
			*ppcE++ = pcScan;
			continue;
		}
		/* Check for self-encapsulation, honor -E
		 */
		*pcCursor = '\000';
		i = 0 == strcmp(pcScan, acMyDefName);
		*pcCursor++ = '_';
		if ((!fWrapAll && i) || 0 == strcmp(pcEq+1, pcOurSocket)) {
			*ppcE++ = pcScan;
			*ppcC++ = pcScan;
			continue;
		}

		/* Look for the possible wrapper types _<int> | _d
		 */
		if ('d' == *pcCursor) {
			++pcCursor;
		} else while (isdigit(*pcCursor)) {
			++pcCursor;
		}
		if (pcEq != pcCursor) {
			*ppcE++ = pcScan;
			continue;
		}

		*pcEq++ = '\000';
		/* build tableau entry and new env value
		 */
		snprintf(acSuffix, sizeof(acSuffix), "%u", uUniq++);
		i = strlen(acSuffix)+1+strlen(pcEq)+1;
		pcMem = malloc((i|7)+1);
		snprintf(pcMem, i, "%s:%s", acSuffix, pcEq);
		*ppcT++ = pcMem;

		i = 7|(strlen(pcScan)+1+strlen(pcOurSocket)+1+strlen(acSuffix)+1);
		pcMem = malloc(i+1);
		snprintf(pcMem, i, "%s=%s/%s", pcScan, pcOurSocket, acSuffix);
		*ppcE++ = pcMem;
		*ppcC++ = pcMem;
		*--pcEq = '=';
	}
	*ppcT = *ppcE = *ppcC = (char *)0;
}

static volatile pid_t wInferior = 0;
static volatile int wInfExits = EX_SOFTWARE;
static volatile int fInfOpts = WNOHANG|WUNTRACED;

/* When a signal for a child arrives we burry the dead.			(ksb)
 * If he is "our boy", then forget he's alive.
 */
static void
Death(int _dummy)	/*ARGSUSED*/
{
	register pid_t wReap;
	auto int wStatus;

	while (0 < (wReap = wait3(& wStatus, fInfOpts, (struct rusage *)0))) {
		if (WIFSTOPPED(wStatus)) {
			(void)kill(wReap, SIGCONT);
			continue;
		}
		if (wInferior == wReap) {
			wInferior = 0;
			wInfExits = wStatus;
		}
	}
}

/* Signal handler to remove our socket when we are signaled;		(ksb)
 * we use atexit(3) to do that, by the way.
 */
static void
DeadMan(int _dummy)	/*ARGSUSED*/
{
	exit(EX_OK);
}

/* Build the service socket, put the mame in the environment		(ksb)
 */
static int
Bindings(unsigned uMake)
{
	static const char acEnvRead[] = "WRAPW";
	register char *pcTail, *pcMem;
	register unsigned int i;
	register int sRet;
	auto struct sigaction saWant;
	auto struct stat stLook;
	auto char acLevel[(sizeof(acEnvRead)|15)+(32+1)];

	(void)memset((void *)& saWant, '\000', sizeof(saWant));
	saWant.sa_handler = (void *)Death;
#if HAVE_SIGACTION
	saWant.sa_sigaction = (void *)Death;
#endif
	saWant.sa_flags = SA_RESTART;
	if (-1 == sigaction(SIGCHLD, & saWant, (struct sigaction *)0)) {
		fprintf(stderr, "%s: sigaction: CHLD: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}

	/* Accepting a -HUP signal to re-load the tableau data adds
	 * uncertianty for no gain here, just install -TERM -- ksb
	 */
	(void)memset((void *)& saWant, '\000', sizeof(saWant));
	saWant.sa_handler = (void *)DeadMan;
#if HAVE_SIGACTION
	saWant.sa_sigaction = (void *)DeadMan;
#endif
	saWant.sa_flags = SA_RESTART;
	if (-1 == sigaction(SIGINT, & saWant, (struct sigaction *)0)) {
		fprintf(stderr, "%s: sigaction: INT: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}
	(void)sigaction(SIGTERM, & saWant, (struct sigaction *)0);

	if ((char *)0 != pcBindHere) {
		if ('/' == *pcBindHere) {
			pcMySocket = pcBindHere;
		} else {
			register int iGuess;
			iGuess = (15|(strlen(pcMySpace)+strlen(pcBindHere)))+9;
			pcMem = malloc(iGuess);
			snprintf(pcMem, iGuess, "%s/%s", pcMySpace, pcBindHere);
			pcMySocket = pcMem;
		}
		if (-1 == (sRet = MakeServerPort(pcMySocket))) {
			fprintf(stderr, "%s: bind: %s: %s\n", progname, pcMySocket, strerror(errno));
			exit(EX_OSERR);
		}
	} else {
		pcMem = malloc((strlen(pcMySpace)|63)+65);
		pcTail = strchr(strcpy(pcMem, pcMySpace), '\000');
		if (pcTail > pcMem && '/' != pcTail[-1]) {
			*pcTail++ = '/';
		}
		for (i = 0; 1; ++i) {
			snprintf(pcTail, 64, "ww%u", i);
			if (-1 != lstat(pcMem, & stLook))
				continue;
			if (-1 != (sRet = MakeServerPort(pcMem)))
				break;

			/* We failed to get a bind in pcMySpace, if we
			 * didn't make a space we still can, once. -- ksb
			 * (Someone chmod'd our super-space ugo-w, sigh.)
			 */
			if ((char *)0 != pcMyTemp) {
				fprintf(stderr, "%s: bind: %s: %s\n", progname, pcMem, strerror(errno));
				exit(EX_OSERR);
			}
			pcMySpace = pcMyTemp = MyPlace();
			return Bindings(uMake);
		}
		pcMySocket = pcMem;
	}

	/* Notify the inferior processes of our IPC location
	 */
	if (fPublish) {
		divPush(pcMySocket);
	} else {
		divDetach(pcMySocket, (const char *)0);
	}

	/* Diddle the options variable $US_$level -> $US, I'm a wrapper
	 */
	(void)snprintf(acLevel, sizeof(acLevel), "%s_%u", acEnvRead, uMake);
	if ((char *)0 != (pcTail = getenv(acLevel))) {
		(void)setenv(acEnvRead, pcTail, 1);
	} else {
		(void)unsetenv(acEnvRead);
	}

	return sRet;
}

/* Read a known size block of data from a peer.				(ksb)
 * The block is followed by a lone '\n' sync marker, which becomes the
 * sentinal '\000' in a list of strings (ps type).
 */
static int
ReadBlock(int sFrom, char *pcMem, int iMemSize)
{
	register int cc;

	++iMemSize;
	while (iMemSize > 0) {
		if (0 == (cc = read(sFrom, pcMem, iMemSize)))
			break;
		if (-1 == cc)
			return -1;
		iMemSize -= cc;
		pcMem += cc;
	}
	if ('\n' != pcMem[-1]) {
		errno = EMSGSIZE;
		return -1;
	}
	pcMem[-1] = '\000';	/* return as a point to strings (ps) */
	return 0;
}

/* True when the environment variable looks like part of the wrapper	(ksb)
 * stack.  In other words is not $_ and matches
 *	 m/^[A-Za-z0-9]+_(d|[0-9]*|link)=/
 */
static int
IsWrapEnv(register char *pcPick)
{
	while (isalnum(*pcPick))
		++pcPick;
	if ('_' != *pcPick++)
		return 0;
	if ('d' == pcPick[0] && '=' == pcPick[1])
		return 1;
	if (0 == strncmp(pcPick-1, acIsLink, sizeof(acIsLink)-1))
		return 1;
	while (isdigit(*pcPick))
		++pcPick;
	return '=' == *pcPick;
}

/* If the environment record given is in the links list then		(ksb)
 * return a pointer to the recorded depth.  List given in cats format:
 *	"ksb\000""2", "ptbw\000""7", (char *)0
 */
static const char *
IsInLinks(register char *pcThis, register char **ppsList)
{
	register size_t w;
	register const char *pcLook, *pcDigits;

	while ((char *)0 != (pcLook = *ppsList++)) {
		w = strlen(pcLook);
		if (0 != strncmp(pcThis, pcLook, w))
			continue;

		pcLook += strlen(pcLook)+1;
		if (0 == strncmp(pcThis+w, acIsLink, sizeof(acIsLink)-1))
			return pcLook;
		pcDigits = pcThis+w;
		if ('_' == *pcDigits)
			++pcDigits;
		while (isdigit(*pcDigits))
			++pcDigits;
		if ('=' == *pcDigits)
			return pcLook;
	}
	return (const char *)0;
}


/* Add the old link count to the current one if any.			(ksb)
 * This adds the old enviornment to the new environment, with the
 * new one closer the the client process (under -I).
 */
static char *
Remap(register char *pcSet, register char **ppsLinks)
{
	register const char *pcAdd;
	register int iSum, iMem;
	register char *pcRet, *pcEq, *pcUnder;

	if ((char *)0 == (pcEq = strchr(pcSet, '=')) || (char *)0 == (pcAdd = IsInLinks(pcSet, ppsLinks)))
		return pcSet;

	/* An addition of 2 intergers can not be longer than the sum
	 * of the digits.  (99+1 -> 100)
	 */
	iMem = (7|(strlen(pcAdd) + strlen(pcSet))) + 1;
	if ((char *)0 == (pcRet = calloc(iMem, sizeof(char)))) {
		fprintf(stderr, "%s: calloc: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}
	/* There cases	ksb_link=cur + Add	-> ksb_link=<sum>
	 * or		ksb_depth=/path		-> ksb_<sum>=/path
	 * or		ksb_link=d		-> ksb_link=d
	 */
	iSum = atoi(pcAdd);
	if ((char *)0 != (pcUnder = strstr(pcSet, acIsLink)) && pcUnder < strchr(pcSet, '=')) {
		*pcEq = '\000';
		if (!isdigit(pcEq[1])) {
			/* Could sanity check for pcSet"_"pcEq+1 in the env,
			 * but would computing the new TOS really help anyone?
			 */
			*pcEq = '=';
			return pcSet;
		}
		iSum += atoi(pcEq+1);
		snprintf(pcRet, iMem, "%s=%d", pcSet, iSum);
	} else {
		pcUnder = pcEq;
		while ('_' != *--pcUnder)
			;
		iSum += atoi(pcUnder+1);
		pcUnder[1] = '\000';
		snprintf(pcRet, iMem, "%s%d%s", pcSet, iSum, pcEq);
		pcEq = pcUnder+1;
	}
	*pcEq = '%';		/* debug, mark string as used */
	return pcRet;
}

/* Sort the environment in alpha order					(ksb)
 */
static int
OrderEnv(const void *pvLeft, const void *pvRight)
{
	return strcmp(0[(char **)pvLeft], 0[(char **)pvRight]);
}

/* Assuming both lists are in alpha order merge them.			(ksb)
 * Note that we also replace the socket path an removed the junk.
 * Also note that the new list must have space allocated for all possible
 * entries (new + old + sentinal NULL), which ModEnv did for us (below).
 */
static char **
Fold(char **ppcNew, char **ppcOld)
{
	register char *pcEqNew, *pcEqOld, *pcPick;
	register int iCmp;

	/* No place to write new list, ouch.
	 */
	if ((char **)0 == ppcNew) {
		return ppcOld;
	}

	/* End of new list, copy old on the end, if any
	 */
	if ((char *)0 == *ppcNew) {
		register char **ppcCopy;

		for (ppcCopy = ppcNew; (char *)0 != *ppcOld; ++ppcOld) {
			if (!fAugment && IsWrapEnv(*ppcOld))
				continue;
			*ppcCopy++ = *ppcOld;
		}
		*ppcCopy = (char *)0;
		return ppcNew;
	}
	/* End of old list, return new list.
	 */
	if ((char **)0 == ppcOld || (char *)0 == *ppcOld) {
		return ppcNew;
	}
	/* If the name is wrapper name rm it from the old list
	 */
	pcPick = *ppcOld;
	if (!fAugment && IsWrapEnv(*ppcOld)) {
		return Fold(ppcNew, ppcOld+1);
	}
	if ((char *)0 != (pcEqNew = strchr(*ppcNew, '='))) {
		*pcEqNew = '\000';
	}
	if ((char *)0 != (pcEqOld = strchr(*ppcOld, '='))) {
		*pcEqOld = '\000';
	}
	iCmp = strcmp(*ppcNew, *ppcOld);
	if ((char *)0 != pcEqNew) {
		*pcEqNew = '=';
	}
	if ((char *)0 != pcEqOld) {
		*pcEqOld = '=';
	}
	if (0 == iCmp) {	/* same variable name, keep new one */
		pcPick = *ppcNew;
		Fold(ppcNew+1, ppcOld+1);
	} else if (0 > iCmp) {	/* new is sorted before old */
		pcPick = *ppcNew;
		Fold(ppcNew+1, ppcOld);
	} else {		/* new is after old, slide new down */
		register int i;
		register char *pc, *pcNext;

		i = 0;
		pcPick = *ppcOld;
		pcNext = (char *)0;
		do {
			pc = ppcNew[i];
			ppcNew[i++] = pcNext;
			pcNext = pc;
		} while ((char *)0 != pcNext);
		ppcNew[i] = pcNext;
		(void)Fold(ppcNew+1, ppcOld+1);
	}
	*ppcNew = pcPick;
	return ppcNew;
}

/* Add or replace the wrapper list in the current environment		(ksb)
 * N.B. The caller may _not_ free the results here.  And substrings of
 * psList may be included in the new environment, or not.
 */
static char **
ModEnv(int iCnt, char *psList, char **ppcEnv, char *pcReplace, char *pcWith)
{
	register int i, iList, iCntl, iMax, iRepLen = 0;
	register char **ppcRet, **ppcCntls, *pcMod;

	ppcCntls = (char **)0;
	iCntl = 0;
	for (iList = 0; (char **)0 != ppcEnv && (char *)0 != ppcEnv[iList]; ++iList) {
		/* count'em */
		if (0 != strstr(ppcEnv[iList], acIsLink))
			++iCntl;
	}
	qsort((void *)ppcEnv, iList, sizeof(char *), OrderEnv);
	/* Worst case size of new environment is old + wrap-env + NULL
	 * If we augment a stack we keep the previous link value to
	 * offset to the additional elements depth (except _d, which we
	 * just overwrite).
	 * Record all old _link values, as they were under fAugment.
	 */
	iMax = iList + iCnt + 1;
	if ((char **)0 == (ppcRet = calloc((iMax|1)+1, sizeof(char *)))) {
		fprintf(stderr, "%s: calloc: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}
	if (!fAugment) {
		/* nada */
	} else if ((char **)0 == (ppcCntls = calloc(++iCntl, sizeof(char *)))) {
		fprintf(stderr, "%s: calloc: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	} else {
		register char **ppc, *pcEq, *pcSnip, *pcMem;

		ppc = ppcCntls;
		iCntl = 0;
		/* Never add "Bogus=my_link=fake" but allow "my_link=d",
		 * because that is harmless and useful. --ksb
		 */
		for (iList = 0; (char **)0 != ppcEnv && (char *)0 != ppcEnv[iList]; ++iList) {
			if (0 == (pcSnip = strstr(ppcEnv[iList], acIsLink)))
				continue;
			pcEq = strchr(ppcEnv[iList], '=');
			if (pcEq < pcSnip /* || !(isdigit(pcEq[1]) || 'd' == pcEq[1])*/)
				continue;
			iMax = strlen(ppcEnv[iList]);
			if ((char *)0 == (pcMem = calloc(iMax, sizeof(char)))) {
				fprintf(stderr, "%s: calloc: %s\n", progname, strerror(errno));
				exit(EX_OSERR);
			}
			snprintf(pcMem, iMax, "%.*s%c%s", (int)(pcSnip-ppcEnv[iList]), ppcEnv[iList], '\000', pcEq+1);
			*ppc++ = pcMem;
		}
		*ppc = (char *)0;
	}

	if ((char *)0 != pcReplace) {
		iRepLen = strlen(pcReplace);
	}
	for (i = 0; '\000' != *psList; (ppcRet[i++] = pcMod), psList += strlen(psList)+1) {
		register char *pcEq, *pcNew;
		pcMod = psList;
		if ((char *)0 == pcReplace || !IsWrapEnv(pcMod)) {
			continue;
		}
		pcEq = strchr(pcMod, '=');	/* IsWrapEnv saw one */
		if (0 != strncmp(pcEq+1, pcReplace, iRepLen)) {
			continue;
		}
		*pcEq++ = '\000';
		pcEq += iRepLen;

		/*    "env_name" + '=' + "new-text"  +  "suffix"  + '\000'
		 */
		iMax = strlen(pcMod)+1+strlen(pcWith)+strlen(pcEq)+1;
		if ((char *)0 == (pcNew = malloc((iMax|7)+1))) {
			fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno));
			exit(EX_OSERR);
		}
		snprintf(pcNew, iMax, "%s=%s%s", pcMod, pcWith, pcEq);
		pcMod = pcNew;
		psList = pcEq;	/* skip debris we left */
	}
	ppcRet[i] = (char *)0;
	if ((char **)0 != ppcCntls && (char *)0 != ppcCntls[0]) {
		for (iList = 0; iList < i; ++iList) {
			ppcRet[iList] = Remap(ppcRet[iList], ppcCntls);
		}
	}
	qsort((void *)ppcRet, i, sizeof(char *), OrderEnv);
	return Fold(ppcRet, ppcEnv);
	/* We didn't free ppcCntls, which is not a big deal and surely not
	 * worth an alloca(3) call.  We might loose ~20 bytes of memory.
	 */
}

/* return the concat of the two ps's.					(ksb)
 */
static char *
Cats(char *psOrig, char *psAdd)
{
	register size_t w, wLeft, wRight;
	register char *ps, *pcMem;

	if ((char *)0 == psOrig)
		return psAdd;
	if ((char *)0 == psAdd)
		return psOrig;

	/* Count chars in each ps, not the sentinal '\000' in Orig
	 */
	for (wLeft = 0, ps = psOrig; '\000' != *ps; ps += w, wLeft += w)
		w = strlen(ps)+1;
	for (wRight = 1, ps = psAdd; '\000' != *ps; ps += w, wRight += w)
		w = strlen(ps)+1;

	w = ((wLeft+wRight)|7)+1;
	if ((char *)0 == (pcMem = malloc(w))) {
		fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}
	(void)memcpy(pcMem+wLeft, psAdd, wRight);
	return memcpy(pcMem, psOrig, wLeft);
}
/* If you have a wrapw connection on stdin we'll take it:
 * said service must provide 'R', 'U', and '.' commands.
 * 'E' would be nitfy for -W request too.  If we can't
 * replace stdin with tty or null we'll use stderr,
 * traditionally stderr is read/write for emergencies.
 */
static int /* fd */
FindMyself(char *pcExplicit, const char **ppcOut)
{
	register int sRet;
	register const char *pcOuter;

	if ((char *)0 != pcTags && '-' == pcTags[0] && '\000' == pcTags[1]) {
		pcOuter = "stdin";
		if (-1 == (sRet = dup(0))) {
			fprintf(stderr, "%s: dup: %s\n", progname, strerror(errno));
			exit(EX_OSERR);
		}
		close(0);
		if (-1 == open("/dev/tty", O_RDONLY, 0) && -1 == open("/dev/null", O_RDONLY, 0))
			dup(2);
	} else {
		pcOuter = divSelect();
		if (-1 == (sRet = divConnect(pcOuter, RightsWrap, (void *)0)) && -1 == (sRet = MakeClientPort(pcOuter))) {
			fprintf(stderr, "%s: %s: cannot establish client connection\n", progname, pcOuter);
			exit(EX_DATAERR);
		}
	}
	if ((const char **)0 != ppcOut) {
		*ppcOut = pcOuter;
	}
	return sRet;
}

/* We are the client side, like xclate we include a client wrapper.	(ksb)
 * Unlike other wrappers, the client doesn't really need an active
 * manager, a file works as weill.  How odd.
 */
static void
Client(int argc, char **argv)
{
	register int sHandle, iMemSize;
	register char *pcArgv0, *pcN, *psEnv;
	register pid_t wReap;
	register unsigned uN;
	auto char *pcRep;
	auto int wStatus;
	auto struct sigaction saWant;
	auto struct stat stCheck;
	static char *apcDefCmd[] = { "/bin/sh", (char *)0 };

	if ((char **)0 == argv || (char *)0 == argv[0]) {
		argv = apcDefCmd;
		if ((char *)0 != (pcN = getenv("SHELL")))
			apcDefCmd[0] = pcN;
	}

	(void)memset((void *)& saWant, '\000', sizeof(saWant));
	saWant.sa_handler = (void *)Death;
#if HAVE_SIGACTION
	saWant.sa_sigaction = (void *)Death;
#endif
	saWant.sa_flags = SA_RESTART;
	if (-1 == sigaction(SIGCHLD, & saWant, (struct sigaction *)0)) {
		fprintf(stderr, "%s: sigaction: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}

	/* Allow a non-sockets to contain the proposed environment adds
	 * inv. both sides set psEnv and update pcTags
	 */
	stCheck.st_mode = S_IFLNK;	/* if we don't call stat */
	if ((char *)0 != pcTags && !('-' == pcTags[0] && '\000' == pcTags[1]) && -1 != stat(pcTags, &stCheck) && !S_ISSOCK(stCheck.st_mode) && -1 != (sHandle = open(pcTags, O_RDONLY, 0))) {
		register char *psNew, *psRead;

		if (S_ISDIR(stCheck.st_mode)) {
			fprintf(stderr, "%s: %s: %s\n", progname, pcTags, strerror(EISDIR));
			exit(EX_SOFTWARE);
		}

		/* When the file is empty we force no wrappers in, and
		 * by default we remove the existing, that's usefull too.
		 */
		psEnv = strdup("");
		pcRep = (char *)0;
		while ((char *)0 != ReadTable(sHandle, (char *)0)) {
			if ((char *)0 == (psRead = ReadTable(sHandle, (char *)0))) {
				fprintf(stderr, "%s: %s: Premature EOF\n", progname, pcTags);
				exit(EX_SOFTWARE);
			}
			if ((char *)0 == (psNew = Cats(psEnv, psRead))) {
				fprintf(stderr, "%s: out of memory\n", progname);
				exit(EX_OSERR);
			}
			free((void *)psRead);
			psRead = (char *)0;
			free((void *)psEnv);
			psEnv = psNew;
		}
		pcTags = (char *)0;
		close(sHandle);
		fQuit = 0;	/* you can't quit a file */
	} else {
		auto const char *pcOuter;

		if ((char *)0 == pcTags) {
			pcTags = divSelect();
		}
		sHandle = FindMyself(pcTags, &pcOuter);

		/* Expect "msize\n" "varN=valueN\000"* "\000" file on any
		 * "-Message" replies.  'R' becomes 'E' to restore whole env.
		 */
		if (-1 == (iMemSize = PTBCmdInt(sHandle, fWhole ? 'E' : 'R'))) {
			fprintf(stderr, "%s: write: %s: %s\n", progname, pcOuter, strerror(errno));
			exit(EX_PROTOCOL);
		}
		/* Under -t (aka from sshw) we need to remap things that point
		 * to our original socket to mention the local proxy name.
		 */
		if ((char *)0 == (psEnv = malloc((iMemSize|7)+1))) {
			fprintf(stderr, "%s: malloc: %ld: %s\n", progname, (long)iMemSize, strerror(errno));
			exit(EX_OSERR);
		}
		if (-1 == ReadBlock(sHandle, psEnv, iMemSize)) {
			fprintf(stderr, "%s: %s: read environment: %s\n", pcOuter, progname, strerror(errno));
			exit(EX_PROTOCOL);
		}
		if ((char *)0 != pcTags) {
			if (1 != write(sHandle, "U", 1)) {
				fprintf(stderr, "%s: write: %d: %s\n", progname, sHandle, strerror(errno));
				exit(EX_OSERR);
			}
			pcRep = ReadTable(sHandle, (char *)0);
		}
	}
	for (uN = 1, pcN = psEnv; '\000' != *pcN; ++uN) {
		pcN += strlen(pcN)+1;
	}

	if (!fQuit) {
		if (-1 != sHandle)
			close(sHandle);
		sHandle = -1;
	}
	environ = ModEnv(uN, psEnv, environ, pcRep, pcTags);
	switch (-1 == sHandle ? 0 : (wInferior = fork())) {
	case -1:
		fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	case 0:
		if (-1 == sHandle)
			CleanUp();
		else
			close(sHandle);
		pcArgv0 = argv[0];
		if (0 != strcmp(acMyDefName, progname)) {
			argv[0] = progname;
		}
		(void)execvp(pcArgv0, argv);
		fprintf(stderr, "%s: execvp: %s: %s\n", progname, argv[0], strerror(errno));
		exit(EX_OSERR);
	default:
		break;
	}

	while (0 < (wReap = wait3(& wStatus, 0, (struct rusage *)0))) {
		if (wInferior != wReap)
			continue;
		wInferior = 0;
		wInfExits = wStatus;
	}

	/* Tell the master to abandon forever (:) mode, we get back the
	 * number of client connected (ourself included) which we ignore.
	 * If we got back "-No\n", then the master was not in : mode -- ksb
	 */
	if (fQuit) {
		auto char acTrash[64];
		write(sHandle, "Q", 1);
		read(sHandle, acTrash, sizeof(acTrash));
		/* The number of clients left is not useful to us. */
	}
	close(sHandle);
	exit(wInfExits);
}

/* The client sent us a suffix on our socket name send them back	(ksb)
 * a socket connected to the original wrapper.  We assume that all
 * wrappers can accept a socket without any special options set _before_
 * the connection is established.  LLL add options if ever needed.
 * pcReq is m/[\d]+$/ for a plain connection m/[\d+][GND]/ reserved
 * for example 2N might send the pid creds ptbw wants.  The "+"
 * command never makes it here to catch the postfix letters.
 */
static int
MapReply(char *pcReq, char *pcOpts)	/*ARGSUSED*/
{
	register int iCmp, i;
	register char *pcLook;

	if ((char **)0 == ppcTableau) {
		return -1;
	}
	iCmp = atoi(pcReq);
	for (i = 0; (char *)0 != (pcLook = ppcTableau[i]); ++i) {
		if (iCmp == atoi(pcLook))
			break;
	}
	if ((char *)0 == pcLook || (char *)0 == (pcLook = strchr(pcLook, ':'))) {
		return -1;
	}
	return divConnect(++pcLook, RightsWrap, (void *)0);
}

/* Receive an array of descriptors through a unix domain socket       (ksb/bj)
 * return number of descriptors found.  Works on plain text too.
 * Copied from RightsRecvFd with a spin on it -- ksb.
 */
int
MaybeRights(s, piFds, iFdCount, pcText, piTextLen)
int s;
int *piFds;
unsigned iFdCount;
char *pcText;
size_t *piTextLen;
{
	register int error, iLen;
	auto struct msghdr msg;
	auto struct iovec iov;
#if HAVE_SCM_RIGHTS
	register struct cmsghdr *pcmsg;
	register int e;

	iLen = sizeof(struct cmsghdr) + sizeof(int)*iFdCount;
	if ((struct cmsghdr *)0 == (pcmsg = (struct cmsghdr *) malloc(iLen))) {
		errno = ENOMEM;
		return -1;
	}
	pcmsg->cmsg_len = iLen;
	pcmsg->cmsg_level = SOL_SOCKET;
	pcmsg->cmsg_type = SCM_RIGHTS;
	msg.msg_control = (caddr_t) pcmsg;
	msg.msg_controllen = pcmsg->cmsg_len;
#else
	msg.msg_accrights = (caddr_t) piFds;
	msg.msg_accrightslen = sizeof(int) * iFdCount;
#endif /* HAVE_SCM_RIGHTS */

	/* no name (assume socket is connected) */
	msg.msg_name = (char *)0;
	msg.msg_namelen = 0;
	/* some sync data data and our file descriptors */
	iov.iov_base = pcText;
	iov.iov_len = (size_t *)0 == piTextLen ? 0 : *piTextLen;
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;

	error = recvmsg(s, &msg, 0);
	if ((size_t *)0 != piTextLen) {
		*piTextLen = error;
	}

#if HAVE_SCM_RIGHTS
	iLen = (0 == msg.msg_controllen) ? 0 : (msg.msg_controllen - sizeof(struct cmsghdr));
	{
		e = errno;
		memcpy(piFds, pcmsg+1, iLen);
		free((void *)pcmsg);
		errno = e;
	}
#else
	iLen = msg.msg_accrightslen;
#endif /* HAVE_SCM_RIGHTS */

	return iLen/sizeof(int);
}

/* Record the wrapper environment in a plan file for -t			(ksb)
 * 'U', 'R' -> file
 */
static void
Scribe(int argc, char **argv)
{
	register int sHandle, iCc;
	auto const char *pcOuter;
	auto char acPump[8*1024];
	auto struct stat stSock;

	if (0 == argc) {
		/* use stdout */
	} else if (1 != argc) {
		fprintf(stderr, "%s: -R: too many output filenames\n", progname);
		exit(EX_USAGE);
	} else if ('-' == argv[0][0] && '\000' == argv[0][1]) {
		/* use stdout */
	} else if ((FILE *)0 == freopen(argv[0], "wb", stdout)) {
		exit(EX_CANTCREAT);
	}

	/* We handle the case were -t is a file, but if you really
	 * want to copy a file you should use cp(1).
	 */
	if ((char *)0 == pcTags) {
		pcTags = divSelect();
	}
	if (-1 == lstat(pcTags, & stSock) || S_ISSOCK(stSock.st_mode)) {
		sHandle = FindMyself(pcTags, &pcOuter);
		if (3 != write(sHandle, fWhole ? "UE." : "UR.", 3)) {
			fprintf(stderr, "%s: %s: write: %s\n", progname, pcOuter, strerror(errno));
			exit(EX_PROTOCOL);
		}
	} else if (S_ISDIR(stSock.st_mode)) {
		fprintf(stderr, "%s: %s: is a directory\n", progname, pcTags);
		exit(EX_DATAERR);
	} else if (-1 == (sHandle = open(pcTags, O_RDONLY, 0444))) {
		fprintf(stderr, "%s: open: %s: %s\n", progname, pcTags, strerror(errno));
		exit(EACCES == errno ? EX_NOPERM : EX_OSERR);
	}

	while (0 < (iCc = read(sHandle, acPump, sizeof(acPump)))) {
		if (-1 == fwrite(acPump, iCc, sizeof(char), stdout)) {
			fprintf(stderr, "%s: fwrite: %s\n", progname, strerror(errno));
			exit(EX_CANTCREAT);
		}
	}
	fflush(stdout);	/* paranoid */
	exit(0);
}


/* The client sends us a number, we send back that diversion		(ksb)
 * as access rights.  They send <digits>'\000', and maybe a 'Q'
 * or 'V' command. We send fd rights and text back only.  For a
 * new client (a direct connection to establish a new environment)
 * dump our Tableau and our _link variables to the client in a
 * big dump they can give to setenv.
 */
static void
FiniteStates(sCtl)
{
	register int iClients, i, iTok, iGot;
	auto fd_set fdsTemp, fdsReading;
	auto int iMaxPoll, fdSend, aiSocks[2];
	auto char acSend[8192];	/* short reply strings */
	typedef struct FNnode {
		fd_set fdOwn;
	} FINITE_STATE;

	aiSocks[0] = aiSocks[1] = -1;
	iClients = 0;
	iMaxPoll = sCtl;
	FD_ZERO(& fdsReading);
	FD_SET(sCtl, & fdsReading);
	while (0 != wInferior || 0 != iClients) {
		fdsTemp = fdsReading;
		if (-1 == (iGot = select(iMaxPoll+1, &fdsTemp, (fd_set *)0, (fd_set *)0, (struct timeval *)0))) {
			continue;
		}
		if (0 < iGot && FD_ISSET(sCtl, & fdsTemp)) {
			register int sClient;

			if (-1 == (sClient = accept(sCtl, (struct sockaddr *)0, (void *)0))) {
				if (EINTR == errno)
					continue;
				/* Someone broke our listening socket.
				 */
				FD_CLR(sCtl, & fdsReading);
				close(sCtl);
				continue;
			}
			if (sClient >= iMaxPoll) {
				iMaxPoll = sClient;
			}
			++iClients;
			FD_SET(sClient, & fdsReading);
			--iGot;
		}

		/* Start at stderr+1, don't scan 0, 1, 2 every time -- ksb
		 */
		for (i = 3; 0 != iGot && i <= iMaxPoll; ++i) {
			register int iLen;
			auto char acCmd[64];

			if (!FD_ISSET(i, & fdsTemp) || sCtl == i) {
				continue;
			}
			--iGot;
			iLen = read(i, acCmd, 1);
			if (1 != iLen || '.' == acCmd[0]) {
	drop:
				close(i);
				FD_CLR(i, & fdsReading);
				--iClients;
				continue;
			}
			switch (acCmd[0]) {
			case 'T':	/* send man ppcTableau prefixes */
				SendTable(i, (const char **)ppcTableau);
				continue;
			case 'R':	/* send man client restore env */
				SendTable(i, (const char **)ppcClientRestore);
				continue;
			case 'V':	/* ask for our version (std) */
				if (4 != write(i, acProto, sizeof(acProto)-1))
					goto drop;
				continue;
			case 'Q':	/* if in : mode quit after this (std) */
				if (getpid() == wInferior) {
					wInferior = 0;
					wInfExits = EX_OK;
				}
				if (0 == wInferior) {
					iTok = iClients;
					break;
				}
				write(i, "-No\n", 4);
				continue;
			case '+': /* build a new connection between us (spec) */
				/* read the terminating \000 or \n */
				iLen = read(i, acCmd, 1);
				if (1 == iLen && isdigit(acCmd[0])) {
					/* allow "+2" for "2", sigh */
					goto digit;
				}
				/* we might have "+G" or "+N", ignore cmd */
				if (-1 == socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, aiSocks)) {
					/* oh, crap! */
					write(i, "-Fail\n", 6);
					continue;
				}
				if (-1 == RightsSendFd(i, &aiSocks[1], 1, "+Me\n")) {
					write(i, "-Ouch\n", 6);
					close(aiSocks[1]);
					close(aiSocks[0]);
					continue;
				}
				if (aiSocks[0] >= iMaxPoll) {
					iMaxPoll = aiSocks[0];
				}
				++iClients;
				FD_SET(aiSocks[0], & fdsReading);
				/* XXX is +N, send our creds, not yet */
				close(aiSocks[1]);
				continue;
			case '\"':	/* just for debugging, not optimized */
				write(2, progname, strlen(progname));
				write(2, ": ", 2);
				while (1 == read(i, acCmd, 1) && '\"' != acCmd[0]) {
					write(2, acCmd, 1);
				}
				write(2, "\n", 1);
				continue;
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
	digit:
				/* an indirect client */
				while (1 == read(i, acCmd+iLen, 1) && isdigit(acCmd[iLen])) {
					if (sizeof(acCmd)-1 == ++iLen)
						break;
				}
				acCmd[iLen] = '\000';
				if (-1 == (fdSend = MapReply(acCmd, (char *)0))) {
					write(i, "-No such diversion\n", 19);
					continue;
				}
				write(i, "@", 1);
				if (-1 == RightsSendFd(i, &fdSend, 1, acCmd)) {
					/* ouch, I don't know */
				}
				close(fdSend);
				continue;
			case 'E':	/* ask for our new environt */
				SendTable(i, (const char **)ppcEnvClient);
				continue;
			case 'Y':	/* ask for the original environment */
				snprintf(acSend, sizeof(acSend), "%d\n", uOldSize);
				write(i, acSend, strlen(acSend));
				write(i, ppcOldEnv[0], uOldSize);
				write(1, "\n", 1);
				continue;
			case 'U':	/* ask for our socket name (std) */
				SendString(i, pcMySocket);
				continue;
			case 'C':	/* ptbc ask for meta comments (std) */
				if (8 != write(i, "5\nNone\000\n", 8))
					goto drop;
				continue;
			case 'M':	/* ask for our pid, std */
				iTok = getpid();
				break;
			case '-':
			default:	/* unknonw command */
				write(i, "-No\n", 4);
			case '\n':	/* consume Alex's \n fetish */
			case '\000':
				continue;
			}
			snprintf(acSend, sizeof(acSend), "%d\n", iTok);
			write(i, acSend, strlen(acSend));
		}
	}
}

/* Setup the master process and turn it loose				(ksb)
 */
static void
Master(int argc, char **argv)
{
	register const char *pcArgv0, *pcOuterLevel;
	register unsigned uMasterLevel;
	auto int sCtl;
	static char *apcDefArgv[2];

	if (0 == argc || (char **)0 == argv || (char *)0 == argv[0]) {
		argc = 1;
		argv = apcDefArgv;
		if ((char *)0 == (apcDefArgv[0] = getenv("SHELL"))) {
			apcDefArgv[0] = "sh";
		}
		apcDefArgv[1] = (char *)0;
	}
	if ((char *)0 == (pcOuterLevel = divCurrent((char *)0))) {
		uMasterLevel = 1;
	} else {
		uMasterLevel = atoi(pcOuterLevel)+1;
	}

	atexit(CleanUp);
	SafePlace(pcOuterLevel);
	sCtl = Bindings(uMasterLevel);
	SetupTableau(environ, pcMySocket);
	if (':' == argv[0][0] && '\000' == argv[0][1] && (char *)0 == argv[1]) {
		wInferior = getpid();
	} else switch (wInferior = fork()) {
	case -1:
		fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
		(void)chdir("/");
		exit(EX_OSERR);
	case 0:
		(void)close(sCtl);
		pcArgv0 = argv[0];
		if (0 != strcmp(acMyDefName, progname)) {
			argv[0] = progname;
		}
		environ = ppcEnvClient;
		(void)execvp(pcArgv0, argv);
		fprintf(stderr, "%s: execvp: %s: %s\n", progname, argv[0], strerror(errno));
		exit(EX_OSERR);
	default:
		break;
	}

	/* We are not really a daemon, but we don't want to be in the way,
	 * so free us from stdin, stdout, and the current working dir.
	 * We keep stderr for messages, but shouldn't spew any.
	 */
	(void)close(0);
	(void)open("/dev/null", O_RDONLY, 0666);
	(void)close(1);
	(void)open("/dev/null", O_WRONLY, 0666);
	(void)chdir(pcMySpace);
	FiniteStates(sCtl);
	close(sCtl);
	(void)chdir("/");
	exit(wInfExits);
}
