#!/usr/bin/env mkcmd
# A replacement for distrib which looks more like xapply.
# $Compile(*): %b -mMkcmd -dprog %f && %b -mCompile -d%s prog.c
# $Compile: %b -mMkcmd -dprog %f && %b -mCompile prog.c
# $Mkcmd(*): ${mkcmd:-mkcmd} -n "%s" %f
comment "%c%kCompile: $%{gcc-gcc%} -g -Wall prog.c"
comment "%c%kCompile(1): $%{gcc-gcc%} -g -DDEBUG=1 -Wall prog.c"
from '<sys/types.h>'
from '<sys/param.h>'
from '<sys/socket.h>'
from '<sys/mman.h>'
from '<sys/wait.h>'
from '<fcntl.h>'
from '<signal.h>'
from '<sys/time.h>'
from '<sys/uio.h>'
from '<strings.h>'
from '<unistd.h>'
from '<errno.h>'
from '<sys/stat.h>'
from '<sysexits.h>'
from '<time.h>'

getenv ;
init 3 "(void)GtfwTrap(argc, argv);"
char* named "pcETmp" { local hidden }
init 8 'if ((char *)0 != (pcETmp = getenv("HXMD_PASS")) && \'\\000\' != *pcETmp) {u_envopt(pcETmp, & argc, & argv);} else if ((char *)0 != (pcETmp = getenv("HXMD"))) {u_envopt(pcETmp, & argc, & argv);}'


require "util_ppm.m" "util_errno.m"
require "std_version.m" "std_help.m"
require "evector.m" "hostdb.m" "slot.m"

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

from '"machine.h"'

# for/from xapply
boolean 'n' {
	named 'fExec'
	help "passed to xapply as given"
}
boolean 'x' {
	named "fTrace"
	help "passed to xapply as given"
}
letter 'a' {
	named "cEscape" "pcEscape"
	track "uGaveEscape"
	param "c"
	help "passed to xapply when given"
}
type "argv" 'e' {
	named "pPPMXapplyEnv"
	param "var=dicer"
	help "xapply environment modifier for each process"
}
boolean 'A' {
	named "fAppend"
	help "passed on to xapply when given"
}
char* 't' {
	named "pcThreads"
	param "file"
	help "passed on xapply when given"
}
char* 'R' {
	named "pcReq"
	param "req"
	help "passed on to xapply (for ptbw) when given"
}
char* 'J' {
	named "pcTasks"
	param "tasks"
	help "passed on to xapply (for ptbw) when given"
}
char* 'i' {
	named "pcXInput"
	param "input"
	help "xapply input option, when given"
}
unsigned 'P' {
	named "uParallel"
	init "6"
	help "number of tasks xapply runs in parallel, defaults to %i"
	param "[jobs]"
	noparam "if (%ypi == (%N = getenv(\"PARALLEL\")) || '\\000' == *%N) {%N = \"1\";}"
	after "if (%n < 1) {%n = 1;}"
}
char* 'S' {
	named "pcExShell"
	param "shell"
	help "xapply shell option, when given"
}
char* 'N' {
	param "else"
	named "pcExElse"
	help "xapply zero-pass command, when given"
}

# for/from xclate
char* 'L' {
	named "pcXclL"
	param "cap"
	help "passed to xclate as given"
}
char* 'H' {
	named "pcXclH"
	param "hr"
	help "passed to xclate as given"
}
char* 'T' {
	named "pcXclT"
	param "title"
	help "passed to xclate as given"
}
char* 'W' {
	named "pcXclW"
	param "widow"
	help "passed to xclate as given"
}
char* 'O' {
	named "pcXclO"
	param "output"
	help "passed to xclate as given"
}
type "argv" 'r' {
	named "pPPMReRuns"
	track "uGaver"
	param "rerun"
	help "process status from each command with STATUS and U defined"
}
type "argv" 'Q' {
	named "pPPMWards"
	track "uGaveQ"
	param "over"
	help "m4 commands included at the top of the rerun processing"
}
char* 'K' {
	named "pcKleaner"
	param "filter"
	help "cleanup hook to process rerun script"
}

# options passed though to m4, dnl
function 'D' {
	named "AddToArgs"
	update "%n(& PPMM4, %w, (isalnum(*%a) || '_' == *%a) ? %a : %a+1);"
	user "%n(& PPMAutoDef, '\\000', %a);"
	param "name=value"
	help "filter defines passed to every m4"
}
function 'U' {
	named "AddToArgs"
	update "%n(& PPMM4, %w, %a);"
	param "name"
	help "filter undefines passed to every m4"
}
function 'I' {
	named "AddToArgs"
	update "%n(& PPMM4, %w, 0 == strcmp(\"--\", %a) ? acTilde : %a);"
	param "dirname"
	help "include directory name passed to every m4"
}
type "argv" 'j' {
	named "pPPMM4Prep"
	param "m4prep"
	help "include specified files in every m4 process\'s arguments"
}
accum[""] 'd' {
	named "pcDebug"
	param "flags"
	user "if ((char *)0 != strchr(%a, '?')) {DebugHelp(stderr);}"
	after "if ((char *)0 == %n) {%n = \"\";}"
	help "debug mask, last specified is passed to m4"
}

# our options
boolean 'g' {
	named "fGtfw"
	help "assure that this instance is wrapped by gtfw"
}
action 'z' {
	from '<stdio.h>'
	update 'setenv("HXMD_PASS", "", 1);'
	help "remove HXMD_PASS from the environment"
}
number {
	named "uPreLoad"
	init "2"
	param "preload"
	help "number of preload slots to fill ahead of processing, default %qi"
}
char* 'M' {	# might be used by msrc (or the like) to change m4 defs
	hidden named "pcXM4Define"
	init '"HXMD_"'
	help "macro filename prefix for m4 cross file references"
}
boolean 's' {
	named "fSlowStart"
	init '1'
	help "turn off any slow-start logic"
}
char* 'c' {
	named "pcXCmd"
	track "uGaveCmd"
	param "cmd"
	help "specify a custom cmd for xapply, rather than %%+"
}
integer 'F' {
	named 'iCmdLits'
	init "1"
	param 'literal'
	help "the number of parameters which are literal text (default %qi)"
}
char* named "pcCmd" {
	param "control"
	help "command expanded by our macro filter"
}
left "pcCmd" {
}
list {
	param "files"
	help "list of files to process, - for stdin"
}

%i
static char rcsid[] =
	"$Id: hxmd.m,v 1.108 2012/08/30 00:21:34 ksb Exp $";

/* mmsrc and hxmd share slot.m, we need the +x bit force-set on slot 0
 */
#if !defined(SLOT_0_ORBITS)
#define SLOT_0_ORBITS	0100
#endif

/* Override this in the recipe file, as needed
 */
#if !defined(DEF_HXMD_LIB)
#define DEF_HXMD_LIB	".:/usr/local/lib/hxmd:/usr/local/lib/distrib"
#endif

/* Size of the largest command string between Child and Hxmd
 */
#if !defined(HX_MESSAGE_LEN)
#define HX_MESSAGE_LEN	(1+MAXPATHLEN+1)	/* "%c%s\000" */
#endif

/* Number of slots to work ahead in the child, 1 or 2 is usually
 * enough to keep your machine busy.  Might be a command line option,
 * but few would know how/when to tune it.  We are a function of
 * uParallel, we might want m(Mi)+b on some system.  The code in ReDo
 * depends on this never retuning less than 2 more than iParallel.
 */
#if !defined(HX_AHEAD)
#define HX_AHEAD(Mi)	((Mi)+(uPreLoad < 2 ? 2 : uPreLoad))
#endif

/* Bytes we keep per file, MAXPATHLEN+some, the some for \000 padding
 */
#if !defined(HX_BYTES_PER_FILE)
#define	HX_BYTES_PER_FILE	(MAXPATHLEN+4)
#endif

/* XID length from xclate, we know it is <= 64 today, and we force
 * xapply -u, so it is the number of the task we started, should be
 * at most 6 digits, I'd think.
 */
#if !defined(MAX_XID_LEN)
#define	MAX_XID_LEN	64
#endif
#if MAX_XID_LEN < 10
#abort XID lenght too small to hold a task number
#endif

static const char
	acShell[] = "/bin/sh";
static char acHxmdLib[] =
	"HXMD_LIB";
static char acHxmdSearch[] =
	DEF_HXMD_LIB;
static char acDefCmd[] =
	"HX_CMD";
static char acDevNull[] =
	NULL_DEVICE;
static unsigned uLeftLits, uRightLits, uRightBegin;
%%

