#!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 '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' 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?e1qv;' } char* named "pcPtbw" { before '%n = %K?e1qv;' } %c /* if we do not have NCARGS in try posix or guess (ksb) */ #if !defined(NCARGS) #include #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 G, then * back off by 1's to just */ 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; }