#!mkcmd
# $Id: xapply.m,v 3.72 2012/12/21 16:56:37 ksb Exp $
#
# ksb's version of apply.  The Rob Pike version was too strange for me,
# and it used that errx() stuff that I couldn't use any where else.
# This one has some nice features. See the manual page.
#
# mk will compile this if you have other ksb tools (mkcmd, mk) installed.
#	$Compile: %b %o -mMkcmd %f && %b %o prog.c && mv prog %F
#	$Mkcmd: ${mkcmd-mkcmd} %f
	comment "%c %kCompile: $%{cc-gcc%} -g %%f -o %%F"
# [yeah the line above doesn't start with a hash]

from '<sys/types.h>'
from '<sys/param.h>'
from '<sys/wait.h>'
from '<fcntl.h>'
from '<errno.h>'
from '<ctype.h>'
from '<signal.h>'
from '<unistd.h>'
from '<stdlib.h>'
from '<string.h>'
from '<sysexits.h>'

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

require "std_help.m" "std_version.m" "std_control.m"
require "util_pipefit.m" "util_cache.m" "util_divconnect.m"
require "dicer.c"
require "../ptbw/ptbc.m"

basename "xapply" ""

augment action 'V' {
	user "Version();"
}
action 'H' {
	named "QuickRef"
	update "%n(stdout);"
	aborts "exit(EX_OK);"
	help "output only on-line expander markup help"
}
%i
static char rcsid[] =
	"$Id: xapply.m,v 3.72 2012/12/21 16:56:37 ksb Exp $";
extern char **environ;
extern int errno;
static void Tasks(unsigned, unsigned int);
static const char acEmpty[] = "";
static char acXCL_D[] = "xcl_d";
static char acPtbwLink[] = "ptbw_link";
static char acCommandLine[] = "command-line";	/* might rather exit */
static char *pcLoader = (char *)0;

/* we still want to try to work on old-school systems, a little
 */
#if defined(FREEBSD) && HOSTOS < 40000
typedef int socklen_t;
#define HAVE_SIGACTION		0
#endif

#if !defined(HAVE_SIGACTION)
#define HAVE_SIGACTION		1
#endif
%%

boolean 'A' {
	named "fAddEnv"
	init "0"
	help "append the tokens as shell parameters"
}
letter 'a' {
	named "cEsc"
	init "'%'"
	param "c"
	help "change the escape character"
}

number {
	named "iPass"
	param "count"
	init "1"
	help "number of arguments passed to each command"
}

unsigned 'P' {
	named "iParallel"
	init "1"
	help "number of tasks to run in parallel"
	param "[jobs]"
	noparam "if (%ypi == (%N = getenv(\"PARALLEL\")) || '\\000' == *%N) {%N = \"3\";}"
}
boolean 'm' {
	track named "fManage"
	help "use xclate to manage output streams"
}
boolean 'g' {
	named "fGtfw"
	after 'if (%n) {GtfwInit(getenv("gtfw_link"));}'
	help "use gtfw for unique sequential numbers"
}
boolean 's' {
	named "fSqueeze"
	help "pass the squeeze option down to all xclate output filters"
}
boolean 'd' {
	named "fPublish"
	init "1"
	help "keep any xclate usage out of the published chain"
}
boolean 'u' {
	named "fUniq"
	help "force the xclate xid to be the expansion of %%u"
}

char* 'S' {
	named "pcShell"
	init getenv "SHELL"
	before 'if ((char *)0 == %n) {%n = "%D/sh/pv";}'
	param "shell"
	help "the shell to run tasks under"
}

type "argv" 'e' {
	named "pPPMAddEnv"
	param "var=dicer"
	help "set var to the expansion of dicer for each process"
}

char* 'N' {
	named "pcElse"
	param "else"
	help "a command to run when no tasks were otherwise launched"
}
# If we provide a script under -F, then cmd is suppressed for left params
char* variable "pcCmd" {
	exclude 'F'
	param "cmd"
	help "template command to control each task"
}
char* variable "pcScript" {
	hidden
	init "(char *)0"
}
integer 'F' {
	named "fDidF"
	track "fGaveF"
	param "script"
	init "0"
	update "%n = 1;"
	user "pcCmd = pcScript = %a;"
	help "take script file with percent markup, no cmd expected"
}

int named "cEoln" {
	init "'\\n'"
	help ""
}

boolean 'f' {
	named "fpIn" "pcIn"
	help "arguments are read indirectly one per line from files"
	char* 'p' {
		named "pcPad"
		init '""'
		param "pad"
		help "fill short files with this token"
	}
	action 'z' {
		update "cEoln = '\\000';"
		help "read find-print0 output as input files"
	}
	left "pcCmd" {
	}
	list file ["r"] {
		named "pfpIn" "ppcNames"
		param "files"
		user "if (%rFn) {pcCmd = ReadCmd(pcCmd);}if (fZero) {DoZero();}VApply(%#, %n, %N);"
		help "files of arguments to distribute to tasks"
	}
}

char* 'i' {
	named "pcRedir"
	param "input"
	help "change stdin for the child processes"
}
# for ptbw, we can handle -R ourself worst-case
char* 't' {
	track "fGavet"
	named "pcTags"
	param "tags"
	help "list of target resources for %%t, connect to (start) a ptbw"
}
unsigned 'R' {
	track "fGaveR"
	named "uReq"
	init '1'
	param "req"
	help "number of resource allocated to each task, start a ptbw"
}
integer 'J' {
	track "fGaveJ"
	named "iCopies"
	param "tokens"
	help "number of task instances expected, start a ptbw"
}
#end ptbw interface
integer named "fZero" {
	local hidden help "specified -0 count"
}
after {
	named "PostProc"
	update "fZero = %n(argc, argv);"
	help ""
}

left "pcCmd" {
}

list {
	named "Apply"
	param "args"
	update "if (%rFn) {pcCmd = ReadCmd(pcCmd);}if (fZero) {DoZero();}%n(%#, %@);"
	help "list of arguments to distribute to tasks"
}

boolean 'x' {
	named "fTrace"
	help "trace actions on stderr"
}

key "PATH" 1 init {
	"/usr/local/bin" "/usr/bin"
}
key "xclate" 1 init {
	"xclate"
}
key "ptbw" 1 init {
	"ptbw"
}
char* named "pcXclate" {
	before '%n = %K<xclate>?e1qv;'
}
char* named "pcPtbw" {
	before '%n = %K<ptbw>?e1qv;'
}

%c
/* if we do not have NCARGS in <sys/param.h> try posix or guess		(ksb)
 */
#if !defined(NCARGS)
#include <limits.h>
#if defined(ARG_MAX)
#define NCARGS	ARG_MAX
#else
#define NCARGS	4000
#endif
#endif

#if !defined(MAX_XID_LEN)
#define MAX_XID_LEN	64		/* xclate'd xid limit, or so	*/
#endif

/* These all are nice to know						(ksb)
 */
static void
Version()
{
	printf("%s:  %s\n", progname, DicerVersion);
	printf("%s: shell: %s\n", progname, pcShell);
	printf("%s: xclate as %s\n", progname, pcXclate);
	printf("%s: xclate xid length limit %d\n", progname, MAX_XID_LEN);
	printf("%s: xclate -d environment \"%s\"\n", progname, acXCL_D);
	printf("%s: ptbw as %s, protocol %.*s\n", progname, pcPtbw, (int)strlen(PTBacVersion)-1, PTBacVersion);
	printf("%s: ptbw link environment \"%s\"\n", progname, acPtbwLink);
}

/* Output the command -H help for a ksb-stype markup expander		(ksb)
 */
static void
QuickRef(FILE *fp)
{
	fprintf(fp, "See dicer(5) for a complete BNF specification\n");
	fprintf(fp, "    e ::= %c{\'Q\' \'W\' \'q\' \'f\' \'t\'}* dicer\n", cEsc);
	fprintf(fp, "param ::= \'%c\' | \'f\' | \'t\' | \'p\' | \'u\' | \'$\' | \'+\' | number | \'~\' number\n", cEsc);
	fprintf(fp, " %cWE    expand E as a single word, backslash [`$\\\\\"~*?[|&;()#=\'<> \\n\\t]\n", cEsc);
	fprintf(fp, " %cQE    expand E protecting metas, backslash [`$\\\\\"~*?[|&;()#=\'<>]\n", cEsc);
	fprintf(fp, " %cqE    expand E protecting quotes, backslash [`$\\\\\"]\n", cEsc);
	fprintf(fp, " %cft    name of token source\n", cEsc);
	fprintf(fp, " %cfE    shift to filenames that provided specified parameter E\n", cEsc);
	fprintf(fp, " %ctE    shift to token list as parameters for the expansion of E\n", cEsc);
	fprintf(fp, " %cN     select the Nth positional parameter from the left\n", cEsc);
	fprintf(fp, " %c~N    select the Nth parameter from the right\n", cEsc);
	fprintf(fp, " %c{N}   select the multi-digit Nth parameter\n", cEsc);
	fprintf(fp, " %c*     all parameters separated with spaces\n", cEsc);
	fprintf(fp, " %c+     shift parameters left, insert the expansion of the new cmd, continue\n", cEsc);
	fprintf(fp, " %ct+    insert the expansion of next token as a cmd, continue\n", cEsc);
	fprintf(fp, " %c$     the last positional parameter\n", cEsc);
	fprintf(fp, " %c%c     literal %c\n", cEsc, cEsc, cEsc);
	fprintf(fp, " %c0     the empty string, and squelch auto append\n", cEsc);
	fprintf(fp, " %cc     the cmd, %cfc script, %ctc loader\n", cEsc, cEsc, cEsc);
	fprintf(fp, " %cp     process-id for USR1, %cfp progname, %ctp ptbw path\n", cEsc, cEsc, cEsc);
	fprintf(fp, " %cu     unique iteration counter, %cfu gtfw path, %ctu token count\n", cEsc, cEsc, cEsc);
	fprintf(fp, " %c[ECR] dicer expression: split the expansion of E on character C, reduce with R\n", cEsc);
	fprintf(fp, " %c(E,S) mixer expression: expand E, reduce with span S (S,S, S-S, N, ~N, *, $)\n", cEsc);
}