int named "bHasRoot" {
	init dynamic "0 == geteuid();"
	hidden help "cache our effective uid's root powers"
}

init 4 "InitVectors();"

%c
static char acMySpace[HX_BYTES_PER_FILE];	/* /tmp/$RANDOM to work in*/

/* The version details we love so much					(ksb)
 */
static void
Version()
{
	register char *pcEnvSearch, *pcFrom, *pcSearch;
	register FILE *fpCheck;
	auto size_t wLen;

	printf("%s:  slot code: %s\n", progname, rcs_slot);
	printf("%s:  hostdb code: %s\n", progname, rcs_hostdb);
	printf("%s:  evector code: %s\n", progname, rcs_evector);
	if ((char *)0 != (pcEnvSearch = getenv(acHxmdLib)) && '\000' != *pcEnvSearch) {
		pcFrom = "environment";
		pcSearch = pcEnvSearch;
	} else {
		pcFrom = "default";
		pcSearch = pcHostSearch;
	}
	printf("%s: default column headers: %%%s\n", progname, pcHOST);
	printf("%s: replace an empty control with: %s\n", progname, acDefCmd);
	printf("%s: auto-define prefix, for indexed files and {B,C,U,OPT,STATUS}: %s\n", progname, pcXM4Define);
	printf("%s: parallel factor: %u, slots %u\n", progname, uParallel, HX_AHEAD(uParallel));
	printf("%s: template for private file space: %s\n", progname, acMySpace);
	printf("%s: %s $%s: %s\n", progname, pcFrom, acHxmdLib, pcSearch);
	printf("%s: %s double-dash directory: %s\n", progname, pcFrom, acTilde);
	printf("%s: cache recipe file: %s\n", progname, acDownRecipe);
	if ((FILE *)0 != (fpCheck = popen("xapply -V", "r")) && (char *)0 != (pcFrom = fgetln(fpCheck, & wLen)) && (char *)0 != (pcFrom = strchr(pcFrom, ','))) {
		do ++pcFrom; while ('\000' != *pcFrom && !isdigit(*pcFrom));
		if (3 > atoi(pcFrom)) {
			printf("%s: xapply major version should be 3, or better (%.2s)\n", progname, pcFrom);
		} else if (44 > atoi(strchr(pcFrom, '.')+1)) {
			printf("%s: xapply minor version must be 44, or better\n", progname);
		}
	} else {
		printf("%s: cannot check xapply version\n", progname);
	}
	if ((FILE *)0 != fpCheck) {
		pclose(fpCheck);
	}
	if ((FILE *)0 != (fpCheck = popen("xclate -V", "r")) && (char *)0 != (pcFrom = fgetln(fpCheck, & wLen)) && (char *)0 != (pcFrom = strchr(pcFrom, ','))) {
		do ++pcFrom; while ('\000' != *pcFrom && !isdigit(*pcFrom));
		if (2 > atoi(pcFrom)) {
			printf("%s: xclate major version should be 2, or better (%.2s)\n", progname, pcFrom);
		} else if (42 > atoi(strchr(pcFrom, '.')+1)) {
			printf("%s: xclate minor version must be 42, or better\n", progname);
		}
	} else {
		printf("%s: cannot check xclate version\n", progname);
	}
	if ((FILE *)0 != fpCheck) {
		pclose(fpCheck);
	}
}

/* tell the customer how to use the -d flag				(ksb)
 */
static void
DebugHelp(FILE *fpOut)
{
	fprintf(fpOut, "%s: -d [BCDGLMRTX,PS][m4-opts]\n", progname);
	fprintf(fpOut, "B\ttrace boolean exclude under -B\n");
	fprintf(fpOut, "C\tdebug cache make requests\n");
	fprintf(fpOut, "D\tdebug m4 defines\n");
	fprintf(fpOut, "G\ttrace guards and compares under -G and -E\n");
	fprintf(fpOut, "L\tdisplay host list\n");
	fprintf(fpOut, "M\tmore debugging of configuration files (missing values)\n");
	fprintf(fpOut, "R\ttrace cleanup of temporary files\n");
	fprintf(fpOut, "T\ttrace temporary files created for literal strings\n");
	fprintf(fpOut, "X\ttrace execution of child processes\n");
	fprintf(fpOut, "PS\tused by msrc and mmsrc\n");
	fprintf(fpOut, "m4-opts\tany other letters the local m4 accepts, filtered with the letters in $HXMD_M4_DEBUG\n");
	exit(EX_OK);
}

/* Remember the original arguments in case we needed to wrap ourself.	(ksb)
 * We strdup the strings that the hostdb code might modify, if we can.
 */
static char **
GtfwTrap(int argc, char **argv)
{
	register int iOrig, iNew;
	static int iArgc = -1;
	static char **ppcArgv = (char **)0;

	if (-1 != argc) {
		iArgc = argc;
		if ((char **)0 == (ppcArgv = calloc(iArgc+4, sizeof(char *))))
			return (char **)0;
		iOrig = iNew = 0;
		ppcArgv[iNew++] = "gtfw";
		ppcArgv[iNew++] = "-m";
		while (iOrig < argc) {
			if ((char *)0 == (ppcArgv[iNew++] = strdup(argv[iOrig++]))) {
				ppcArgv[iNew-1] = argv[iOrig-1];
			}
		}
		ppcArgv[iNew] = (char *)0;
	}
	return ppcArgv;
}

/* Base vectors for the AddToArgs to manipulate, see InitVectors
 */
static PPM_BUF
	PPMM4,		/* m4 [-Uundef] [-Dvar=value] [-Idir] [-dflags]	*/
	PPMXclate,	/* xclate -m [-H..] [...] -N '>&5'		*/
	PPMXapply;	/* -- xapply -[nx]P... -mfuz ... '%+' -...	*/

/* We need to make a few argv segements, this helps.			(ksb)
 * Add a pair like {"-x", "param"} to the end of the vector and slide the
 * NULL over to the right.  N.B. pcValue can be (char *)0 for a switch w/o
 * a parameter, but this doen't work for -Pjobs (or other :: options).
 * In that case pass cLetter = '\000' and pcValue is the "-Whole".
 */
static void
AddToArgs(PPM_BUF *pPPM, int cLetter, char *pcValue)
{
	register unsigned int i;
	register char **ppc;

	if ((char **)0 == (ppc = (char **)util_ppm_size(pPPM, (unsigned)0))) {
		fprintf(stderr, "%s: ppm_size: %d: %s\n", progname, 0, strerror(errno));
		exit(EX_UNAVAILABLE);
	}
	for (i = 0; (char *)0 != ppc[i]; ++i) {
		/*nada*/
	}
	if ((char **)0 == (ppc = (char **)util_ppm_size(pPPM, i+3))) {
		fprintf(stderr, "%s: ppm_size: %d: %s\n", progname, i+3, strerror(errno));
		exit(EX_UNAVAILABLE);
	}
	if ('\000' != cLetter) {
		ppc[i] = strdup("-_");
		ppc[i][1] = cLetter;
		++i;
	}
	ppc[i] = pcValue;
	ppc[i+1] = (char *)0;
}

/* Write a \000 terminated command to our buddy, over a pipe.		(ksb)
 * ignore any pipe signals when fBlock is set.
 */
static int
WriteCmd(int fd, char *pcSend, char *pcMessage, int fBlock)
{
	register int iRet;
	auto struct iovec aIO[2];
	auto struct sigaction saBlock, saRestore;

	if (-1 == fd) {
		return -1;
	}
	aIO[0].iov_base = pcSend;
	aIO[0].iov_len = 1;
	if ((char *)0 == (aIO[1].iov_base = pcMessage)) {
		aIO[1].iov_base = "";
	}
	aIO[1].iov_len = strlen(aIO[1].iov_base)+1;
#if DEBUG
	fprintf(stderr, "%s: WriteCmd(%d, %c%s\\000)\n", progname, fd, *pcSend, (char *)aIO[1].iov_base);
#endif
	if (!fBlock) {
		return writev(fd, & aIO[0], 2);
	}
	(void)memset((void *)&saBlock, '\000', sizeof(saBlock));
	saBlock.sa_handler = SIG_IGN;
	saBlock.sa_flags = SA_RESTART;
	if (-1 == sigaction(SIGPIPE, &saBlock, &saRestore)) {
		fBlock = 0;
	}
	iRet = writev(fd, & aIO[0], 2);
	(void)sigaction(SIGPIPE, &saRestore, (struct sigaction *)0);
	return iRet;
}

/* Read a \000 terminated command from our buddy			(ksb)
 */