/* Keep up with -t targets -- ksb
 */
typedef struct TEnode {
	char **ppctokens;	/* uReq of them */
	unsigned int *publock;	/* uReq of these too */
	pid_t wlock;
} TARGET_ENV;
static TARGET_ENV *pTEList = (TARGET_ENV *)0;
static unsigned uTargetCursor = 0, uTargetMax = 0;

/* Take a list of tokens and put them in the local bookkeeping format	(ksb)
 * thay can be sep'd with '\n' or '\000', either.
 */
static void
LocalPool(char *pcList, unsigned int uCount, unsigned *puList, unsigned int uBlob)
{
	register TARGET_ENV *pTE;
	register unsigned int i, uFill;
	register char *pcNl, **ppcMem;

	if (0 == uBlob) {
		uBlob = 1;
	}
	ppcMem = (char **)calloc(uCount, sizeof(char *));
	pTEList = (TARGET_ENV *)calloc(iParallel, sizeof(TARGET_ENV));
	if ((TARGET_ENV *)0 == pTEList) {
		fprintf(stderr, "%s: calloc: %u,%u: %s\n", progname, uCount, (unsigned)sizeof(TARGET_ENV), strerror(errno));
		exit(EX_OSERR);
	}
	pTE = pTEList;
	for (i = 0, uFill = uBlob; i < uCount; ++i, ++uFill) {
		if ((char *)0 != (pcNl = strchr(pcList, '\n'))) {
			*pcNl = '\000';
		}
		if (uBlob == uFill) {
			pTE->ppctokens = ppcMem;
			pTE->publock = ((unsigned *)0 == puList) ? (unsigned *)0 : puList+i;
			pTE->wlock = 0;
			++pTE;
			uFill = 0;
		}
		*ppcMem++ = pcList;
		pcList += strlen(pcList)+1;
	}
	uTargetMax = pTE-pTEList;
}

static char **ppcManage, *pcSlaveXid;
/* We need an xclate in the process tree above us.			(ksb)
 * when we don't have one, we become it and make xapply our command.
 */
static void
XCLOverlay(int argc, char **argv)
{
	register char *pcXEnv = (char *)0, *pcTail;
	register char **ppcOver;
	register int i;

	if (fPublish ? ((char *)0 != getenv("xcl_link")) : (char *)0 != (pcXEnv = getenv(acXCL_D))) {
		/* 8 ~= xclate [-u $xcl_d] [-s] xid (char *)0 pad pad
		 */
		ppcManage = (char **)calloc(8, sizeof(char *));
		if ((char **)0 == ppcManage || (char *)0 == (pcSlaveXid = (char *)malloc((MAX_XID_LEN|7)+1))) {
			fprintf(stderr, "%s: building xclate argv: no memory\n", progname);
			exit(EX_OSERR);
		}
		i = 0;
		ppcManage[i++] = pcXclate;
		if (!fPublish) {
			ppcManage[i++] = "-u";
			ppcManage[i++] = strdup(pcXEnv);
			(void)unsetenv(acXCL_D);
		}
		if (fSqueeze) {
			ppcManage[i++] = "-s";
		}
		ppcManage[i++] = strcpy(pcSlaveXid, "~");
		ppcManage[i++] = (char *)0;
		return;
	}
	/* xclate -m[d] [-s] -- argv + (char *)0  =>  4+roundup(argc)
	 */
	ppcOver = (char **)calloc((size_t)(argc|3)+5, sizeof(char *));
	if ((char **)0 == ppcOver) {
		fprintf(stderr, "%s: calloc: %d: cannot allocate argv for %s\n", progname, argc+4, pcXclate);
		exit(EX_OSERR);
	}
	i = 0;
	if ((char *)0 != (pcTail = strrchr(pcXclate, '/'))) {
		++pcTail;
	} else {
		pcTail = pcXclate;
	}
	ppcOver[i++] = pcTail;
	ppcOver[i++] = fPublish ? "-m" : "-md";
	if (fSqueeze) {
		ppcOver[i++] = "-s";
	}
	ppcOver[i++] = "--";
	while (argc-- > 0) {
		ppcOver[i++] = *argv++;
	}
	ppcOver[i] = (char *)0;
	(void)execvp(ppcOver[0], ppcOver);
	(void)execve(pcXclate, ppcOver, environ);
	fprintf(stderr, "%s: execve: %s: %s\n", progname, pcXclate, strerror(errno));
	exit(EX_UNAVAILABLE);
}

static int sMaster = -1;
static char *pcDyna = (char *)0;

/* Get tokens from our ptbw-wrapper					(ksb)
 * send "T", get the list, ask for <iParallel*uReq>G, then
 * back off by 1's to just <uReq>
 */
static char *	/*ARGUSED*/
DynaTokens(char *pcDirect, unsigned int *puMine, unsigned int **ppuOut)
{
	register char *pcRet;
	register unsigned int i, uTry;
	auto char acSource[MAXPATHLEN+4];

	pcRet = (char *)0;
	if ((unsigned int *)0 != puMine) {
		*puMine = 0;
	}
	if ((char *)0 == pcDirect) {
		register char *pcEnv;

		if ((char *)0 == (pcEnv = getenv(acPtbwLink))) {
			fprintf(stderr, "%s: $%s: no enclosing ptbw\n", progname, acPtbwLink);
			exit(EX_DATAERR);
		}
#if HAVE_SNPRINTF
		snprintf(acSource, sizeof(acSource), "ptbw_%s", pcEnv);
#else
		sprintf(acSource, "ptbw_%s", pcEnv);
#endif
		if ((char *)0 == (pcDirect = getenv(acSource))) {
			fprintf(stderr, "%s: $%s: not found in the environment\n", progname, acSource);
			exit(EX_PROTOCOL);
		}
	}
	if (-1 == (sMaster = PTBClientPort(pcDirect))) {
		fprintf(stderr, "%s: %s: no connection: %s\n", progname, pcDirect, strerror(errno));
		exit(EX_NOINPUT);
	}
	if (-1 != PTBReqSource(sMaster, acSource, sizeof(acSource), (char **)0)) {
		pcDyna = strdup(acSource);
	}

	uTry = 0;
	for (i = iParallel; i > 0; --i) {
		uTry = uReq * i;
		if (uTry > uOurCount)
			continue;
		if ((char *)0 != (pcRet = PTBReadTokenList(sMaster, uTry, ppuOut)))
			break;
	}
	if (0 == i) {
		fprintf(stderr, "%s: ptbw cannot provide %u tokens (only %u available)\n", progname, uReq, uOurCount);
		exit(EX_DATAERR);
	}
	if ((unsigned int *)0 != puMine) {
		*puMine = uTry;
	}
	return pcRet;
}

/* If we need a ptb-wrapper around it make it so			(ksb)
 * then use it to get the targets we need, before any kids get them.
 * If we don't need a wrapper just fix up the options and return with
 * the internal target list inplace.
 * "-t -"	-> get tokens from existing ptbw
 * "-t file"	-> start a new ptbw, replace with -t -
 * -J N		-> start a new ptbw, pass down
 * -R rep	-> passed to new ptbw, only as given
 */
static void
PTBOverlay(int argc, char **argv, unsigned int uJobs)
{
	static char acMySelf[] = "XAPPLY_WRAP";
	auto char **ppcPTBArgv;
	auto unsigned uMine, *puList;
	register char *pcList, *pcBuffer;
	register unsigned int i;
	register int iCopy;
	register pid_t wCvt;
	auto char acNumber[64];		/* %ld */

	pcList = (char *)acEmpty;
	puList = (unsigned int *)0;
	uMine = 0;
	wCvt = -1;	/* assume we can't be pid -1 */
	if ((char *)0 != (pcBuffer = getenv(acMySelf)) && '\000' != *pcBuffer) {
		wCvt = atoi(pcBuffer);
		(void)unsetenv(acMySelf);
	}

	/* We are the child of a ptbw we started, or told to look at one
	 * we didn't start then look to it for tokens.  Else we don't really
	 * need the passible transaction buster widgit [:-)].
	 */
	if (wCvt == getppid()) {
		if (0 == uReq) {
			uMine = uJobs;
			pcList = PTBIota((unsigned) uJobs, (unsigned int *)0);
		} else {
			pcTags = "-";
			pcList = DynaTokens((char *)0, & uMine, & puList);
		}
	} else if (fGavet) {
		if (PTBIsSocket(pcTags)) {
			pcList = DynaTokens(pcTags, & uMine, & puList);
		} else if ('-' == pcTags[0] && '\000' == pcTags[1]) {
			pcList = DynaTokens((char *)0, & uMine, & puList);
		} else {
			/* wrap it up, dude */
		}
	} else if (fGaveJ || (fGaveR && 0 != uReq)) {
		/* wrap me up in a default token list */
	} else {
		uMine = uJobs;
		pcList = PTBIota((unsigned) uJobs, (unsigned int *)0);
	}
	if (acEmpty == pcList) {
		/* fall into wrapper below */
	} else if ((char *)0 != pcList) {
		LocalPool(pcList, uMine, puList, uReq);
		return;
	} else {
		fprintf(stderr, "%s: cannot create resource pool\n", progname);
		exit(EX_SOFTWARE);
	}

	/* We need to wrap ourself in a ptbw, build the command line:
	 *	ptwb -m[q] [-Jjobs] [-Rreq] [-t file] -- argv (char *)0
	 *	   1+   1+       1+      1+        2+ 1+ original-argc  +1
	 */
	if ((char **)0 == (ppcPTBArgv = calloc(((1+1+2+2+2+1+(size_t)argc+1)|3)+1, sizeof(char *)))) {
		fprintf(stderr, "%s: calloc: %d,%u: %s\n", progname, (1+1+1+2+1)+argc, (unsigned)sizeof(char *), strerror(errno));
		exit(EX_OSERR);
	}
	i = 0;
	/* mkcmd now searches for this, but that means it must be installed
	 * first and our build path matters.  Since msrc now depends on
	 * ptbw it is important to all.
	 */
	if ((char *)0 != (pcBuffer = strrchr(pcPtbw, '/'))) {
		++pcBuffer;
	} else {
		pcBuffer = pcPtbw;
	}
	ppcPTBArgv[i++] = pcBuffer;
	if (fGaveJ) {
		ppcPTBArgv[i++] = "-m";
	} else {
		ppcPTBArgv[i++] = "-mq";
		iCopies = iParallel;
	}
	if (0 != iCopies) {
#if HAVE_SNPRINTF
		snprintf(acNumber, sizeof(acNumber), "-J%d", iCopies);
#else
		sprintf(acNumber, "-J%d", iCopies);
#endif
		ppcPTBArgv[i++] = strdup(acNumber);
	}
	if (fGaveR && 0 != uReq) {
#if HAVE_SNPRINTF
		snprintf(acNumber, sizeof(acNumber), "-R%d", uReq);
#else
		sprintf(acNumber, "-R%u", uReq);
#endif
		ppcPTBArgv[i++] = strdup(acNumber);
	}
	if (fGavet) {
		ppcPTBArgv[i++] = "-t";
		ppcPTBArgv[i++] = pcTags;
	}
	ppcPTBArgv[i++] = "--";
	for (iCopy = 0; iCopy < argc; ++iCopy) {
		ppcPTBArgv[i++] = argv[iCopy];
	}
	ppcPTBArgv[i] = (char *)0;
#if HAVE_SNPRINTF
	snprintf(acNumber, sizeof(acNumber), "%ld", (long)getpid());
#else
	sprintf(acNumber, "%ld", (long)getpid());
#endif
	setenv(acMySelf, acNumber, 1);
	fflush(stdout);
	execvp(ppcPTBArgv[0], ppcPTBArgv);
	execve(pcPtbw, ppcPTBArgv, environ);
	fprintf(stderr, "%s: execve: %s: %s\n", progname, ppcPTBArgv[0], strerror(errno));
	exit(EX_UNAVAILABLE);
	/*NOTREACHED*/
}

/* Setup the -e option data we'll need in Launch			(ksb)
 */
static char **ppcAddEnv, **ppcAddDice, **ppcAddValue;
static void
SetEnvDriver(char **ppcVec, unsigned int uParams)
{
	register int i;
	register size_t iMax;
	register unsigned int uModuli;
	register char *pcEq;

	ppcAddValue = ppcAddDice = ppcAddEnv = (char **)0;
	if ((char **)0 == ppcVec || (char *)0 == *ppcVec) {
		return;
	}

	for (iMax = 0; (char *)0 != ppcVec[iMax]; ++iMax) {
		/* count them */
	}
	iMax = (iMax|3)+1;
	ppcAddEnv = ppcVec;
	ppcAddDice = (char **)calloc(iMax, sizeof(char *));
	ppcAddValue = (char **)calloc(iMax, sizeof(char *));
	if ((char **)0 == ppcAddValue || (char **)0 == ppcAddDice) {
		fprintf(stderr, "%s: calloc: %ld,%lu: %s\n", progname, (long)iMax, (unsigned long)sizeof(char*), strerror(errno));
		exit(EX_OSERR);
	}
	uModuli = 0;
	for (i = 0; (char *)0 != ppcVec[i]; ++i) {
		auto char acTemp[64];
		if ((char *)0 == (pcEq = strchr(ppcVec[i], '='))) {
			++uModuli;
#if HAVE_SNPRINTF
			snprintf(acTemp, sizeof(acTemp), "%c%u", cEsc, uModuli);
#else
			sprintf(acTemp, "%c%u", cEsc, uModuli);
#endif
			if (uModuli >= uParams)
				uModuli = 0;
			ppcAddDice[i] = strdup(acTemp);
			continue;
		}
		*pcEq++ = '\000';
		ppcAddDice[i] = pcEq;
		ppcAddValue[i] = (char *)0;
	}
	ppcAddValue[i] = ppcAddDice[i] = (char *)0;
}

/* Put the values in the enviroment from the -e options			(ksb)
 */
static void
DoExportOpts()
{
	register char **ppc;
	register int i;

	if ((char **)0 == (ppc = ppcAddEnv)) {
		return;
	}
	for (i = 0; (char *)0 != ppc[i]; ++i) {
		setenv(ppc[i], ppcAddValue[i], 1);
	}
}

static pid_t *piOwn =	/* pid table we own, last is _our_pid_		*/
	(pid_t *)0;

/* Build a list of processes we own, of course the list			(ksb)
 * starts out with just us in it.  We build one extra slot on the end
 * to keep our pid (and in case we get 0 tasks calloc is not NULL).
 * Also setup the environment passdowns, and the %t list.
 */
static void
Tasks(unsigned iMax, unsigned int uParams)
{
	register int i;

	SetEnvDriver(util_ppm_size(pPPMAddEnv, 0), uParams);
	piOwn = (pid_t *)calloc(iMax+1, sizeof(pid_t));
	if ((pid_t *)0 == piOwn) {
		fprintf(stderr, "%s: calloc: pid table[%d]: %s\n", progname, iMax, strerror(errno));
		exit(EX_OSERR);
	}
	piOwn[0] = getpid();
	for (i = 0; i < iMax; ++i)
		piOwn[i+1] = piOwn[i];
}

/* remove the pid from the table, return 1 if it's in there		(ksb)
 * if fDone we also free the %t token allocated to it in the master
 */
static int
Stop(pid_t iPid, int fDone)
{
	register int i, iTok;

	for (i = 0; i < iParallel; ++i) {
		if (piOwn[i] != iPid)
			continue;
		for (iTok = 0; iTok < uTargetMax; ++iTok) {
			if (iPid != pTEList[iTok].wlock)
				continue;
			pTEList[iTok].wlock = 0;
			if (fDone && -1 != sMaster && (unsigned int *)0 != pTEList[iTok].publock) {
				(void)PTBFreeTokenList(sMaster, uReq, pTEList[iTok].publock);
				pTEList[iTok].publock = (unsigned int *)0;
			}
			break;
		}
		piOwn[i] = piOwn[iParallel];
		return 1;
	}
	return 0;
}

/* Get a token from the pool, wait for one if we must			(ksb)
 */
static int
GetToken(TARGET_ENV **ppTEFound)
{
	register int i, iRet;
	register pid_t w;
	auto int wExit;

	iRet = 0;
	*ppTEFound = (TARGET_ENV *)0;
	while (0 != uTargetMax) {
		for (i = uTargetMax; 0 < i; --i, ++uTargetCursor) {
			if (uTargetMax <= uTargetCursor)
				uTargetCursor = 0;
			if (0 != pTEList[uTargetCursor].wlock)
				continue;
			*ppTEFound = & pTEList[uTargetCursor];
			++uTargetCursor;
			return iRet;
		}

		if (0 < (w = wait3(& wExit, 0, (struct rusage *)0))) {
			if (Stop(w, 0))
				++iRet;
		}
	}
	return 0;
}

/* When we get a USR1 we open "|xclate -N %d -P" which just copies	(ksb)
 * our reports to hxmd, as needed.
 */
static void
XclateShorts(const char *pcWhich)
{
	static int fdXclDirect = -1;
	auto char acNark[MAX_XID_LEN+16];	/* xid\000 */

	/* Common reset logic
	 */
	if ((const char *)0 == pcWhich) {
		if (-1 != fdXclDirect)
			close(fdXclDirect);
		fdXclDirect = -1;
		return;
	}

	/* Id there no body to tell, then just return.
	 */
	if (!fManage) {
		return;
	}
	if (-1 == fdXclDirect) {
		static char *apcTattle[] = {
			"xclate", "-P", (char *)0
		};
		auto int fdHold;

		if (-1 == (fdHold = dup(1))) {
			fprintf(stderr, "%s: dup: %s\n", progname, strerror(errno));
			exit(EX_OSERR);
		}
		if (-1 == PipeFit(pcXclate, apcTattle, environ, 1)) {
			exit(EX_OSERR);
		}
		if (-1 == (fdXclDirect = dup(1))) {
			fprintf(stderr, "%s: dup %s: %s\n", progname, pcXclate, strerror(errno));
			exit(EX_SOFTWARE);
		}
		close(1);
		dup2(fdHold, 1);
		close(fdHold);
	}
#if HAVE_SNPRINTF
	snprintf(acNark, sizeof(acNark), "%s", pcWhich);
#else
	sprintf(acNark, "%s", pcWhich);
#endif
	(void)write(fdXclDirect, acNark, strlen(acNark)+1);
	if (fTrace) {
		fprintf(stderr, "%s: told xclate `%s\' didn\'t run\n", progname, pcWhich);
		fflush(stderr);
	}
}

/* If we were given the USR1 signal stop forking kids			(ksb)
 */
static int fShortCircuit = 0;
static void
Shutdown(int _dummy)	/*ARGSUSED*/
{
	if (!fShortCircuit++ && (fVerbose || fTrace)) {
		fprintf(stderr, "%s: caught USR1, stop processing\n", progname);
		fflush(stderr);
	}
}

/* add a process to our list						(ksb)
 */