static int
ReadCmd(int fd, char *pcMessage)
{
	register int iCc, i, cRet;
	auto char cIn;

	cRet = -1;
	*pcMessage = '\000';
	for (i = -1; 1 == (iCc = read(fd, &cIn, 1)); ++i) {
		if (-1 == i)
			cRet = cIn;
		else if ('\000' == (pcMessage[i] = cIn))
			break;
	}
#if DEBUG
	if (-1 == cRet) {
		fprintf(stderr, "%s: EOF = Cmd(%d, %s)\n", progname, fd, pcMessage);
	} else {
		fprintf(stderr, "%s: %c = Cmd(%d, %s)\n", progname, cRet, fd, pcMessage);
	}
#endif
	return cRet;
}

static unsigned uRecSz;		/* Max size of any file list	*/
static unsigned uPrep;		/* Number of slots we use	*/

/* Filter all the files for each host recovered from			(ksb)
 * the host database information, and start the tasks under xapply.
 * We wait for Hxmd to fire up (below), then let him know that we'd
 * like to start some tasks, he tells us what to start and when to
 * cleanup a slot (then refill it).
 */
static META_HOST *
Child(int fdHxmd, char *pcFiles, char **argv, char **ppcM4, const int *piIsCache, const char *pcCacheCtl)
{
	auto char acText[HX_MESSAGE_LEN];	/* "%x,%d", or "c$path" */
	register unsigned i, fDid;
	register META_HOST *pMHNext;
	register unsigned uAble, u, uCmdLits;
	register char *pcComma;
	typedef struct SFnode {
		char *pcbase;		/* Concat of paths+'\000'	*/
		unsigned ulen;		/* Length of concat		*/
		struct SFnode *pSFnext;	/* Next slot with same state	*/
		META_HOST *pMHredo;	/* for -r to redo each host	*/
	} SLOT_FUEL;
	register SLOT_FUEL **ppSF, **ppSFTailR;
	register SLOT_FUEL *pSFTemp;
	auto SLOT_FUEL *pSFInit, *pSFMem, *pSFRun, *pSFReady;
	auto fd_set fdsCheck;
	auto struct timeval tvPoll;
	auto int fdCmd, uSkipped;
	auto META_HOST *pMHRet;

	/* Turn the literal commands into a files, this makes it
	 * way easier to m4 in FillSlot.
	 */
	uCmdLits = uLeftLits + uRightLits;
	for (u = 0; u < uCmdLits; ++u, ++i) {
		i = u < uLeftLits ? u : (uRightBegin+u - uLeftLits);
		snprintf(acText, sizeof(acText), "%s/cmd%u", acMySpace, u);
		if (-1 == (fdCmd = open(acText, O_RDWR|O_CREAT|O_EXCL, 0600))) {
			fprintf(stderr, "%s: open: %s: %s\n", progname, acText, strerror(errno));
			exit(EX_CANTCREAT);
		}
		if ((char *)0 != strchr(pcDebug, 'T')) {
			fprintf(stderr, "%s: echo \"%s\" >%s\n", progname, argv[i], acText);
		}
		write(fdCmd, argv[i], strlen(argv[i]));
		close(fdCmd);
		argv[i] = strdup(acText);
	}

	/* Poll at "fib22 microseconds", about 1/35th of a second
	 * this helps avoid the load spike, but we'll still start about
	 * 32 a second on a fast multiprocessor host -- ksb.
	 */
	tvPoll.tv_sec = 0;
	tvPoll.tv_usec = 28657;

	/* Make a mkstemp suffix that is wide enough, but not too wide.
	 * One character wider than the strlen of the max slot number
	 * works well.  On broken Linux one _must_ use exactly 6 X's.
	 */
#if BROKEN_MKTEMP
	snprintf(acText, sizeof(acText), "XXXXXX");
#else
	snprintf(acText, sizeof(acText), "%u", uPrep);
	for (i = 0; '\000' != acText[i]; ++i) {
		acText[i] = 'X';
	}
	acText[i++] = 'X';
	acText[i] = '\000';
#endif

	/* Build our bookkeepping queues and such
	 */
	uSkipped = 0;
	pSFRun = (SLOT_FUEL *)0;
	pSFReady = (SLOT_FUEL *)0;
	ppSFTailR = & pSFReady;
	if ((SLOT_FUEL *)0 == (pSFMem = (SLOT_FUEL *)calloc(uPrep, sizeof(SLOT_FUEL)))) {
		fprintf(stderr, "%s: calloc(%u, %u): %s\n", progname, uPrep, (unsigned)sizeof(SLOT_FUEL), strerror(errno));
		exit(EX_OSERR);
	}
	ppSF = & pSFInit;
	for (i = 0; i < uPrep; ++i) {
		pSFMem[i].pcbase = & pcFiles[i*uRecSz];
		pSFMem[i].ulen = InitSlot(acText, pSFMem[i].pcbase, argv, acMySpace);
		pSFMem[i].pMHredo = (META_HOST *)0;
		*ppSF = & pSFMem[i];
		ppSF = & pSFMem[i].pSFnext;
	}
	*ppSF = (SLOT_FUEL *)0;
	pMHRet = pMHNext = HostInit(ppcM4);

	/* We depend on our Hxmd to send us an initial "G" command.  Which
	 * tells us to start at uParallel tasks.
	 */
	uAble = 0;
	switch (ReadCmd(fdHxmd, acText)) {
	case 'G':	/* Go! protocol		*/
		uAble = uParallel;
		break;
	case '+':	/* Slow start protocol	*/
		uAble = 1;
		break;
	case 'Q':	/* Parent failed init	*/
		exit(EX_OK);
	default:
		fprintf(stderr, "%s: Child: failed to sync with parent (%s)\n", progname, acText);
		exit(EX_SOFTWARE);
	}

	/* While we have hosts, running tasks, or ready tasks advance the
	 * state-machine
	 */
	FD_ZERO(& fdsCheck);
	while ((META_HOST *)0 != pMHNext || (SLOT_FUEL *)0 != pSFRun || (SLOT_FUEL *)0 != pSFReady) {
#if DEBUG
		fprintf(stderr, "%s: top: allows=%u, init=%p, host=%p, ready=%p, run=%p\n", progname, uAble, pSFInit, pMHNext, pSFReady, pSFRun);
#endif
		fDid = 0;
		/* Build another waiting task, if we can
		 */
		if ((SLOT_FUEL *)0 != pSFInit && (META_HOST *)0 != pMHNext) {
			auto int wExit;
			pSFTemp = pSFInit;
			pSFInit = pSFInit->pSFnext;
			pSFTemp->pMHredo = pMHNext;
			pMHNext->uu -= uSkipped;
			if (0 == (wExit = FillSlot(pSFTemp->pcbase, argv, ppcM4, pMHNext, piIsCache, pcCacheCtl))) {
				*ppSFTailR = pSFTemp;
				ppSFTailR = & pSFTemp->pSFnext;
				pMHNext->istatus = 0;
				fDid = 1;
			} else if (WIFEXITED(wExit)) {
				fprintf(stderr, "%s: %s: %s: exits code %d (host skipped)\n", progname, pMHNext->pchost, ppcM4[0], WEXITSTATUS(wExit));
				pMHNext->istatus = 1000+WEXITSTATUS(wExit);
				snprintf(acText, sizeof(acText), "%u", (unsigned)(pSFTemp-pSFMem));
				WriteCmd(fdHxmd, "s", (char *)pMHNext->pchost, 0);
				++uSkipped;
			} else {
				fprintf(stderr, "%s: %s: %s: caught signal %d (host skipped)\n", progname, pMHNext->pchost, ppcM4[0], WTERMSIG(wExit));
				pMHNext->istatus = 2000+wExit;
				snprintf(acText, sizeof(acText), "%u", (unsigned)(pSFTemp-pSFMem));
				WriteCmd(fdHxmd, "s", (char *)pMHNext->pchost, 0);
				++uSkipped;
			}
			pMHNext = NextHost();
			if (!fDid) {
				pSFTemp->pMHredo->uu = -1;
				pSFInit = pSFTemp;
				continue;
			}
			pSFTemp->pSFnext = (SLOT_FUEL *)0;
		}
		/* We'll queue-for-launch as many as we can, by telling
		 * Hxmd that the slot is ready, see 'r' below
		 */
		while ((SLOT_FUEL *)0 != pSFReady && 0 != uAble) {
			pSFTemp = pSFReady;
			pSFReady = pSFReady->pSFnext;
			if ((SLOT_FUEL *)0 == pSFReady) {
				ppSFTailR = & pSFReady;
			}
			snprintf(acText, sizeof(acText), "%u", (unsigned)(pSFTemp-pSFMem));
			pSFTemp->pSFnext = pSFRun;
			pSFRun = pSFTemp;
			--uAble;
			WriteCmd(fdHxmd, "a", acText, 0);
			fDid = 1;
		}

		/* If we did nothing from the top of the loop to this point
		 * we might as well block wating for Hxmd to talk to us,
		 * else we timeout and build another task to run.
		 */
		FD_SET(fdHxmd, & fdsCheck);
		if (-1 == (i = select(fdHxmd+1, &fdsCheck, (void *)0, (void *)0, fDid ? & tvPoll : (struct timeval *)0))) {
			if (errno == EINTR)
				continue;
			break;
		}
		if (0 == i) {
			continue;
		}
		switch (ReadCmd(fdHxmd, acText)) {
		case -1:	/* Found EOF, or error		*/
			/*fallthrough*/
		case 'Q':	/* Quit now			*/
			break;
		case 'r':	/* Run slot, for real		*/
			i = atoi(acText);
			pSFTemp = pSFMem+i;
			PrintSlot(pSFTemp->pcbase, uLeftLits, uRightBegin, 1);
			continue;
		case 's':	/* skip the slot		*/
			++uAble;
			continue;
		case 'd':	/* Done, move back to init [status,]xid */
			pcComma = (char *)0;
			if ((char *)0 != (pcComma = strchr(acText, ','))) {
				*pcComma++ = '\000';
				i = atoi(pcComma);
			} else {
				i = atoi(acText);
			}
			pSFTemp = pSFMem+i;
			for (ppSF = & pSFRun; (SLOT_FUEL *)0 != *ppSF; ppSF = & ppSF[0]->pSFnext) {
				if (pSFTemp != *ppSF) {
					continue;
				}
				if ((char *)0 != pcComma) {
					pSFTemp->pMHredo->istatus = atoi(acText);
				} else {
					/* We set -r, xapply got a USR1 and
					 * sent a list of skipped xids to us,
					 * so we didn't get a code; set 3000
					 */
					pSFTemp->pMHredo->istatus = 3000;
				}
				*ppSF = pSFTemp->pSFnext;
				pSFTemp = (SLOT_FUEL *)0;
				break;
			}
			if ((SLOT_FUEL *)0 != pSFTemp) {
				snprintf(acText, sizeof(acText), "slot%d: was not on the run list!", i);
				WriteCmd(fdHxmd, "p", acText, 0);
				continue;
			}
			pSFMem[i].pSFnext = pSFInit;
			pSFInit = pSFMem+i;
			++uAble;
			continue;
		case '+':	/* Slow start, start next	*/
			++uAble;
			continue;
		case 'p':	/* Print something		*/
			fprintf(stderr, "%s: %s\n", progname, acText);
			continue;
		case 'V':	/* Return protocol version	*/
			WriteCmd(fdHxmd, "p", "0.4", 0);
			continue;
		}
		break;
	}
	/* Tell xclate we are done, so it will exit, then
	 * shutdown the peer connection to hxmd.
	 */
	argv[0] = "done";
	close(1);
	(void)open("/dev/null", O_WRONLY, 0666);
	close(fdHxmd);
	return pMHRet;
}

/* The parent process reads status data on from the Child and		(ksb)
 * xclate's task notifications stream for status codes.
 * Never exit here, must clean up in caller -- ksb
 */
static void
Hxmd(int fdChild, char *pcFiles, int fdNotify, int *pwElse)
{
	auto char acText[HX_MESSAGE_LEN];
	auto char acStatus[16+MAX_XID_LEN];	/* "%d,%x\000" */
	auto fd_set fdsEither, fdsLoop;
	auto struct timeval tvStart;
	auto time_t wStart, wNow;
	register int iCc, iScan;
	register int iSelMax, iLook, iSlow;
	register unsigned uXid;
	typedef struct SDnode {
		char acxid[MAX_XID_LEN];
	} SLOT_DATA;
	register META_HOST *pMHTrack;
	register SLOT_DATA *pSDMem;
	register struct timeval *ptvBlock;
	register char *pcComma;

	/* Build our slot bookkeepping, just the xid we expect from xclate
	 */
	if ((SLOT_DATA *)0 == (pSDMem = (SLOT_DATA *)calloc(uPrep, sizeof(SLOT_DATA)))) {
		fprintf(stderr, "%s: calloc(%u, %u): %s\n", progname, uParallel, (unsigned)sizeof(SLOT_DATA), strerror(errno));
		WriteCmd(fdChild, "Q", "no memory", 1);
		return;
	}
	for (iScan = 0; iScan < uPrep; ++iScan) {
		pSDMem[iScan].acxid[0] = '\000';
	}

	/* Setup select overhead
	 */
	FD_ZERO(&fdsEither);
	FD_SET(fdChild, &fdsEither);
	FD_SET(fdNotify, &fdsEither);
	iSelMax = (fdChild < fdNotify ? fdNotify : fdChild);

	/* Setup slow-start code, if uParallel is too big, or -s is set.
	 * We let the Child spin 1 task and about ~18 prep slots ahead
	 * of us (fib28/fib22), then we ramp up the tasks about 2 per
	 * second.  So we see more tasks and less prep until we have
	 * uParallel tasks and HX_AHEAD prep -- normal operation.
	 */
	time(& wStart);
	if (!fSlowStart || 1 == uParallel) {
		WriteCmd(fdChild, "G", "child", 0);
		ptvBlock = (struct timeval *)0;
		iSlow = 0;
	} else {
		WriteCmd(fdChild, "+", "slow", 0);
		tvStart.tv_sec = 0;
		tvStart.tv_usec = 514229;	/* fib28 about 0.5s */
		ptvBlock = & tvStart;
		iSlow = uParallel-1;
	}

	uXid = 0;
	for (iLook = 2; 0 != iLook; /*nada*/) {
		fdsLoop = fdsEither;
		if (-1 == (iCc = select(iSelMax+1, & fdsLoop, (void *)0, (void *)0, ptvBlock))) {
			if (errno == EINTR)
				continue;
			break;
		}
		/* When we take the slow-start timeout see if it has been half
		 * a second since the last "+" command.  If so we start one.
		 * Else we poll in 0.317811s (fib27) hoping we'll cross the
		 * second boundry.  The Child is prepping slots with lots of
		 * m4 processes while we stall the tasks, it has time to
		 * prep about 11 more per stall (fib27/fib22).
		 */
		if ((struct timeval *)0 != ptvBlock) {
			time(& wNow);
			tvStart.tv_usec = 317811;
			if (wStart < wNow) {
				WriteCmd(fdChild, "+", "ramp", 1);
				wStart += iSlow & 1;
				if (0 == --iSlow)
					ptvBlock = (struct timeval *)0;
			}
		}
		if (FD_ISSET(fdNotify, &fdsLoop)) {
			static int fOnce = 1;
			iCc = ReadCmd(fdNotify, acStatus+1);
			if (-1 == iCc) {
				FD_CLR(fdNotify, &fdsEither);
				--iLook;
				continue;
			}
			/* Allow a comment in the notify stream, e.g. "#pid".
			 * This would allow us to send a USR2 back to xapply
			 * after a USR1 squelch reqeust.  We expect only digits
			 * (and an optional comma) in any reply from xclate.
			 * We don't have a need for this planned feature, yet.
			 */
			if ('#' == iCc) {
				continue;
			}
			if (!isdigit(iCc)) {
				fprintf(stderr, "%s: %c%s: expected numeric xid\n", progname, iCc, acStatus+1);
				continue;
			}
			acStatus[0] = iCc;
			/* find the code, first
			 */
			if ((char *)0 != (pcComma = strchr(acStatus, ','))) {
				++pcComma;
			} else {
				pcComma = acStatus;
			}
			for (iScan = 0; iScan < uPrep; ++iScan) {
				if (0 == strcmp(pcComma, pSDMem[iScan].acxid))
					break;
			}
			if (iScan != uPrep) {
				snprintf(pcComma, sizeof(acStatus)-4, "%d", iScan);
				WriteCmd(fdChild, "d", acStatus, 0);
				pSDMem[iScan].acxid[0] = '\000';
				continue;
			}

			/* Notification for non-running task?
			 * Was is the "else clause?", the xid is "00" for
			 * that case, and -N must be on our command line.
			 */
			if ('0' == pcComma[0] && '0' == pcComma[1] && '\000' == pcComma[2]) {
				if (pcComma != acStatus) {
					pcComma[-1] = '\000';
				}
				if ((int *)0 != pwElse) {
					*pwElse = atoi(acStatus);
				}
				continue;
			}
			/* Work around xapply 3.16 bug, we hope
			 */
			if (fOnce) {
				fOnce = 0;
				WriteCmd(fdChild, "d", "0", 0);
			}
			fprintf(stderr, "%s: %s: xid not running\n", progname, pcComma);
#if DEBUG
			for (iScan = 0; iScan < uPrep; ++iScan) {
				if ('\000' == pSDMem[iScan].acxid[0])
					continue;
				fprintf(stderr, "\t%d: %s\n", iScan, pSDMem[iScan].acxid);
			}
#endif
			WriteCmd(fdChild, "d", "0", 0);
			continue;
		}
		if (!FD_ISSET(fdChild, &fdsLoop)) {
			continue;
		}
		switch (ReadCmd(fdChild, acText)) {
		case -1:	/* EOF				*/
			/*fallthrough*/
		case 'Q':	/* Child finished, closing	*/
			--iLook;
			FD_CLR(fdChild, &fdsEither);
			continue;
		case 'a':	/* Able {slot} for next host	*/
			iScan = atoi(acText);
			if ((META_HOST *)0 != (pMHTrack = NextHost()))
				pMHTrack->uu = uXid;
			snprintf(pSDMem[iScan].acxid, sizeof(pSDMem[0].acxid), "%u", uXid++);
#if DEBUG
			fprintf(stderr, "%s: got a new task %d \"%s\" -- xid %s\n", progname, iScan, acText, pSDMem[iScan].acxid);
#endif
			WriteCmd(fdChild, "r", acText, 1);
			/* When we are not running we'll not get a notify,
			 * so fake an immediate done reply here.
			 */
			if (fExec) {
				WriteCmd(fdChild, "d", acText, 1);
			}
			continue;
		case 's':	/* skip next host, ack that	*/
			{
			if ((META_HOST *)0 == (pMHTrack = FindHost(acText, strlen(acText)))) {
#if DEBUG
				fprintf(stderr, "%s: skip host failed to find %s\n", progname, acText);
#endif
				continue;
			}
#if DEBUG
			fprintf(stderr, "%s: skip host %s, run the next\n", progname, pMHTrack->pchost);
#endif
			pMHTrack->uu = -1;
			WriteCmd(fdChild, "s", acText, 1);
			}
			continue;
		case 'V':	/* Version command		*/
			WriteCmd(fdChild, "p", "0.4", 0);
			continue;
		case 'p':	/* Print data			*/
			fprintf(stderr, "%s: %s\n", progname, acText);
			continue;
		default:
			fprintf(stderr, "%s: unknown command: ?%s\n", progname, acText);
			break;
		}
		break;
	}
	close(fdNotify);
	if (-1 != fdChild) {
		close(fdChild);
	}
}