static void
Start(pid_t iPid, TARGET_ENV *pTEThis)
{
	static int iWrap = 0;

	while (piOwn[iWrap] != piOwn[iParallel]) {
		if (++iWrap == iParallel)
			iWrap = 0;
	}
	piOwn[iWrap] = iPid;
	pTEThis->wlock = iPid;
}

/* Send gtfw a version string so it knows we chat a like protocol	(ksb)
 */
static int
GtfwConnHook(int s, void *pv)
{
	register char *pcEos;
	auto char acReply[256];
	static const char acGtfwProto[] = "0.1";

	if (sizeof(acGtfwProto) != write(s, acGtfwProto, sizeof(acGtfwProto))) {
		fprintf(stderr, "%s: write: %d: %s\n", progname, s, strerror(errno));
		exit(EX_OSERR);
	}
	acReply[0] = '\000';
	if (-1 == read(s, acReply, sizeof(acReply))) {
		fprintf(stderr, "%s: read: %d: %s\n", progname, s, strerror(errno));
		exit(EX_PROTOCOL);
	}
	if (acReply != (pcEos = strchr(acReply, '\n'))) {
		*pcEos = '\000';
		fprintf(stderr, "%s: gtfw: %s\n", progname, acReply);
		exit(EX_PROTOCOL);
	}
	return s;
}

static int sGtfw = -1;			/* fd to chat with gtfw */
static char acInternal[] = "iota";
static char *pcGTFSrc = acInternal;	/* internal %u counter source */

/* Chat up our enclosing gtfw instance for %u tokens			(ksb)
 */