/* Build the argument vector PPM spaces, and set some fixed args	(ksb)
 */
static void
InitVectors()
{
	register char **ppc;
	register char *pcEnvTemp;

	/* Copy down debug options m4 knows about
	 */
	util_ppm_init(& PPMM4, sizeof(char *), 32);
	ppc = util_ppm_size(& PPMM4, 32);
	ppc[0] = "m4";
	ppc[1] = (char *)0;

	util_ppm_init(& PPMXclate, sizeof(char *), 32);
	ppc = util_ppm_size(& PPMXclate, 32);
	ppc[0] = "xclate";
	ppc[1] = "-m";		/* in list_func we may replace with "-ms" */
	ppc[2] = (char *)0;

	util_ppm_init(& PPMXapply, sizeof(char *), 32);
	ppc = util_ppm_size(& PPMXapply, 32);
	ppc[0] = "--";
	ppc[1] = "xapply";
	ppc[2] = "-fzmu";
	ppc[3] = (char *)0;
	if ((char *)0 == (pcEnvTemp = getenv("TMPDIR")) || '\000' == *pcEnvTemp) {
		pcEnvTemp = "/tmp";
	}
	snprintf(acMySpace, MAXPATHLEN, "%s/hxtfXXXXXX", pcEnvTemp);
}

/* Build an argv and execvp xclate					(ksb)
 * the new argv is <xclate-args> <xapply-argx>
 */
static void
RunXclate()
{
	register char **ppcArgs, **ppc;

	for (ppc = util_ppm_size(& PPMXapply, 0); (char *)0 != *ppc; ++ppc) {
		AddToArgs(& PPMXclate, '\000', *ppc);
	}
	ppcArgs = util_ppm_size(& PPMXclate, 0);
	if ((char *)0 != strchr(pcDebug, 'X')) {
		fprintf(stderr, "%s:", progname);
		for (ppc = ppcArgs; (char *)0 != *ppc; ++ppc) {
			fprintf(stderr, " %s", *ppc);
		}
		fprintf(stderr, "\n");
		fflush(stderr);
	}
	/* We go ahead and run the the xclate under -n, it won't run others
	 */
	(void)execvp("xclate", ppcArgs);
	(void)execvp("/usr/local/bin/xclate", ppcArgs);
	fprintf(stderr, "%s: execvp: xclate: %s\n", progname, strerror(errno));
	exit(EX_OSFILE);
}

/* Run a post loop command to cleanup					(ksb)
 * Build a HXMD_0..N for the orginal argv list, build a selection
 * based on -r for each host processed,
 */
int
ReDo(META_HOST *pMHList, char *pcTemp, char **argv, char **ppcM4Argv)
{
	static char acSTATUS[] = "STATUS", acU[] = "U";
	register unsigned uCount, fIsPipe;
	register FILE *fp;
	register pid_t wExits;
	register char **ppcInfo, **ppcWards, *pcReControl;
	register const char *pcShell;
	auto pid_t wFilter;
	auto int wStatus;
	auto struct stat stFake;
	auto char *apcKleaner[8];

	/* If there is not a request to redo or no hosts done, just return OK
	 */
	if (!uGaver || (META_HOST *)0 == pMHList) {
		return EX_OK;
	}

	/* Put in something for -r if nothing was given, if you really
	 * want nothing give a " " (space).  Default to "host status %u".
	 * buy default -K pages the resultant status, and -Q is nonthing.
	 */
	ppcInfo = util_ppm_size(pPPMReRuns, 2);
	if ((char *)0 == *ppcInfo || ('\000' == ppcInfo[0][0] && (char *)0 == ppcInfo[1])) {
		register int iLen;
		static char acFmt[] = "%s %s%s %s%s";

		iLen = 7|(sizeof(acFmt)+strlen(pcHOST)+sizeof(acSTATUS)+sizeof(acU)+2*strlen(pcXM4Define));
		if ((char *)0 == (ppcInfo[0] = malloc(++iLen))) {
			return EX_CANTCREAT;
		}
		snprintf(ppcInfo[0], iLen, acFmt, pcHOST, pcXM4Define, acSTATUS, pcXM4Define, acU);
		ppcInfo[1] = (char *)0;
	}
	if ((char *)0 == pcKleaner || '\000' == *pcKleaner) {
		register int iLen;
		static char acFmt[] = "${PAGER:-more} %s0";

		iLen = 7|(sizeof(acFmt)+strlen(pcXM4Define));
		if ((char *)0 == (pcKleaner = malloc(++iLen))) {
			return EX_CANTCREAT;
		}
		snprintf(pcKleaner, iLen, acFmt, pcXM4Define);
	}
	if (0 != (fIsPipe = '|' == pcKleaner[0])) {
		++pcKleaner;
	}
	ppcWards = util_ppm_size(pPPMWards, 0);

	stFake.st_mode = 0700;
	stFake.st_uid = getuid();
	stFake.st_gid = getgid();
	SetCurPhase("redo");
	if ((FILE *)0 == (fp = M4Gadget(pcTemp, & stFake, &wFilter, ppcM4Argv))) {
		return EX_UNAVAILABLE;
	}
	SetCurPhase((char *)0);

	fprintf(fp, "define(`%s0',`%s')", pcXM4Define, pcTemp);
	for (uCount = 0; '\000' != argv[uCount]; ++uCount) {
		fprintf(fp, "define(`%s%u',`%s')", pcXM4Define, 1+uCount, argv[uCount]);
	}
	fprintf(fp, "define(`%sC',`%u')dnl\n", pcXM4Define, uCount);
	RecurBanner(fp);
	while ((char *)0 != *ppcWards) {
		fprintf(fp, "%s\n", *ppcWards++);
	}
	for (/* */; (META_HOST *)0 != pMHList; pMHList = pMHList->pMHnext) {
		fprintf(fp, "pushdef(`%s%s',%d)pushdef(`%sU',%d)dnl\n", pcXM4Define, acSTATUS, pMHList->istatus, pcXM4Define, pMHList->uu);
		BuildM4(fp, ppcInfo, pMHList, (char *)0, (char *)0);
#if M4_POPDEF_MANY
		fprintf(fp, "popdef(`%s%s',`%sU')dnl\n", pcXM4Define, acSTATUS, pcXM4Define);
#else
		fprintf(fp, "popdef(`%s%s')popdef(`%sU')dnl\n", pcXM4Define, acSTATUS, pcXM4Define);
#endif
	}
	fclose(fp);
	while (errno = 0, -1 != (wExits = wait(&wStatus)) || EINTR == errno) {
		if (wExits == wFilter)
			break;
	}

	/* We depend on the fact that HX_AHEAD makes at least 2 slots for us
	 * we get another cmd file from the next slot -- ksb.
	 */
	pcReControl = pcTemp+uRecSz;
	SetCurPhase("filter");
	if ((FILE *)0 == (fp = M4Gadget(pcReControl, & stFake, &wFilter, ppcM4Argv))) {
		return EX_UNAVAILABLE;
	}
	SetCurPhase((char *)0);
	fprintf(fp, "define(`%s0',`%s')", pcXM4Define, pcTemp);
	for (uCount = 0; '\000' != argv[uCount]; ++uCount) {
		fprintf(fp, "define(`%s%u',`%s')", pcXM4Define, 1+uCount, argv[uCount]);
	}
	fprintf(fp, "define(`%sC',`%u')dnl\n", pcXM4Define, uCount);
	RecurBanner(fp);
	fprintf(fp, "%s\n", pcKleaner);
	fclose(fp);
	while (errno = 0, -1 != (wExits = wait(&wStatus)) || EINTR == errno) {
		if (wExits == wFilter)
			break;
	}

	/* Using -S is not really a limiter: to work around it set XCLATE to
	 * -e SHELL=what-xapply-wants, or use the HXMD_SHELL to trap it out.
	 */
	if ((char *)0 != (pcShell = getenv("HXMD_SHELL"))) {
		/* OK */
	} else if ((char *)0 != pcExShell) {
		pcShell = pcExShell;
	} else if ((char *)0 == (pcShell = getenv("SHELL"))) {
		pcShell = acShell;
	}
	if ((char *)0 != (apcKleaner[0] = strrchr(pcShell, '/'))) {
		apcKleaner[0] += 1;
	} else {
		apcKleaner[0] = (char *)pcShell;
	}
	apcKleaner[1] = ((char *)0 != strstr(pcShell, "perl")) ? "-e" : "-c";
	apcKleaner[2] = pcReControl;
	apcKleaner[3] = (char *)0;
	switch (wFilter = fork()) {
	case -1:	/* crud */
		return EX_UNAVAILABLE;
	case 0:
		if (fIsPipe) {
			close(0);
			(void)open(pcTemp, O_RDONLY, 0600);
		}
		/* Stdout is open to /dev/null now, shift that to stderr.
		 * I worry about this: we might be Wrong to use stderr as
		 * an output stream.  The theory is that any recovery we
		 * take is in response on an error of some kind. -- ksb
		 */
		close(1);
		dup(2);
		execvp(pcShell, apcKleaner);
		execvp("sh", apcKleaner);
		fprintf(stderr, "%s: execvp: %s: %s\n", progname, pcShell, strerror(errno));
		exit(EX_OSFILE);
	default:
		break;
	}
	while (errno = 0, -1 != (wExits = wait(& wStatus)) || EINTR == errno) {
		if (wExits == wFilter)
			break;
	}
	return wStatus>>8;
}

/* Form the commands and pipes to start the nuke reation by building	(ksb)
 * a cache for m4 processed files, forking the workers. Then provide
 * cleanup for the temp files and directories.
 */
static void
list_func(unsigned argc, char **argv)
{
	auto char acTemp[MAXPATHLEN+512], acRecipe[MAXPATHLEN+4];
	register char *pcEnd, **ppc;
	register char *pcM4Allows;
	register unsigned i, u;
	auto int aiXclatePipe[2], aiNotify[2], aiPeer[2], fdPages, wElse;
	auto size_t iShared, iPageSz;
	auto void *pvShared;
	auto pid_t wXclatePid, wPeerPid, wWait;
	auto int wStatus, iExit, fScriptDash, *piIsCache;
	auto char *pcHostSlot;
	auto char *apcLook[2];
	auto META_HOST *pMHReRun;
	auto const char *pcParamType, *pcCacheCtl;
	static const char
		acCntName[] = "cache control recipe",
		acPlainName[] = "marked-up file";

	/* Make sure 0, 1, 2 are open'd, to avoid corner cases below
	 */
	do {
		i = open(acDevNull, O_RDWR, 0666);
	} while (i > -1 && i < 3);
	close(i);

	/* xapply's default escape is `%'; if you set it in the
	 * environment we are going to be hosed here		-- ksb
	 */
	if (!uGaveEscape) {
		cEscape = '%';
	} else if ('\000' == cEscape) {
		fprintf(stderr, "%s: -a: NUL (\\000) is not going to work as an escape character\n", progname);
		exit(EX_USAGE);
	}
	/* If you gave -A we have to add "$@" (in quotes) to the -c spec
	 * if $SHELL doesn't do "$@" you have a problem.  So I'm not going
	 * to help you use csh, really, and I'm not interested in making a
	 * login shell perl work for you either.		--ksb
	 * xapply implies this should force a token req (mostly):
	 *	fAppend || fGavet || fGaveJ || (fGaveR && 0 != uReq)
	 * under -A we would use the "$@", otherwise use %t*.  Plus, I'll
	 * save you from csh, just this once. But specify -c, if you feel
	 * a perl binge coming on.
	 */
	if (!uGaveCmd) {
		register const char *pcShell, *pcAdd;
		register char *pcBuild;
		static const char
			acCshExpr[] = "$argv[*]",
			acPerlExpr[] = ", @ARGV",
			acShExpr[] = "$@";

		if ((char *)0 == (pcShell = getenv("SHELL"))) {
			pcShell = acShell;
		}
		if (uGaveEscape && 't' == cEscape) {
			snprintf(acRecipe, sizeof(acRecipe), " %c(t*)", cEscape);
		} else {
			snprintf(acRecipe, sizeof(acRecipe), " %ct*", cEscape);
		}
		if (fAppend) {
			if ((char *)0 != strstr(pcShell, "csh"))
				pcAdd = acCshExpr;
			else if ((char *)0 != strstr(pcShell, "perl"))
				pcAdd = acPerlExpr;
			else
				pcAdd = acShExpr;
			pcBuild = acRecipe;
			for (*pcBuild++ = ' '; '\000' != (*pcBuild++ = *pcAdd)
; ++pcAdd)
				if (cEscape == *pcAdd)
					*pcBuild++ = cEscape;
		} else if ((char *)0 != pcThreads || (char *)0 != pcTasks || ((char *)0 != pcReq && 0 != atoi(pcReq))) {
			/* use the %t code above */
		} else {
			acRecipe[0] = '\000';
		}
		if (uGaveEscape && '+' == cEscape) {
			snprintf(acTemp, sizeof(acTemp), "%c(+)%s", cEscape, acRecipe);
		} else {
			snprintf(acTemp, sizeof(acTemp), "%c+%s", cEscape, acRecipe);
		}
		if ((char *)0 == (pcXCmd = strdup(acTemp))) {
			fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno));
			exit(EX_OSERR);
		}
	}

	/* if we are not in a gtfw and we need to be start over
	 */
	if (fGtfw && ((char *)0 == (pcParamType = getenv("gtfw_link")) || '\000' == *pcParamType)) {
		register char **ppcGtfw;
		if ((char **)0 == (ppcGtfw = GtfwTrap(-1, (char **)0))) {
			fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno));
			exit(EX_OSERR);
		}
		if ((char *)0 != strchr(pcDebug, 'X')) {
			fprintf(stderr, "%s:", progname);
			for (ppc = ppcGtfw; (char *)0 != *ppc; ++ppc) {
				fprintf(stderr, " %s", *ppc);
			}
			fprintf(stderr, "\n");
			fflush(stderr);
		}
		(void)execvp("gtfw", ppcGtfw);
		(void)execvp("/usr/local/bin/gtfw", ppcGtfw);
		fprintf(stderr, "%s: execvp: gtfw: %s\n", progname, strerror(errno));
		exit(EX_OSFILE);
	}

	/* Build the M4 phase buffer for HXMD_PHASE=(selection|%d|filter|redo)
	 */
	AddToArgs(&PPMM4, 'D', SetupPhase("PHASE", 64));

	/* Put the fixed m4 prep files in the m4 template options.
	 * Then add the dash for stdin, so m4 will read our pipe.
	 */
	if ((char **)0 != (ppc = util_ppm_size(pPPMM4Prep, 0))) {
		static const char acDex[] = ".m4";

		for (/*above */; (char *)0 != *ppc; ++ppc) {
			if ('-' != ppc[0][0] || '-' != ppc[0][1] || !('/' == ppc[0][2] || '\000' == ppc[0][2])) {
				AddToArgs(&PPMM4, '\000', *ppc);
				continue;
			}
			ppc[0] += 2;
			while ('/' == ppc[0][0]) {
				ppc[0] += 1;
			}
			if ('\000' == ppc[0][0]) {
				/* "/opt/hxmd/lib"+'/'+progname+".m4"+'\000'
				 */
				i = ((strlen(acTilde)+1+strlen(progname)+sizeof(acDex))|7)+1;
				if ((char *)0 == (pcEnd = malloc(i))) {
					fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno));
					exit(EX_OSERR);
				}
				snprintf(pcEnd, i, "%s/%s%s", acTilde, progname, acDex);
			} else {
				/* "/opt/hxmd/lib"+'/'+ppc[0]+'\000'
				 */
				i = ((strlen(acTilde)+1+strlen(*ppc))|7)+1;
				if ((char *)0 == (pcEnd = malloc(i))) {
					fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno));
					exit(EX_OSERR);
				}
				snprintf(pcEnd, i, "%s/%s", acTilde, *ppc);
			}
			AddToArgs(&PPMM4, '\000', pcEnd);
		}
	}
	AddToArgs(&PPMM4, '\000', "-");

	/* Hack alert: we depend on argv extending back a slot,
	 * we know that mkcmd read the pcCmd from that slot so
	 * it is always there, or at least progname should be.
	 */
	--argv, ++argc;
	argv[0] = '\000' == *pcCmd ? acDefCmd : pcCmd;
	uRightBegin = argc;
	uLeftLits = uRightLits = 0;
	if (0 <= iCmdLits) {
		uLeftLits = iCmdLits;
	} else {
		uRightLits = -iCmdLits;
		uRightBegin -= uRightLits;
	}
	i = (uLeftLits + uRightLits);
	if (argc < i) {
		fprintf(stderr, "%s: not enough parameters for literals (%d < %u)\n", progname, argc, i);
		exit(EX_USAGE);
	}

	if ((int *)0 == (piIsCache = calloc((argc|3)+1, sizeof(int)))) {
		fprintf(stderr, "%s: calloc: %u,%d: %s\n", progname, (argc|3)+1, (int)sizeof(int), strerror(errno));
		exit(EX_TEMPFAIL);
	}
	/* Don't even try unless we can see the files in argv, we do
	 * not use access(2) because it is lame.  Don't use open on
	 * non-regular files because they might be FIFOs balanced for
	 * the host list: yes I really do that. -- ksb
	 */
	acTemp[0] = '\000';
	apcLook[1] = (char *)0;
	fScriptDash = 0;
	for (i = 0; i < argc; ++i) {
		register int iOK;
		register char *pcFound;
		auto struct stat stCheck;

		piIsCache[i] = 0;
		if (i < uLeftLits || i >= uRightBegin) {
			continue;
		}
		if ('-' == argv[i][0] && '\000' == argv[i][1]) {
			ReserveStdin(& argv[i], "files");
			fScriptDash = 0 == i;
			continue;
		}
		pcParamType = acPlainName;
		apcLook[0] = argv[i];
		if ('/' == argv[i][0] || ('.' == argv[i][0] && '/' == argv[i][1])) {
			/* explicity do not search for file */
		} else if ((char *)0 == (pcFound = util_fsearch(apcLook, getenv(acHxmdLib)))) {
			fprintf(stderr, "%s: %s: cannot find file\n", progname, argv[i]);
			acTemp[0] = 'n';
			continue;
		} else {
			argv[i] = pcFound;
		}
		if (0 != stat(argv[i], & stCheck)) {
			fprintf(stderr, "%s: stat: %s: %s\n", progname, argv[i], strerror(errno));
			acTemp[0] = 'n';
			continue;
		}

		/* A request for $target.host/ accesses a cache dir via make
		 *	m4Gadget $target.host/Cache.m4 >$tfile
		 *	cd $target.host && make -f $tfile $target >$target
		 * this allows processors other than m4 for mapped files, and
		 * it creates a better host after host state machine.
		 */
		if (S_ISDIR(stCheck.st_mode)) {
			snprintf(acRecipe, sizeof(acRecipe), "%s/%s", argv[i], acDownRecipe);
			if ((char *)0 == (argv[i] = strdup(acRecipe))) {
				fprintf(stderr, "%s: strdup: %s\n", progname, strerror(errno));
				exit(EX_TEMPFAIL);
			}
			if (0 != stat(argv[i] , &stCheck)) {
				fprintf(stderr, "%s: stat: %s: %s\n", progname, argv[i], strerror(errno));
				acTemp[0] = '/';
				continue;
			}
			pcParamType = acCntName;
			piIsCache[i] = 1;
		}
		if (S_ISFIFO(stCheck.st_mode)) {
			/* I don't see any reason to disallow this, and I
			 * can see several uses for it: uniq resources per
			 * host (in a known order), synchronous execution,
			 * or just plain showing off.
			 */
		} else if (!S_ISREG(stCheck.st_mode)) {
			fprintf(stderr, "%s: %s: %s must be plain files or FIFOs\n", progname, acRecipe, pcParamType);
			acTemp[0] = 'm';
			continue;
		} else if (-1 == (iOK = open(argv[i], O_RDONLY, 0666))) {
			fprintf(stderr, "%s: open: %s: %s\n", progname, argv[i], strerror(errno));
			acTemp[0] = 'e';
			continue;
		} else {
			close(iOK);
		}
		if (0 == i && 0 == (0111 & stCheck.st_mode)) {
			fprintf(stderr, "%s: %s: must be executable\n", progname, argv[i]);
			acTemp[0] = 'x';
		}
	}
	if ('\000' != acTemp[0]) {
		exit(EX_NOINPUT);
	}

	/* We use low level I/O tell stdio to turn loose of any pending I/O,
	 * after we pick a preload zero config for any special basenames.
	 */
	if ((char *)0 == pcZeroConfig ? (0 == strcmp("-", progname) || 0 == strcmp("stdin", progname)) : (0 == strcmp("-", pcZeroConfig))) {
		pcZeroConfig = "-";
	} else if ((char *)0 == pcZeroConfig && 0 != strcmp("hxmd", progname)) {
		snprintf(acTemp, MAXPATHLEN, "%s.cf", progname);
		pcZeroConfig = strdup(acTemp);
	}
	fflush(stdout);
	fflush(stderr);

	if ((char *)0 == mkdtemp(acMySpace)) {
		fprintf(stderr, "%s: mkdtemp: %s: %s\n", progname, acMySpace, strerror(errno));
		exit(EX_CANTCREAT);
	}
	acTemp[0] = '-';
	acTemp[1] = '\000';
	pcEnd = acTemp+1;
	if (1 != uParallel && (char *)0 == pcXclH && (char *)0 == pcXclT) {
		*pcEnd++ = 's';
	}
	if (fExec) {
		*pcEnd++ = 'n';
	}
	if (fTrace) {
		*pcEnd++ = 'x';
	}
	if (fAppend) {
		*pcEnd++ = 'A';
	}
	*pcEnd++ = 'P';
	*pcEnd = '\000';
	snprintf(pcEnd, sizeof(acTemp)-strlen(acTemp), "%u", uParallel);
	AddToArgs(& PPMXapply, '\000', strdup(acTemp));
	if ((char *)0 != pcEscape) {
		AddToArgs(& PPMXapply, 'a', pcEscape);
	}
	if ((char *)0 != pcXInput) {
		AddToArgs(& PPMXapply, 'i', pcXInput);
	}
	if ((char *)0 != pcReq) {
		AddToArgs(& PPMXapply, 'R', pcReq);
	}
	if ((char *)0 != pcTasks) {
		AddToArgs(& PPMXapply, 'J', pcTasks);
	}
	if ((char *)0 != pcThreads) {
		AddToArgs(& PPMXapply, 't', pcThreads);
	}
	if ((char **)0 != (ppc = util_ppm_size(pPPMXapplyEnv, 0))) {
		while ((char *)0 != *ppc) {
			AddToArgs(& PPMXapply, 'e', *ppc++);
		}
	}
	if ((char *)0 != pcExShell) {
		AddToArgs(& PPMXapply, 'S', pcExShell);
	}
	if ((char *)0 != pcExElse) {
		AddToArgs(& PPMXapply, 'N', pcExElse);
	}
	if (argc > 1) {
		snprintf(acTemp, sizeof(acTemp), "-%u", argc);
		AddToArgs(& PPMXapply, '\000', strdup(acTemp));
	}
	/* A pcXCmd of the empty string ("") is going to trigger apply's
	 * lame catenation of the args list, I hope the first file is
	 * a script that uses the other files as useful data.		(ksb)
	 */
	AddToArgs(& PPMXapply, '\000', pcXCmd);
	for (i = 0; i < argc; ++i) {
		AddToArgs(& PPMXapply, '\000', "-");
	}

	/* Pass xclate options
	 */
	uGaver |= uGaveQ || (char *)0 != pcKleaner;
	if (uGaver || (char *)0 != pcExElse) {
		AddToArgs(& PPMXclate, 'r', (char *)0);
	}
	if ((char *)0 != pcXclH) {
		AddToArgs(& PPMXclate, 'H', pcXclH);
	}
	if ((char *)0 != pcXclL) {
		AddToArgs(& PPMXclate, 'L', pcXclL);
	}
	/* No xclate title/hr/input means -s is safe and effective,
	 * -P1 obviates the optimization totally.
	 */
	if ((char *)0 != pcXclT) {
		AddToArgs(& PPMXclate, 'T', pcXclT);
	} else if ((char *)0 == pcXclH && (char *)0 == pcXInput && 1 != uParallel) {
		ppc = util_ppm_size(& PPMXclate, 4);
		ppc[1] = "-ms";	/* replacing "-m" from InitVectors */
	}
	if ((char *)0 != pcXclO) {
		AddToArgs(& PPMXclate, 'O', pcXclO);
	}
	if ((char *)0 != pcXclW) {
		AddToArgs(& PPMXclate, 'W', pcXclW);
	}

	/* Tell m4 about any debugging we want to activate
	 */
	if ((char *)0 == (pcM4Allows = getenv("HXMD_M4_DEBUG"))) {
		pcM4Allows = M4_DEBUG_MASK;
	}
	if ('\000' != 0[pcDebug] && '\000' != pcM4Allows[0]) {
		register char *pcCursor, *pcOK, *pcMem;

		pcOK = pcMem = malloc((strlen(pcM4Allows)|3)+5);
		*pcOK++ = '-';
		*pcOK++ = 'd';
		for (pcCursor = pcM4Allows; '\000' != *pcCursor; ++pcCursor) {
			if ((char *)0 == strchr(pcDebug, *pcCursor))
				continue;
			*pcOK++ = *pcCursor;
		}
		*pcOK = '\000';
		if ('\000' != pcMem[2]) {
			ppc[1] = pcMem;
			ppc[2] = (char *)0;
		}
	}

	/* We need at least 1 cache control recipe find the tempfile we're
	 * going to use in the Child (mktemp base "make")
	 */
	if ((char *)0 == (pcHostSlot = HostSlot(acMySpace))) {
		exit(EX_DATAERR);	/* not config files */
	}
	if ((char *)0 == (pcCacheCtl = FindCache("make", pcHostSlot))) {
		exit(EX_SOFTWARE);
	}

	/* The pipes we need for xclate
	 *   Child -[pipe]- xclate in aiXclatePipe
	 *  xclate -[pipe]- Parent in aiNotify
	 */
	if (-1 == pipe(aiXclatePipe) || -1 == pipe(aiNotify)) {
		fprintf(stderr, "%s: pipe: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}
	switch (wXclatePid = fork()) {
	case -1:	/* Who is surprised with all the forks we need */
		fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
		/* We are not commited yet, so just exit */
		exit(EX_OSERR);
	case 0:		/* Child is the xclate process */
		close(aiNotify[0]);	/* We don't read notifications	*/
		close(aiXclatePipe[1]);	/* We don't write to ourself	*/
		/* Our stdin is the xclate pipe's read side
		 */
		if (0 != aiXclatePipe[0]) {
			dup2(aiXclatePipe[0], 0);
			close(aiXclatePipe[0]);
		}
		snprintf(acTemp, sizeof(acTemp), ">&%d", aiNotify[1]);
		AddToArgs(& PPMXclate, 'N', strdup(acTemp));

		RunXclate();
		exit(EX_UNAVAILABLE);
	default:
		close(aiNotify[1]);	/* We don't write notifictation */
		close(aiXclatePipe[0]);	/* We don't read task argvs	*/
		break;
	}

	/* Peer to peer structure include a mmaped shared memory pool to
	 * store the filenames for the temp files and a socketpair:
	 *   child -[pair]- parent in aiPeer
	 * Map in the shared space for the filename buffers after we
	 * fork the xclate (no need for execve to clean it up). Compute
	 * the number of buffers we need via HX_AHEAD().
	 */
	uPrep = HX_AHEAD(uParallel);
	uRecSz = HX_BYTES_PER_FILE*argc;
	iShared = uRecSz*uPrep;
	iPageSz = getpagesize();
	if (0 != (iShared % iPageSz)) {
		iShared = (1 + iShared / iPageSz) * iPageSz;
	}
	/* If we can't map in the shared space we loose
	 */
#if defined(MAP_ANONYMOUS)	/* HPUX */
#if !defined(MAP_ANON)
#define MAP_ANON	MAP_ANONYMOUS
#endif
	fdPages = -1;
#else
#if defined(MAP_ANON)
	fdPages = -1;
#else
#define MAP_ANON	0
	if (-1 == (fdPages = open("/dev/zero", O_RDWR, 0666))) {
		fprintf(stderr, "%s: open: /dev/zero: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}
#endif
#endif
	if (MAP_FAILED == (pvShared = mmap((void *)0, iShared, PROT_READ|PROT_WRITE, MAP_ANON|MAP_NOSYNC|MAP_SHARED, fdPages, (off_t)0))) {
		fprintf(stderr, "%s: mmap anonymous: %lu: %s\n", progname, (unsigned long)iShared, strerror(errno));
		exit(EX_OSERR);
	}
	if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, aiPeer)) {
		fprintf(stderr, "%s: socketpair: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}

	/* If we took the script from stdin we set it +x -- ksb
	 * fScriptDash == argv[0] == Hostslot.stdin, but we can't see that
	 */
	if (fScriptDash && -1 == chmod(argv[0], 0700)) {
		fprintf(stderr, "%s: chmod: %s +x: %s\n", progname, argv[0], strerror(errno));
		exit(EX_OSERR);
	}
	switch (wPeerPid = fork()) {
	case -1:
		/* Now I'm really sad.
		 */
		fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	case 0:		/* We are the peer to m4 process files		*/
		close(aiNotify[0]);	/* We don't read notification	*/
		close(aiPeer[1]);	/* We are not Hxmd		*/
		if (1 != aiXclatePipe[1]) {
			dup2(aiXclatePipe[1], 1);
			close(aiXclatePipe[1]);
		}
		ppc = util_ppm_size(& PPMM4, 0);
		pMHReRun = Child(aiPeer[0], pvShared, argv, ppc, piIsCache, pcCacheCtl);
		exit(ReDo(pMHReRun, pvShared, argv, ppc));
	default:
		close(aiXclatePipe[1]);	/* We don't talk to xclate	*/
		close(aiPeer[0]);	/* We are not the Child		*/
		break;
	}
	wElse = EX_OK;
	Hxmd(aiPeer[1], pvShared, aiNotify[0], & wElse);

	/* Cleanup, processes and tempfiles
	 */
	iExit = EX_OK;
	while (errno = 0, -1 != (wWait = wait(& wStatus)) || EINTR == errno) {
		if (wPeerPid == wWait)
			iExit = wStatus >> 8;
	}
	pcEnd = (char *)pvShared;
	for (i = 0; i < uPrep; ++i) {
		CleanSlot(pcEnd);
		pcEnd += uRecSz;
	}
	CleanSlot(pcHostSlot);
	for (u = uLeftLits+uRightLits; u > 0; /* below */) {
		snprintf(acTemp, sizeof(acTemp), "%s/cmd%u", acMySpace, --u);
		if ((char *)0 != strchr(pcDebug, 'R')) {
			fprintf(stderr, "%s: rm -f %s\n", progname, acTemp);
		}
		if (-1 == unlink(acTemp)) {
			fprintf(stderr, "%s: unlink: %s: %s\n", progname, acTemp, strerror(errno));
		}
	}
	if (0 == rmdir(acMySpace) && (char *)0 != strchr(pcDebug, 'R')) {
		fprintf(stderr, "%s: rmdir %s\n", progname, acMySpace);
	}
	exit(EX_OK == wElse ? iExit : wElse);
}