static int
GtfwInit(char *pcLink)
{
	register char *pcDirect;
	auto char acSource[2*MAXPATHLEN+4];	/* host:/domain-socket */
	auto void *(*pfpvKeep)(void *);
	auto int (*pfiKeep)(int, void *);
	auto char acNotVoid[4];
	static const char acFetch[] = "U";

	if ((char *)0 == pcLink) {
		fprintf(stderr, "%s: no enclosing gtfw instance\n", progname);
		exit(EX_NOINPUT);
	}
#if HAVE_SNPRINTF
	snprintf(acSource, sizeof(acSource), "gtfw_%s", pcLink);
#else
	sprintf(acSource, "gtfw_%s", pcLink);
#endif
	if ((char *)0 == (pcDirect = getenv(acSource))) {
		fprintf(stderr, "%s: gtfw instance missing environment link \"$%s\"\n", progname, acSource);
		exit(EX_PROTOCOL);
	}
	/* gtfw won't love us unless we send a version string
	 */
	pfiKeep = divConnHook;
	divConnHook = GtfwConnHook;
	pfpvKeep = divNextLevel;
	divNextLevel = (void *(*)(void *))0;
	sGtfw = divConnect(pcDirect, RightsWrap, acNotVoid);
	divConnHook = pfiKeep;
	divNextLevel = pfpvKeep;

	/* Ask for the token source, which could be off-host, we could
	 * remove the FQDN of our host if that's the prefix, but we don't.
	 */
	if (sizeof(acFetch)-1 != write(sGtfw, acFetch, sizeof(acFetch)-1) || (1 > read(sGtfw, acSource, sizeof(acSource)))) {
		fprintf(stderr, "%s: lost gtfw diversion\n", progname);
		exit(EX_SOFTWARE);
	}
	if ((char *)0 == (pcGTFSrc = strdup(acSource))) {
		fprintf(stderr, "%s: strdup: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}
	return sGtfw;
}

/* Get a unique integer from an enclosing gtfw				(ksb)
 */
static unsigned
FromGtfw()
{
	register unsigned uRet;
	auto char *pcEos;
	auto char acUniq[256];	/* "%d\n" */
	static const char acFetch[] = "u";
	static int uSeq = 0;

	if (!fGtfw) {
		return uSeq++;
	}
	if (sizeof(acFetch)-1 != write(sGtfw, acFetch, sizeof(acFetch)-1) || (1 > read(sGtfw, acUniq, sizeof(acUniq)))) {
		fprintf(stderr, "%s: lost gtfw diversion\n", progname);
		exit(EX_OSERR);
	}
	uRet = strtoul(acUniq, &pcEos, 0);
	/*  ('\n' != *pcEos) */
	return uRet;
}

static unsigned uSeqUniq = 0;	/* used to number the commands (%u)	*/
static unsigned uSeqInit = 0;	/* used to find zero -N else clause	*/
static char **ppcArgs;

/* Launch the command the User built.					(ksb)
 * If we've done something bad to the system back off by Fibonacci numbers:
 * (viz. 1 2 3 5 8 13 21 34 55 ...) up to a max sleep time of MAX_BACKOFF.
 * If we have no fork() by then we are at MAXPROCS and the caller will
 * wait() for a slot then call us again.  {It might not be us that is at
 * MAXPROCS so we will find no done kid in the wait and spin back here,
 * repeating until the system figures out who to kill.}
 */
#if !defined(MAX_BACKOFF)
#define MAX_BACKOFF	15
#endif
static pid_t
Launch(char *pcBuilt, const char *pcXid, TARGET_ENV *pTECur)
{
	register int iPid;
	register unsigned int iD1, iD2, u, iTmp;
	register char *pcRun;
	auto int afdSync[2];
	auto char acNone[8];

	fflush(stdout);
	fflush(stderr);
	afdSync[0] = afdSync[1] = -1;
	if ((fVerbose || fTrace) && -1 == pipe(afdSync)) {
		fprintf(stderr, "%s: pipe: %s\n", progname, strerror(errno));
	}
	for (iD1 = iD2 = 1; iD1 < MAX_BACKOFF; iTmp = iD1, iD1 += iD2, iD2 = iTmp) {
		switch (iPid = fork()) {
		case -1:
			if (errno == EAGAIN || errno == ENOMEM) {
				sleep(iD1);
				continue;
			}
			fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
			exit(EX_OSERR);
			/*NOTREACHED*/
		case 0:
			DoExportOpts();
			ppcArgs[2] = pcBuilt;
			if (fAddEnv) {
				for (iTmp = 0; (char *)0 != ppcArgs[iTmp]; ++iTmp) {
					/* find the end */
				}
				for (u = 0; u < uReq; ++u) {
					ppcArgs[iTmp++] = pTECur->ppctokens[u];
				}
				ppcArgs[iTmp] = (char *)0;
			}
			/* wait for our parent to close a pipe to tell
			 * us he output the user notification.
			 */
			if (fVerbose || fTrace) {
				close(afdSync[1]);
				(void)read(afdSync[0], acNone, sizeof(acNone));
				close(afdSync[0]);
			}
			pcRun = ppcArgs[0];
			/* -m needs xclate to be our child, not a child of init
			 */
			if (fManage) {
				if ((char *)0 == pcXid || '\000' == *pcXid) {
#if HAVE_SNPRINTF
					snprintf(pcSlaveXid, MAX_XID_LEN, "%u", uSeqUniq);
#else
					sprintf(pcSlaveXid, "%u", uSeqUniq);
#endif
				} else {
					(void)strncpy(pcSlaveXid, pcXid, MAX_XID_LEN);
				}
				iPid = PipeFit(pcRun, ppcArgs, environ, 0);
				pcRun = pcXclate;
				ppcArgs = ppcManage;
			}
			execvp(pcRun, ppcArgs);
			execve(pcRun, ppcArgs, environ);
			fprintf(stderr, "%s: execve: %s: %s\n", progname, pcRun, strerror(errno));
			exit(EX_OSERR);
			/*NOTREACHED*/
		default:
			if (fVerbose)
				printf("%s\n", pcBuilt);
			if (fTrace)
				fprintf(stderr, "%s\n", pcBuilt);
			/* make sure the customer knows what we're doing before
			 * the child process begins to execute
			 */
			if (fVerbose || fTrace) {
				close(afdSync[0]);
				fflush(stdout);
				close(afdSync[1]);
			}
			return iPid;
		}
	}
	if (fVerbose || fTrace) {
		close(afdSync[0]);
		close(afdSync[1]);
	}
	return -1;
}

/* Compute %* from a list (any of %*, %f*, or %t*)			(ksb)
 * call with ((char **)0, 0) to free space
 */
static char *
CatAlloc(char **ppcList, unsigned int iMax)
{
	register unsigned int i, uNeed;
	register char *pcCat;
	static char *pcMine = (char *)0;
	static unsigned int uMyMax = 0;

	uNeed = 0;
	if ((char **)0 == ppcList) {
		if ((char *)0 != pcMine) {
			free((void *)pcMine);
			pcMine = (char *)0;
		}
		uMyMax = 0;
		return (char *)0;
	}
	if (0 == iMax) {
		static char *apc_[2] = { "", (char *)0 };

		ppcList = apc_;
		iMax = 1;
	}
	for (i = 0; i < iMax; ++i) {
		uNeed += strlen(ppcList[i])+1;
	}
	if (uMyMax < uNeed) {
		if ((char *)0 != pcMine) {
			free((void *)pcMine);
		}
		uNeed |= 31;
		uMyMax = ++uNeed;
		pcMine = (char *)malloc(uNeed);
	}
	if ((char *)0 != pcMine) {
		pcCat = pcMine;
		for (i = 0; i < iMax; ++i) {
			if (0 != i) {
				*pcCat++ = ' ';
			}
			(void)strcpy(pcCat, ppcList[i]);
			pcCat += strlen(ppcList[i]);
		}
	}
	return pcMine;
}

/* Put a backslah quote behind any character that might get expanded	(ksb)
 * by a shell (ksh/sh) inside double quotes (%q) viz \ " $ `, naked
 * glob/commad/-k/comment/redirect (%Q), or including $IFS (%W).
 */
static void
ReQuote(char *pcScan, unsigned *puCall, unsigned int uMax, int fStrength)
{
	static char *pcMem = (char *)0;
	static unsigned uMem;
	register char *pcCursor;
	register unsigned uLen;
	register int i;

	uLen = ((*puCall)|63)+1;
	if ((char *)0 == pcMem) {
		uMem = uLen;
		pcMem = (char *)malloc(uLen);
	} else if (uMem < uLen) {
		uMem = uLen;
		pcMem = (char *)realloc((void *)pcMem, uLen*sizeof(char));
	}
	if ((char *)0 == pcMem) {
		fprintf(stderr, "%s: malloc: %ld: out of memory\n", progname, (long)uLen);
		exit(EX_OSERR);
	}

	/* We're clear, copy the source to a safe buffer and quote it back.
	 */
	pcCursor = pcMem;
	(void)memcpy(pcCursor, pcScan, *puCall);
	pcCursor[*puCall] = '\000';
	for (uLen = 0; '\000' != (i = *pcCursor++); ) {
		switch (i) {
		case '\n': case '\t': case ' ': /* common IFS */
			if (fStrength > 2)
		case '~': case '*': case '?': case '[':	/* globs */
		case '|': case '&': case ';': case '(': case ')': /* cmd */
		case '#': /* comment */
		case '=': /* under -k is special */
		case '\'': /* hard quote */
		case '<': case '>': /* redirection */
				if (fStrength > 1)
		case '\\': case '\"': case '`': case '$':
					if (fStrength > 0)
						pcScan[uLen++] = '\\';
			/*FALLTHROUGH*/
		default:
			pcScan[uLen++] = i;
			break;
		}
	}
	pcScan[uLen] = '\000';
	*puCall = uLen;
}

/* Build the command from the input data.				(ksb)
 * We know there are enough slots in argv for us to read.
 * We need to construct the command in a buffer and return it,
 * the caller passes in a buffer that is at least NCARGS big.
 * If we never see a parameter expand, then cat them all on the end,
 * apply does this, I gotten used to it.
 */
static char *
AFmt(char *pcBuffer, char *pcTemplate, int iPass, char **argv, char **ppcNamev, TARGET_ENV *pTEThis, int *pfTail)
{
	register char *pcScan;
	register int i, cCache, iParam;
	register unsigned int uLimit, uMyReq;
	register int fMeta, fTags, fSubPart, fGroup, fMixer, fDQuote, fNeg;
	register char **ppcExp, *pcTerm, *pcTidy;
	auto char *pcError, **ppcTokens;
	static unsigned uMax = NCARGS;
	auto unsigned uCall, uHold;
	auto char acNumber[MAXPATHLEN+4];	/* %.2s%ld or "/bin/sh" */
	static int fBugNTWarn = 1;

	pcScan = pcBuffer;
	cCache = cEsc;
	uHold = 0;
	pcTidy = (char *)0;
	ppcTokens = pTEThis->ppctokens;
	uMyReq = uReq;
	while ('\000' != *pcTemplate) {
		if (cCache != (*pcScan++ = *pcTemplate++)) {
			++uHold;
			continue;
		}
		if (cCache == (i = *pcTemplate++)) {
			continue;
		}
		--pcScan;

		/* Let's rock this, first all find modifiers
		 * % [ftqQW]* [(] [\[] [{] source [}] dicer [\]] [,mixer [)]]
		 */
		pcError = pcTemplate;
		fMeta = fTags = fDQuote = 0;
		ppcExp = argv;
		uLimit = iPass;
		/* %f... source of data, rather than the data itself
		 * %t... tokens are source of data
		 * %ft   token source is source of data (locks data source)
		 * %tf   as above, for dyslexics like me
		 * %q|%Q|%W... various quote for the shell
		 */
		for (;;) {
			register int iQ;

			if (0 != (iQ = ('f' == i || 'F' == i))) {
				fMeta = iQ;
				if ('\000' != (i = *pcTemplate))
					++pcTemplate;
				continue;
			}
			if (0 != (iQ = ('t' == i || 'T' == i))) {
				fTags = iQ;
				if ('\000' != (i = *pcTemplate))
					++pcTemplate;
				continue;
			}
			if (0 != (iQ = ('q' == i) ? 1 : ('Q' == i) ? 2 : ('W' == i) ? 3 : 0)) {
				if (fDQuote < iQ)
					fDQuote = iQ;
				if ('\000' != (i = *pcTemplate))
					++pcTemplate;
				continue;
			}
			break;
		}

		/* The mixer/dicer markup must come in order (see dicer.c):
		 * param = %{15} to get to numbers abutted to numbers
		 * dicer = %[5/2] for subfields
		 *   mix = %(dicer|param,mixer) for the mixer character snipper
		 */
		fSubPart = fGroup = fMixer = 0;
		if (!fMeta || !fTags) { /* %ft is complete */
			if (0 != (fMixer = MIXER_RECURSE == i)) {
				if ('\000' != (i = *pcTemplate))
					++pcTemplate;
			}
			if (0 != (fSubPart = ('[' == i))) /*]*/ {
				if ('\000' != (i = *pcTemplate))
					++pcTemplate;
			}
			if (0 != (fGroup = ('{' == i))) /*}*/ {
				if ('\000' != (i = *pcTemplate))
					++pcTemplate;
			}
		}

		/* Find the raw data source(s)
		 * %t tag-file tokens	%ft (or tf) token source
		 * %p our pid for &&	%fp progname
		 * %u unique id		%fi iota
		 * %c cmd		%fc script
		 * %N param number N	%fN file source for N
		 * %% %			%f% %
		 * %+ shift, %1 called as a template on %2...%$, for hxmd
		 *    we used %1, so cancel the tail spell (after we call down)
		 * %f+ Humm use the name of the file as a template?
		 * %t+ use the first token as the new template (Easter Egg)
		 * we also allow %f(t) and %t(f) to be really nice.
		 *
		 * Either set pcTerm to the raw data, or set pcTerm=pcScan
		 * to denote that the data is in-place already.
		 */
		pcTerm = (char *)0;
		fNeg = 0;
		if (fMeta && fTags) {
			/* %tf or %ft doesn't look for any other param */
			fMeta = fTags = 0;
			pcTerm = (char *)0 != pcDyna ? pcDyna : acInternal;
			if ('\000' != i)
				--pcTemplate;
		} else if (cCache == i) { /* %{%} */
			fMeta = fTags = 0;
			pcScan[0] = cCache;
			pcScan[1] = '\000';
			pcTerm = pcScan;
		} else switch (i) {
			register int iAdj;
			auto unsigned uStash;
			auto char **ppcHold;
		case '~': /* ~number, ~$, ~* */
			fNeg = 1;
			if ('\000' != (i = *pcTemplate))
				++pcTemplate;
			break;

		case 'f': /* %f  a file names, token, progname, or gtfw */
		case 'F':
			if (fTags) {		/* %t{f} */
				fMeta = fTags = 0;
				pcTerm = (char *)0 != pcDyna ? pcDyna : acInternal;
				break;
			}
			if ('\000' != (i = *pcTemplate)) {
				++pcTemplate;
			}
			if ('t' == i || 'T' == i) {	/* %f{t} */
				fMeta = fTags = 0;
				pcTerm = (char *)0 != pcDyna ? pcDyna : acInternal;
				break;
			}
			if ('c' == i || 'C' ==i) {	/* %fc */
				fMeta = fTags = 0;
				pcTerm = (char *)0 != pcScript ? pcScript : acCommandLine;
				break;
			}
			if ('p' == i || 'P' ==i) {	/* %fp */
				fMeta = fTags = 0;
				pcTerm = progname;
				break;
			}
			if ('u' == i || 'U' ==i) {	/* %fu */
				fMeta = fTags = 0;
				pcTerm = (char *)0 != pcDyna ? pcDyna : acInternal;
				break;
			}
			/* %{f} */
			if ('\000' == i) {
				fprintf(stderr, "%s: %cf: requires a specification (number, t, p, $, *)\n", progname, cEsc);
				exit(EX_DATAERR);
			}
			fMeta = 1;
			break;
		case 't': /* %t  a lock tag, token, thread name, or target */
		case 'T':
			if (fMeta) {	/* %f(t) */
				fMeta = fTags = 0;
				pcTerm = (char *)0 != pcDyna ? pcDyna : acInternal;
				break;
			}
			if ('\000' != (i = *pcTemplate)) {
				++pcTemplate;
			}
			if ('f' == i || 'F' == i) {	/* %{tf} */
				fMeta = fTags = 0;
				pcTerm = (char *)0 != pcDyna ? pcDyna : acInternal;
				fTags = 0;
				break;
			}
			if ('c' == i || 'C' == i) {	/* %{tc} */
				fMeta = fTags = 0;
				snprintf(acNumber, sizeof(acNumber), "#!%s", pcShell);
				pcTerm = (char *)0 != pcLoader ? pcLoader : acNumber;
				break;
			}
			if ('u' == i || 'U' == i) {	/* %{tu} */
				fMeta = fTags = 0;
				snprintf(acNumber, sizeof(acNumber), "%u", uMyReq);
				pcTerm = acNumber;
				break;
			}
			if ('p' == i || 'P' == i) {	/* %{tp} */
				fMeta = fTags = 0;
				pcTerm = pcPtbw;
				break;
			}
			if ('\000' == i) {		/* %{t} */
				fprintf(stderr, "%s: %ct: requires a specification (number, f, p, $, *)\n", progname, cEsc);
				exit(EX_DATAERR);
			}
			fTags = 1;
			break;
		case 'c':/* %c cmd, %fc script, %tc loader line */
		case 'C':
			if (fMeta) {			/* %f{c} script */
				fMeta = fTags = 0;
				pcTerm = (char *)0 != pcScript ? pcScript : acCommandLine;
				break;
			}
			/* we know the number of lines, but that is useless
			 */
			if (fTags) {			/* %t{c} loader */
				fTags = 0;
				snprintf(acNumber, sizeof(acNumber), "#!%s", pcShell);
				pcTerm = (char *)0 != pcLoader ? pcLoader : acNumber;
				break;
			}
			/* %c cmd itself */
			pcTerm = pcCmd;
			break;
		case 'u': /* %u pid, %fu gtfw path, or %tu tokens left */
		case 'U':
			/* %tu is the number of tokens that remain
			 * in light of %t+ you'll need this. --ksb
			 */
			if (fMeta) {			/* %f{u} */
				fMeta = fTags = 0;
				pcTerm = pcGTFSrc;
				break;
			}
			if (fTags) {			/* %t{u} */
				fTags = 0;
				snprintf(acNumber, sizeof(acNumber), "%u", uMyReq);
				pcTerm = acNumber;
				break;
			}
			/* %u */
#if HAVE_SNPRINTF
			snprintf(acNumber, sizeof(acNumber), "%u", uSeqUniq);
#else
			sprintf(acNumber, "%u", uSeqUniq);
#endif
			pcTerm = acNumber;
			break;
		case 'p': /* %p pid, %fp progname, or %tp ptbw name */
		case 'P':
			if (fMeta) {		/* %f{p} */
				fMeta = 0;
				pcTerm = progname;
				break;
			}
			if (fTags) {		/* %t{p} */
				fTags = 0;
				pcTerm = pcPtbw;
				break;
			}
			/* %p */
#if HAVE_SNPRINTF
			snprintf(acNumber, sizeof(acNumber), "%ld", (long)getpid());
#else
			sprintf(acNumber, "%ld", (long)getpid());
#endif
			pcTerm = acNumber;
			break;
		case '+': /* %+ shift , %f+, %t+ are similar, kinda */
			iAdj = 1;
			ppcExp = argv;
			uLimit = iPass;
			/* These are in-elastic, and undocumented.  By using
			 * %t+ you remove the first token from the list, for
			 * the rest of the expansion of this command.  More
			 * useful than you'd think, also visible in the
			 * nested call.
			 */
			if (fMeta) {	/* %f+, use current file name */
				/* If you put precent-markup in the filename
				 * then you win the Easter Egg bush!
				 */
				ppcExp = ppcNamev;
			}
			if (fTags) {	/* %t+, use tag from list as template*/
				/* If you put markup in the token, good for
				 * you.  I expected that, now use the shift.
				 */
				iAdj = 0;
				ppcExp = ppcTokens++;
				uLimit = uMyReq--;
			}
			if (0 == uLimit || (char **)0 == ppcExp || (char *)0 == ppcExp[0]) {
				pcTerm = pcTidy = strdup("");
				if (fTrace)
					fprintf(stderr, "%s: %c%s+: nothing left to shift\n", progname, cEsc, fTags ? "t" : fMeta ? "f" : "");
				break;
			}
			uStash = uMax;
			uMax -= uHold;
			ppcHold = pTEThis->ppctokens;
			pTEThis->ppctokens = ppcTokens;
			if ((char *)0 == AFmt(pcScan, ppcExp[0], iPass-iAdj, argv+iAdj, ppcNamev+1, pTEThis, pfTail))
				exit(EX_SOFTWARE);
			pTEThis->ppctokens = ppcHold;
			pcTerm = pcScan;
			uMax = uStash;
			*pfTail = 0;
			break;
		default:
			/* handle in switch below */
			break;
		}

		/* Must be a source here still, check other sources
		 */
		if ((char *)0 == pcTerm) {
			if (fMeta) {
				if ((char **)0 == (ppcExp = ppcNamev)) {
					fprintf(stderr, "%s: %cf used without -f\n", progname, cEsc);
					exit(EX_DATAERR);
				}
				uLimit = iPass;
			}
			if (fTags) {
				if ((char **)0 == (ppcExp = ppcTokens)) {
					fprintf(stderr, "%s: %cf failed\n", progname, cEsc);
					exit(EX_SOFTWARE);
				}
				uLimit = uMyReq;
			}
			switch (i) {
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				if (argv == ppcExp) {
					*pfTail = 0;
				}
				iParam = i - '0';
				while (fGroup) {
					if ('\000' != (i = *pcTemplate))
						++pcTemplate;
					if (!isdigit(i))
						break;
					iParam *= 10;
					iParam += i - '0';
				}
				if (0 == iParam) {
					pcTerm = (char *)acEmpty;
				} else if (iParam < 1 || iParam > uLimit) {
					fprintf(stderr, "%s: %s%d: escape references an out of range parameter (%s limit %d)\n", progname, fNeg ? "~" : "", iParam, (argv == ppcExp ? "argument" : ppcTokens == ppcExp ? "token" : "file"), uLimit);
					exit(EX_USAGE);
				} else {
					/* fNeg reverses the direction
					 * viz. if limit 3 and "%~1" then 3+1-1 => 3
					 */
					if (fNeg) {
						iParam = (uLimit+1) - iParam;
					}
					pcTerm = ppcExp[iParam-1];
				}
				if (fGroup) {
					--pcTemplate;
				}
				break;
			case '$':	/* last param -- implied by %[1.$] */
				pcTerm = fNeg ? ppcExp[0] : ppcExp[iPass-1];
				if (argv == ppcExp) {
					*pfTail = 0;
				}
				break;
			case '*':	/* pack args into a single mix/dice */
				if (argv == ppcExp) {
					*pfTail = 0;
				}
				/* %~* reverses lists of more than 1 element
				 */
				if (fNeg && 1 < uLimit) {
					auto char **ppcTemp;
					auto size_t w;

					w = (uLimit|3)+1;
					if ((char **)0 == (ppcTemp = calloc(w, sizeof(char *)))) {
						fprintf(stderr, "%s: calloc: %ld,%ld: %s\n", progname, (long)w, (long)sizeof(char *), strerror(errno));
						exit(EX_OSERR);
					}
					for (w = 0; w < uLimit; ++w) {
						ppcTemp[uLimit-w-1] = ppcExp[w];
					}
					pcTerm = CatAlloc(ppcTemp, uLimit);
					free((void *)ppcTemp);
				} else {
					pcTerm = CatAlloc(ppcExp, uLimit);
				}
				break;
			default:
				/* Ha! %(%) */
				if (i == cEsc) {
					fMeta = fTags = 0;
					pcScan[0] = cEsc;
					pcScan[1] = '\000';
					pcTerm = pcScan;
					break;
				}
				if (fTags) {
					fprintf(stderr, "%s: %ct: requires a specification (number, f, $, *)\n", progname, cEsc);
					exit(EX_DATAERR);
				}
				if (fMeta) {
					fprintf(stderr, "%s: %cf: requires a specification (number, t, $, *)\n", progname, cEsc);
					exit(EX_DATAERR);
				}
				fprintf(stderr, "%s: %c%c: unknown specification, see -H\n", progname, cEsc, i);
				exit(EX_USAGE);
			}
		}
		if (!fTags || fExec || isatty(1)) {
			/* OK -- if they went to the trouble to put us
			 * on a tty they know "what they are doing".
			 */
		} else if (fBugNTWarn) {
			fprintf(stderr, "%s: %ct used with -n, possible locking issues\n", progname, cEsc);
			fBugNTWarn = 0;
		}

		/*				  { matching close below
		 * OK, close it up, do we need a '}', ']' or ')'?
		 */
		if (fGroup) {
			if ('\000' == *pcTemplate) {
				fprintf(stderr, "%s: %c{%.*s: missing close curly (at end of expression)\n", progname, cEsc, (int)(pcTemplate-pcError), pcError);
				exit(EX_USAGE);
			} else if ('}' != *pcTemplate) {
				fprintf(stderr, "%s: %c{%.*s: missing close curly (%c should be })\n", progname, cEsc, (int)(pcTemplate-pcError), pcError, i);
				exit(EX_USAGE);
			}
			++pcTemplate;
		}
		/* Finish up the Dicer expression on the term
		 * When pcTerm == pcScan the string is already in-place,
		 * which may mean we must strdup it out of the way, or
		 * not copy it to itself.
		 */
		if ((char *)0 == pcTerm) {
			fprintf(stderr, "%s: %c%.*s: no such value\n", progname, cEsc, (int)(pcTemplate-pcError), pcError);
			exit(EX_USAGE);
		}
		uCall = strlen(pcTerm);
		if (fSubPart) {
			if (']' != i) {
				if (pcScan == pcTerm && (char *)0 == (pcTerm = pcTidy = strdup(pcScan))) {
					fprintf(stderr, "%s: strdup: out of memory\n", progname);
					exit(EX_OSERR);
				}
				uCall = uMax-uHold;
				if ((char *)0 == (pcTemplate = Dicer(pcScan, &uCall, pcTemplate, pcTerm))) {
					fprintf(stderr, "%s: dicer: out of memory\n", progname);
					exit(EX_OSERR);
				}
			} else if (pcScan != pcTerm) {
				if (uHold+uCall > uMax) {
					fprintf(stderr, "%s: expansion too large (> %ld)\n", progname, (long)uMax);
					exit(EX_CANTCREAT);
				}
				(void)strcpy(pcScan, pcTerm);
			}
			if (/*[*/ ']' != pcTemplate[-1]) {
				fprintf(stderr, "%s: dicer %c[%.*s: missing \']\' in expression\n", progname, cEsc, (int)(pcTemplate-pcError), pcError);
				exit(EX_USAGE);
			}
		} else if (pcTerm != pcScan) {
			if (uHold+uCall > uMax) {
				fprintf(stderr, "%s: expansion too large (> %ld)\n", progname, (long)uMax);
				exit(EX_CANTCREAT);
			}
			(void)strcpy(pcScan, pcTerm);
		}
		if (fMixer) {
			register char *pcRes;

			uCall = uMax-uHold;
			if (')' == i) {
				pcRes = pcTemplate;
			} else if ((char *)0 == (pcRes = Mixer(pcScan, &uCall, pcTemplate, MIXER_END))) {
				fprintf(stderr, "%s: mixer %c%c%.*s: syntax error in expression\n", progname, cEsc, MIXER_RECURSE, (int)(pcTemplate-pcError), pcError);
				exit(EX_USAGE);
			}
			if (MIXER_END != pcRes[-1]) {
				fprintf(stderr, "%s: mixer %c%c%.*s...: missing \'%c\' in expression\n", progname, cEsc, MIXER_RECURSE, 1+(int)(pcTemplate-pcError), pcError, MIXER_END);
				exit(EX_USAGE);
			}
			pcTemplate = pcRes;
		}
		if (fDQuote) {
			ReQuote(pcScan, & uCall, uMax, fDQuote);
		}
		if ((char *)0 != pcTidy) {
			free((void *)pcTidy);
			pcTidy = (char *)0;
		}
		/* We might have already smacked the stack, but we try
		 * to catch it here --ksb
		 */
		if (uHold+uCall >= uMax) {
			fprintf(stderr, "%s: expansion too large (length > %lu)\n", progname, (long)uMax);
			exit(EX_USAGE);
		}
		pcScan += uCall;
		uHold += uCall;
		if (fSubPart) {
			fSubPart = 0;
		}
	}
	/* If none selected add the implicit "%1 %2 %3 ..."
	 */
	if (0 != *pfTail) {
		if (pcScan > pcBuffer && !isspace(pcScan[-1]) && 0 < iPass) {
			*pcScan++ = ' ';
		}
		for (i = 0; i < iPass; ++i) {
			if (0 != i) {
				*pcScan++ = ' ';
			}
			(void)strcpy(pcScan, argv[i]);
			pcScan += strlen(pcScan);
		}
		*pfTail = 0;
	}
	*pcScan = '\000';
	return pcBuffer;
}

/* format the command and any environment insertions,			(ksb)
 * N.B. side effect globals link us to Launch.
 */
static char *
DiceControl(char *pcBuffer, char *pcTemplate, int iPass, char **argv, char **ppcNamev, TARGET_ENV *pTEThis)
{
	register char **ppc, *pcRes;
	register int i;
	auto int fTail;
	auto char acTemp[NCARGS];

	fTail = 1;
	if ((char **)0 == (ppc = ppcAddEnv)) {
		return AFmt(pcBuffer, pcTemplate, iPass, argv, ppcNamev, pTEThis, &fTail);
	}
	for (i = 0; (char *)0 != ppc[i]; ++i) {
		if ((char *)0 == ppcAddValue[i])
			break;
		free((void *)ppcAddValue[i]);
		ppcAddValue[i] = (char *)0;
	}
	for (i = 0; (char *)0 != ppc[i]; ++i) {
		fTail = 0;
		pcRes = AFmt(acTemp, ppcAddDice[i], iPass, argv, ppcNamev, pTEThis, &fTail);
		if ((char *)0 == pcRes) {
			fprintf(stderr, "%s: $%s: failed to expand\n", progname, ppcAddEnv[i]);
			exit(EX_DATAERR);
		}
		ppcAddValue[i] = strdup(acTemp);
	}
	fTail = 1;
	return AFmt(pcBuffer, pcTemplate, iPass, argv, ppcNamev, pTEThis, &fTail);
}

/* Run any else command we need and dispose of any tokens held.		(ksb)
 * The "wait loop" seems silly, becasue we didn't start any processes,
 * but our same pid might have before we were execve'd.
 */
static void
FinishElse(char *pcSpace, int argc, char **argv)
{
	register int i, iExit;
	auto int wExit;
	static const char acElseId[] = "00";

	iExit = EX_OK;
	if (uSeqInit == uSeqUniq && (char *)0 != pcElse) {
		register char *pcBuilt;
		auto TARGET_ENV *pTEElse;

		(void)GetToken(& pTEElse);
		pcBuilt = DiceControl(pcSpace, pcElse, argc, argv, (char **)0, pTEElse);
		if ((char *)0 == pcBuilt) {
			fprintf(stderr, "%s: -N: failed to expand \"%s\"\n", progname, pcElse);
			iExit = EX_DATAERR;
		} else if (!fExec) {
			if (fVerbose)
				printf("%s\n", pcBuilt);
			if (fTrace)
				fprintf(stderr, "%s\n", pcBuilt);
		} else if (!fShortCircuit) {
			do {
				while (0 < (i = wait3(& wExit, WNOHANG, (struct rusage *)0))) {
					Stop(i, 1);
				}
			} while (-1 == (i = Launch(pcBuilt, fGtfw ? (char *)0 : acElseId, pTEElse)));
			Start(i, pTEElse);
		} else if (fManage) {
			XclateShorts(acElseId);
		}
	}

	/* Release free tokens if we have a ptbw to chat up
	 */
	for (i = 0; -1 != sMaster && i < uTargetMax; ++i) {
		if (0 != pTEList[i].wlock || (unsigned int *)0 == pTEList[i].publock)
			continue;
		(void)PTBFreeTokenList(sMaster, uReq, pTEList[i].publock);
		pTEList[i].publock = (unsigned int *)0;
	}

	/* Just wait for any out-standing tasks, even the xclate co-process
	 */
	if (fShortCircuit) {
		XclateShorts((const char *)0);
	}
	while (-1 != (i = wait((void *)0))) {
		Stop(i, 1);
	}
	exit(iExit);
}

/* The template on the command line is a filename, not the literal	(ksb)
 * command template.  Read the files, snip off the first #! line,
 * if it =~  m|^#![\s].*$progname|
 */
static char *
ReadCmd(char *pcName)
{
	register int fd;
	register char *pcRet, *pcScan;
	auto unsigned int uLines;

	if (-1 == (fd = open(pcName, O_RDONLY, 0666))) {
		fprintf(stderr, "%s: open: %s: %s\n", progname, pcName, strerror(errno));
		exit(EX_NOINPUT);
	}
	if ((char *)0 == (pcRet = cache_file(fd, &uLines))) {
		exit(EX_CONFIG);
	}
	close(fd);
	if (0 < uLines && '#' == pcRet[0] && '!' == pcRet[1] && (char *)0 != (pcScan = strstr(pcRet+2, progname))) {
		pcLoader = pcRet;
		pcRet += strlen(pcRet)+1;
		--uLines;
	}

	/* Cache file changes the '\n' to '\000', put them back
	 */
	for (pcScan = pcRet; uLines > 1; --uLines) {
		pcScan += strlen(pcScan);
		*pcScan = '\n';
	}
	return pcRet;
}

/* -P0 -> -P1 plus %0 in the template, to be more like apply		(ksb)
 */
static void
DoZero()
{
	register char *pcMem;
	auto size_t w;

	w = (strlen(pcCmd)|3)+5;	/* 5  rounding "%0\000" */
	if ((char *)0 == (pcMem = malloc(w))) {
		fprintf(stderr, "%s: malloc: %ld: %s\n", progname, (long)w, strerror(errno));
		exit(EX_OSERR);
	}
#if HAVE_SNPRINTF
	snprintf(pcMem, w, "%c0%s", cEsc, pcCmd);
#else
	sprintf(pcMem, "%c0%s", cEsc, pcCmd);
#endif
	pcCmd = pcMem;
}

/* do the work								(ksb)
 *	apply -P$iParallel -S$pcShell -c "$cmd" $argc
 */
static void
Apply(int argc, char **argv)
{
	register int i, iRunning, iAdvance;
	register char *pcBuilt;
	auto int wExit, iRedo;
	auto TARGET_ENV *pTECur;
	auto char acCmd[NCARGS+1];
	auto char acMyXid[MAX_XID_LEN];

	if (0 != (argc % iPass)) {
		fprintf(stderr, "%s: %d argument%s: not a divisible by hunk size (%d)\n", progname, argc, 1 == argc ? "" : "s", iPass);
		exit(EX_DATAERR);
	}
	iRedo = argc;

	/* not very useful here, but we allow it
	 */
	if ((char *)0 != pcRedir) {
		close(0);
		if (0 != open(pcRedir, O_RDONLY, 0666)) {
			fprintf(stderr, "%s: open: %s: %s\n", progname, pcRedir, strerror(errno));
			exit(EX_NOINPUT);
		}
	}

	iAdvance = 0 == iPass ? 1 : iPass;
	uSeqInit = uSeqUniq = FromGtfw();
	for (iRunning = 0; 0 != argc; uSeqUniq = FromGtfw()) {
		iRunning -= GetToken(& pTECur);
		pcBuilt = DiceControl(acCmd, pcCmd, iPass, argv, (char **)0, pTECur);
		if ((char *)0 == pcBuilt) {
			break;
		}
		(void)strncpy(acMyXid, argv[0], sizeof(acMyXid));
		acMyXid[sizeof(acMyXid)-1] = '\000';
		argc -= iAdvance, argv += iAdvance;
		if (!fExec) {
			if (fVerbose)
				printf("%s\n", pcBuilt);
			if (fTrace)
				fprintf(stderr, "%s\n", pcBuilt);
			continue;
		}
		if (fShortCircuit) {
			if (fUniq) {
#if HAVE_SNPRINTF
				snprintf(acMyXid, sizeof(acMyXid), "%u", uSeqUniq);
#else
				sprintf(acMyXid, "%u", uSeqUniq);
#endif
			}
			XclateShorts(acMyXid);
			continue;
		}
		do {
			while (0 < (i = wait3(& wExit, (iRunning < iParallel) ? WNOHANG : 0, (struct rusage *)0))) {
				if (Stop(i, 0))
					--iRunning;
			}
		} while (-1 == (i = Launch(pcBuilt, fUniq ? (char *)0 : acMyXid, pTECur)));
		Start(i, pTECur);
		++iRunning;
	}
	FinishElse(acCmd, iRedo, argv-iRedo);
}

/* the User gave us a list of files on the command line,		(ksb)
 * parameters come from the files indirectly.
 */
static void
VApply(int argc, FILE **pfpArgv, char **argv)
{
	register char **ppcSlots, *pcBuilt;
	register int i, c, iMem, iEofs;
	register int iRunning, iAdvance, fd;
	register FILE *fpMove;
	auto char acBufs[NCARGS+1];
	auto char acCmd[NCARGS+1];
	auto int wExit, iRedo;
	auto TARGET_ENV *pTECur;
	auto char acMyFXid[MAX_XID_LEN];

	if (0 != (argc % iPass)) {
		fprintf(stderr, "%s: %d file%s: not divisible by hunk size (%d)\n", progname, argc, 1 == argc ? "" : "s", iPass);
		exit(EX_DATAERR);
	}
	iRedo = argc;
	if ((char **)0 == (ppcSlots = (char **)calloc((size_t)iPass+1, sizeof(char *)))) {
		fprintf(stderr, "%s: calloc: %ld,%lu: %s\n", progname, (long)iPass+1, (unsigned long)sizeof(char *), strerror(errno));
		exit(EX_OSERR);
	}
	/* set close-on-exec to keep the kids from having lots of
	 * open files, if we can't set the close-on-exec we ignore it.
	 * Move a /dev/null fd on stdin if it is used as a stream of
	 * input files. (So the kids don't race for the input.)
	 * If two copies of stdin are put on the line we can read
	 * more than one token from that stream, the hack is clear. -- ksb
	 */
	fpMove = (FILE *)0;
	for (i = 0; i < argc; ++i) {
		fd = fileno(pfpArgv[i]);
		if (stdin == pfpArgv[i]) {
			if ((FILE *)0 == fpMove) {
				if (-1 == (fd = dup(0))) {
					fprintf(stderr, "%s: dup: 0: %s\n", progname, strerror(errno));
					exit(EX_OSERR);
				}
				if ((FILE *)0 == (fpMove = fdopen(fd, "r"))) {
					fprintf(stderr, "%s: fdopen: %d: %s\n", progname, fd, strerror(errno));
					exit(EX_OSERR);
				}
			}
			pfpArgv[i] = fpMove;
			if (0 == fd) {
				continue;
			}
			/* fall into set close on exec for the stdin dup
			 */
		} else if (1 == fd || 2 == fd) {
			/* through /dev/fd/X I guess!, don't close them
			 */
			continue;
		}
		(void)fcntl(fd, F_SETFD, 1);
	}
	/* We moved the stdin references, now close the old
	 * stdin and open the redirect one.  Or the user gave
	 * the redirect option rather than a less than symbol (why?)
	 */
	if ((FILE *)0 != fpMove || (char *)0 != pcRedir) {
		close(0);
		if ((char *)0 == pcRedir) {
			pcRedir = "/dev/null";
		}
		if (0 != open(pcRedir, O_RDONLY, 0666)) {
			fprintf(stderr, "%s: open: %s: %s\n", progname, pcRedir, strerror(errno));
			exit(EX_NOINPUT);
		}
	}

	iAdvance = 0 == iPass ? 1 : iPass;
	uSeqInit = uSeqUniq = FromGtfw();
	for (iRunning = 0; argc > 0; ) {
		iRunning -= GetToken(& pTECur);
		c = iEofs = iMem = 0;
		for (i = 0; i < iAdvance && iMem < sizeof(acBufs); ++i) {
			ppcSlots[i] = acBufs+iMem;
			while (iMem < sizeof(acBufs)) {
				c = getc(pfpArgv[i]);
				if (EOF == c) {
					if (ppcSlots[i] == acBufs+iMem) {
						ppcSlots[i] = pcPad;
						++iEofs;
					}
					acBufs[iMem++] = '\000';
					break;
				}
				if (cEoln == c) {
					acBufs[iMem++] = '\000';
					break;
				}
				acBufs[iMem++] = c;
			}
		}
		if (i < iAdvance || (EOF != c && cEoln != c)) {
			fprintf(stderr, "%s: execve: %s\n", progname, strerror(E2BIG));
			exit(EX_OSERR);
		}
		/* out of data on all inputs advance to next input group
		 */
		if (iAdvance == iEofs) {
			argc -= iAdvance, argv += iAdvance, pfpArgv += iAdvance;
			continue;
		}
		pcBuilt = DiceControl(acCmd, pcCmd, iPass, ppcSlots, argv, pTECur);
		if ((char *)0 == pcBuilt) {
			break;
		}
		strncpy(acMyFXid, ppcSlots[0], sizeof(acMyFXid));
		acMyFXid[sizeof(acMyFXid)-1] = '\000';
		if (!fExec) {
			if (fVerbose)
				printf("%s\n", pcBuilt);
			if (fTrace)
				fprintf(stderr, "%s\n", pcBuilt);
			uSeqUniq = FromGtfw();
			continue;
		}
		if (fShortCircuit) {
			if (fUniq) {
#if HAVE_SNPRINTF
				snprintf(acMyFXid, sizeof(acMyFXid), "%u", uSeqUniq);
#else
				sprintf(acMyFXid, "%u", uSeqUniq);
#endif
			}
			XclateShorts(acMyFXid);
		} else {
			do {
				while (0 < (i = wait3(& wExit, (iRunning < iParallel) ? WNOHANG : 0, (struct rusage *)0))) {
					if (Stop(i, 0))
						--iRunning;
				}
			} while (-1 == (i = Launch(pcBuilt, fUniq ? (char *)0 : acMyFXid, pTECur)));
			Start(i, pTECur);
			++iRunning;
		}
		uSeqUniq = FromGtfw();
	}
	FinishElse(acBufs, iRedo, argv-iRedo);
}

/* Do all the post work, we might get here three times			(ksb)
 * fix -P, -J, -R if we can, bomb on a bad -count.  Overlay any
 * wrapper processors we must have.  Build the task bookkeeping.
 */
static int
PostProc(int argc, char **argv)
{
	register char *pcTail;
	register unsigned int uJobs;
	register int fZero;
	auto struct sigaction saShut;

	fZero = 0;
	if ((char *)0 == pcShell || '\000' == *pcShell) {
		fprintf(stderr, "%s: no shell available\n", progname);
		exit(EX_UNAVAILABLE);
	}
	if ('\000' == cEsc) {
		fprintf(stderr, "%s: -a: the NUL character forbidden\n", progname);
		exit(EX_CONFIG);
	}
	/* Bloody csh counts are differently on different hosts: in the
	 * tuning we can use    csh -c 'echo $1' 0 1 2
	 * to see if $1 is the 0 or the 1, if we get 1 we need to pad
	 * our -A parameters with a "0" or "_".
	 */
	if ((char **)0 == (ppcArgs = calloc(5+uReq, sizeof(char *)))) {
		fprintf(stderr, "%s: calloc: %ld,%lu: %s\n", progname, (long)(5+uReq), (unsigned long)sizeof(char *), strerror(errno));
		exit(EX_OSERR);
	}
	ppcArgs[0] = pcShell;
	ppcArgs[1] = "-c";
	ppcArgs[2] = ":";
	ppcArgs[3] = (char *)0;
	ppcArgs[4] = (char *)0;
	if ((char *)0 == (pcTail = strrchr(pcShell, '/'))) {
		pcTail = pcShell;
	} else {
		++pcTail;
	}
	if ((char *)0 != strstr(pcTail, "perl")) {
		ppcArgs[1] = "-e";
		/* perl programmers expect $ARGV[0] to be the first arg */
	} else if ((char *)0 != strstr(pcTail, "tcsh")) {
		/* tcsh doesn't need the pad */
	} else if ((char *)0 != strstr(pcTail, "csh")) {
#if NEED_CSH_PAD
		ppcArgs[3] = "_";
#else
		/* csh is most likely a link to tsh */
#endif
	} else {
		/* the (1 true) shell needs the pad */
		ppcArgs[3] = "_";
	}

	/* -0 is now compatible with apply,
	 * --2 is take in pairs with no default append set		(ksb)
	 */
	if (0 == iPass) {
		iPass = 1;
		fZero = 1;
	} else if (0 > iPass) {
		iPass = -iPass;
		fZero = 1;
	}
	if (iParallel < 1) {
		iParallel = 1;
	}
#if defined(TUNE_MAX_PROC)
	if (TUME_MAX_PROC < iParallel) {
		fprintf(stderr, "%s: -P%u: too many parallel processes (TUNE_MAX_PROC)\n", progname, iParallel);
		exit(EX_USAGE);
	}
#endif
	if (0 == uReq || 0 == iCopies) {
		uJobs = iParallel;
	} else if (!fGaveJ || iCopies < 1) {
		uJobs = iParallel * uReq;
	} else {
		uJobs = iCopies * uReq;
	}
	if (fManage) {
		XCLOverlay(argc, argv);
	}
	PTBOverlay(argc, argv, uJobs);

	/* Setup the kill %p meme to short-circut our processing
	 */
	(void)memset((void *)&saShut, '\000', sizeof(saShut));
	saShut.sa_handler = (void *)Shutdown;
#if HAVE_SIGACTION
	saShut.sa_sigaction = (void *)Shutdown;
#endif
	saShut.sa_flags = SA_RESTART;
	if (-1 == sigaction(SIGUSR1, & saShut, (struct sigaction *)0)) {
		fprintf(stderr, "%s: sigaction: USR1: %s\n", progname, strerror(errno));
		exit(EX_OSERR);
	}
	Tasks(iParallel, (unsigned int)argc);
	return fZero;
}
