#!mkcmd # $Id: wrapw.m,v 1.41 2012/10/06 20:12:39 ksb Exp $ # The wrapper wrapper for forwarding a set of diversions to another context. # This should use libevent, I think. --ksb # We don't allow -N to ssh, becase we need to run a command # ssh doesn't use options 3567890, dhjruyz, BEGHIJPQUWZ so we do # We use -h, -V for the same things ssh does (help, version) # we use -d, -t (for tty) from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '"machine.h"' from '"fdrights.h"' from '"mkdtemp.h"' require "util_errno.m" "util_ppm.m" require "util_tmp.m" "util_divstack.m" "util_divconnect.m" require "std_help.m" "std_version.m" require "ptbccmd.m" basename "wrapw" "" getenv "WRAPW" %i extern char **environ; static char rcsid[] = "$Id: wrapw.m,v 1.41 2012/10/06 20:12:39 ksb Exp $", acMyDefName[] = "wrapw", acMySuffix[] = "/wrapwXXXXXX"; /* mkdtemp suffix in $TMPDIR */ static char *pcMyTemp = (char *)0, /* if we had to build one */ *pcMySpace = (char *)0, /* where we hide to work */ *pcMySocket = (char *)0, /* my UNIX domain socket */ **ppcOldEnv; /* the original environment */ static unsigned uOldSize; /* the size of the orig env */ #if HAVE_ACCEPT_FILTER && defined(SO_ACCEPTFILTER) static const char acAcceptFilter[] = "dataready"; #endif static const char acProto[] = "0.1\n"; /* includes protocal \n terminator */ %% key "divClient" 1 { "acMyDefName" 'rt' } augment action 'V' { user "Version();" } before { hidden named "Before" help "save the incoming environment" } boolean 'm' { # sadly this conflicts with ssh -m mac_spec once track named "fMaster" help "manage output for descendant processes" boolean 'd' { named "fPublish" init "1" help "do not publish this level in the linked environment" } char* 'N' { # conficts, but useless option for us named "pcBindHere" param "path" help "force a UNIX domain name for this service" } exclude 'IRVhW#' boolean 'E' { named "fWrapAll" help "wrap every instance of wrapw as well as the unwrapped diversions" } list { named "Master" param "utility" help "control process for task coordination" } } action 'R' { stop exclude "QIm" named "sync" from "" update "/* stop option processing */" help "remember the wrapper environment for out-of-band transmission" list { named "Scribe" param "record" help "the name of the out-of-band file, none for stdout" } } char* 't' { named "pcTags" param "wrap" help "select diversion by socket (or file) path" exclude "m" } boolean 'Q' { named "fQuit" init '0' help "tell the enclosing persistent instance to finish" exclude "m" } boolean 'I' { named "fAugment" init '0' help "push wrappers onto the current environment stacks" exclude "m" } boolean 'W' { named "fWhole" init '0' help "include the whole environment, if possible" exclude "m" } list { named "Client" param "client" help "client command" } %c #include "table.c" /* Save the incoming environment for the future, in a sendable block. (ksb) * We don't know who might change it, so copy the whole thing away. */ static void Before(int i_) { register unsigned uMem, uSpan; register char **ppc, **ppcSave, *pcMem; uMem = 0; for (ppc = environ, uSpan = 1; (char *)0 != *ppc; ++ppc, ++uSpan) { uMem += strlen(*ppc)+1; } uMem |= 15; uSpan |= 3; ++uMem, ++uSpan; if ((char *)0 == (pcMem = malloc(uMem))) { fprintf(stderr, "%s: malloc: %lu: %s\n", progname, (unsigned long)uMem, strerror(errno)); exit(EX_OSERR); } if ((char **)0 == (ppcSave = calloc(uSpan, sizeof(char *)))) { fprintf(stderr, "%s: calloc: %lu,%lu: %s\n", progname, (unsigned long)uSpan, (unsigned long)sizeof(char *), strerror(errno)); exit(EX_OSERR); } ppcOldEnv = ppcSave; for (ppc = environ, uSpan = 1; (char *)0 != *ppc; ++ppc, ++uSpan) { *ppcSave++ = strcpy(pcMem, *ppc); pcMem += strlen(pcMem)+1; } *ppcSave = (char *)0; *pcMem++ = '\000'; /* 1 is wrong, I think --ksb XXX */ uOldSize = (ppcOldEnv == ppcSave) ? 1 : (pcMem - ppcOldEnv[0]); } /* Build a temp directory for my space (ksb) */ static char * MyPlace() { register char *pcRet; extern char *mkdtemp(char *); if ((char *)0 == (pcRet = malloc(((strlen(pcTmpdir)+sizeof(acMySuffix))|7)+1))) { fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno)); exit(EX_OSERR); } (void)strcat(strcpy(pcRet, pcTmpdir), acMySuffix); return mkdtemp(pcRet); } /* When we are nested inside an sshw we can use the temp directory (ksb) * it built to hide our service socket. Otherwise we must build one * to hide us from the cold cruel world (eval dirname \$sshw_${sshw_link}). * We try to nest inside gtfw as well. */ static void SafePlace(const char *pcInside) { register char *pcEnv, *pcTail; register const char **ppc; static const char *apcNest[] = { "sshw_%s", "gtfw_%s", "sshw_%s", (char *)0 }; auto char acIndex[255+32+1]; /* 255 is MAXNAMLEN really */ pcMyTemp = (char *)0; if ((char *)0 != (pcEnv = divCurrent((char *)0))) { /* try to buddy with an enclosing instance */ } for (ppc = apcNest; (char *)0 == pcEnv && (char *)0 != *ppc; ++ppc) { snprintf(acIndex, sizeof(acIndex), *ppc, "link"); if ((char *)0 != (pcEnv = getenv(acIndex)) && strlen(pcEnv) <= 32 && (isdigit(*pcEnv) || 'd' == *pcEnv)) { snprintf(acIndex, sizeof(acIndex), *ppc, pcEnv); pcEnv = getenv(acIndex); } else { pcEnv = (char *)0; } } if ((char *)0 == pcEnv || (char *)0 == (pcTail = strrchr(pcEnv, '/')) || pcEnv == pcTail) { pcMySpace = pcMyTemp = MyPlace(); return; } *pcTail = '\000'; pcMySpace = strdup(pcEnv); *pcTail = '/'; } /* Make the server port for this server to listen on (ksb) * stolen from ptyd, xclate, and others I've coded. */ static int MakeServerPort(pcName) char *pcName; { register int sCtl, iRet, iListen; auto struct sockaddr_un run; #if HAVE_ACCEPT_FILTER && defined(SO_ACCEPTFILTER) auto struct accept_filter_arg FA; #endif if ((sCtl = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { fprintf(stderr, "%s: socket: %s\n", progname, strerror(errno)); exit(EX_OSERR); } #if HAVE_SUN_LEN run.sun_len = 0; /* hush valgrind */ #endif run.sun_family = AF_UNIX; (void) strcpy(run.sun_path, pcName); if (-1 == bind(sCtl, (struct sockaddr *)&run, EMU_SOCKUN_LEN(pcName))) { return -1; } /* On some UNIX-like systems we need a sockopt set first -- sigh */ iRet = -1; for (iListen = 20*64; iListen > 5 && -1 == (iRet = listen(sCtl, iListen)) ; iListen >>= 1) { /* try less */ } if (-1 == iRet) { fprintf(stderr, "%s: listen: %d: %s\n", progname, iListen, strerror(errno)); exit(EX_OSERR); } #if HAVE_ACCEPT_FILTER && defined(SO_ACCEPTFILTER) (void)memset(&FA, '\000', sizeof(FA)); (void)strcpy(FA.af_name, acAcceptFilter); setsockopt(sCtl, SOL_SOCKET, SO_ACCEPTFILTER, &FA, sizeof(FA)); #endif (void)fcntl(sCtl, F_SETFD, 1); return sCtl; } /* Pass pcDirect = /tmp-dir/socket-name to contact * a ptbw-like service. */ int MakeClientPort(const char *pcDirect) { register int s, iFailed, iSlow; auto struct sockaddr_un run; iFailed = 0; for (iSlow = 0; ; iSlow ^= 1) { if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { return -1; } memset((void *)& run, '\000', sizeof(run)); run.sun_family = AF_UNIX; if (strlen(pcDirect) > sizeof(run.sun_path)-1) { errno = ENAMETOOLONG; goto failed; } (void)strcpy(run.sun_path, pcDirect); if (-1 != connect(s, (struct sockaddr *)&run, strlen(pcDirect)+2)) { (void)fcntl(s, F_SETFD, 1); break; } if (ECONNREFUSED != errno) { goto failed; } close(s); s = -1; /* try again */ sleep(iSlow); } if (iFailed) { register int e; errno = EPFNOSUPPORT; failed: e = errno; (void)close(s); errno = e; return -1; } return s; } /* We don't want to leave trash around the filesystem (ksb) * Should be install with atexit before we call SafePlace or Bindings. */ static void CleanUp(void) { if ((char *)0 != pcMySocket) { (void)unlink(pcMySocket); pcMySocket = (char *)0; } if ((char *)0 != pcMyTemp) { (void)rmdir(pcMyTemp); pcMyTemp = (char *)0; } } /* Message passed as the (void *) target in calls to DiVersion (ksb) */ struct _WMnode { int iwidth; const char *pcnote; int fsawactive; const char *pcdirect; }; /* Format the info about a stacked (or diconnected) diversion (ksb) * (if we get to level 0 return true). */ static int DiVersion(const char *pcName, const char *pcTag, int fActive, void *pvMark) { auto struct stat stSock; register struct _WMnode *pWM = pvMark; register char *pcTail; if ((const char *)0 == pcName) { pcName = "--"; } if ((const char *)0 == pcTag) { printf("%s:%*s: missing from our environment", progname, pWM->iwidth, pcName); } else if (-1 != lstat(pcTag, & stSock)) { printf("%s:%*s: %s", progname, pWM->iwidth, pcName, pcTag); } else if (ENOTDIR != errno || (char *)0 == (pcTail = strrchr(pcTag, '/'))) { printf("%s:%*s: stat: %s: %s", progname, pWM->iwidth, pcName, pcTag, strerror(errno)); } else { *pcTail = '\000'; if (-1 == lstat(pcTag, & stSock) || S_IFSOCK != (S_IFMT & stSock.st_mode)) { printf("%s:%*s: lstat: %s: %s", progname, pWM->iwidth, pcName, pcTag, strerror(errno)); } else { printf("%s:%*s: %s/%s", progname, pWM->iwidth, pcName, pcTag, pcTail+1); } *pcTail = '/'; } if (fActive) { pWM->fsawactive = 1; printf("%s", pWM->pcnote); } if ((const char *)0 != pWM->pcdirect && 0 == strcmp(pcTag, pWM->pcdirect)) { pWM->pcdirect = (const char *)0; printf(" [-t]"); } printf("\n"); if (fActive && !isdigit(*pcName)) { return 1; } return '0' == pcName[0] && '\000' == pcName[1]; } /* We output more than you'd want to know, good for debugging (ksb) */ static void Version() { register const char *pcLevel; auto unsigned uOuter; auto struct _WMnode WMParam; printf("%s: ptbc code: revision %s", progname, PTBacVersion); printf("%s: table code: %s\n", progname, acTableId); printf("%s: safe directory template: %s\n", progname, acMySuffix+1); #if HAVE_ACCEPT_FILTER && defined(SO_ACCEPTFILTER) printf("%s: accept filter: %s\n", progname, acAcceptFilter); #endif (void)divNumber(& uOuter); divVersion(stdout); pcLevel = divCurrent((char *)0); WMParam.iwidth = (char *)0 == pcLevel ? 3 : strlen(pcLevel)+2; WMParam.pcnote = " [target]"; WMParam.fsawactive = 0; WMParam.pcdirect = pcTags; if (0 == divSearch(DiVersion, (void *)& WMParam) && iDevDepth >= uOuter) { if ((const char *)0 != WMParam.pcdirect) { DiVersion("-t", WMParam.pcdirect, !WMParam.fsawactive, (void *)&WMParam); } else if (0 == iDevDepth) { printf("%s: no current diversions\n", progname); } else { printf("%s: depth -%d is too great for current stack of %u\n", progname, iDevDepth, uOuter); } } else if ((const char *)0 != WMParam.pcdirect) { DiVersion("-t", WMParam.pcdirect, !WMParam.fsawactive, (void *)&WMParam); } else if (!WMParam.fsawactive) { printf("%s: never saw the active diversion\n", progname); } } static char **ppcTableau = (char **)0, **ppcEnvClient = (char **)0, **ppcClientRestore = (char **)0; static const char acIsLink[] = "_link="; /* Support code for wrapw, we need to manage some strings * for each wrapper entry in the list build a map * new-tail -> old socket name, if it pointed to a real socket, * else if wrap-the-wrapped go ahead and wrap it, * else just leave it in alone */ static void SetupTableau(char **ppcList, const char *pcOurSocket) { register int i, iCount; register char *pcCursor, *pcScan; register char **ppcT, *pcEq, *pcMem, **ppcE, **ppcC; register unsigned uUniq; auto char acSuffix[64]; /* big enough to hold an unsigned integer */ if ((char **)0 == ppcList || (char *)0 == *ppcList) { static char *pcEmpty = (char *)0; ppcClientRestore = ppcTableau = ppcEnvClient = & pcEmpty; return; } for (iCount = 0; (char *)0 != ppcList[iCount++]; ) { /* count them */ } i = (iCount|7)+1; if ((char **)0 == (ppcT = (char **)calloc(i, sizeof(char *))) || ((char **)0 == (ppcE = (char **)calloc(i, sizeof(char *)))) || ((char **)0 == (ppcC = (char **)calloc(i, sizeof(char *))))) { fprintf(stderr, "%s: calloc: %ldx%ld: %s\n", progname, (long)i, (long)sizeof(char *), strerror(errno)); exit(EX_OSERR); } ppcTableau = ppcT; ppcEnvClient = ppcE; ppcClientRestore = ppcC; /* Remap the wrapper_{digits|'d'}=/existant/path and the like, * skip the other things in the environement (and ourself) * in the tableau suffix:/the/old/path * in the environ wrapper_spec=/our_path/socket/suffix */ for (uUniq = 0, i = 0; (char *)0 != (pcScan = *ppcList); ++ppcList) { /* pass envs without a value as-is */ if ((char *)0 == (pcEq = strchr(pcScan, '='))) { *ppcE++ = pcScan; continue; } /* pass link records as-is, like "xclate_link=2" */ if ((char *)0 != (pcCursor = strstr(pcScan, acIsLink)) && pcCursor < pcEq) { *ppcE++ = pcScan; *ppcC++ = pcScan; continue; } pcCursor = pcScan; while (isalnum(*pcCursor)) { ++pcCursor; } if (pcScan == pcCursor || '_' != *pcCursor) { *ppcE++ = pcScan; continue; } /* Check for self-encapsulation, honor -E */ *pcCursor = '\000'; i = 0 == strcmp(pcScan, acMyDefName); *pcCursor++ = '_'; if ((!fWrapAll && i) || 0 == strcmp(pcEq+1, pcOurSocket)) { *ppcE++ = pcScan; *ppcC++ = pcScan; continue; } /* Look for the possible wrapper types _ | _d */ if ('d' == *pcCursor) { ++pcCursor; } else while (isdigit(*pcCursor)) { ++pcCursor; } if (pcEq != pcCursor) { *ppcE++ = pcScan; continue; } *pcEq++ = '\000'; /* build tableau entry and new env value */ snprintf(acSuffix, sizeof(acSuffix), "%u", uUniq++); i = strlen(acSuffix)+1+strlen(pcEq)+1; pcMem = malloc((i|7)+1); snprintf(pcMem, i, "%s:%s", acSuffix, pcEq); *ppcT++ = pcMem; i = 7|(strlen(pcScan)+1+strlen(pcOurSocket)+1+strlen(acSuffix)+1); pcMem = malloc(i+1); snprintf(pcMem, i, "%s=%s/%s", pcScan, pcOurSocket, acSuffix); *ppcE++ = pcMem; *ppcC++ = pcMem; *--pcEq = '='; } *ppcT = *ppcE = *ppcC = (char *)0; } static volatile pid_t wInferior = 0; static volatile int wInfExits = EX_SOFTWARE; static volatile int fInfOpts = WNOHANG|WUNTRACED; /* When a signal for a child arrives we burry the dead. (ksb) * If he is "our boy", then forget he's alive. */ static void Death(int _dummy) /*ARGSUSED*/ { register pid_t wReap; auto int wStatus; while (0 < (wReap = wait3(& wStatus, fInfOpts, (struct rusage *)0))) { if (WIFSTOPPED(wStatus)) { (void)kill(wReap, SIGCONT); continue; } if (wInferior == wReap) { wInferior = 0; wInfExits = wStatus; } } } /* Signal handler to remove our socket when we are signaled; (ksb) * we use atexit(3) to do that, by the way. */ static void DeadMan(int _dummy) /*ARGSUSED*/ { exit(EX_OK); } /* Build the service socket, put the mame in the environment (ksb) */ static int Bindings(unsigned uMake) { static const char acEnvRead[] = "WRAPW"; register char *pcTail, *pcMem; register unsigned int i; register int sRet; auto struct sigaction saWant; auto struct stat stLook; auto char acLevel[(sizeof(acEnvRead)|15)+(32+1)]; (void)memset((void *)& saWant, '\000', sizeof(saWant)); saWant.sa_handler = (void *)Death; #if HAVE_SIGACTION saWant.sa_sigaction = (void *)Death; #endif saWant.sa_flags = SA_RESTART; if (-1 == sigaction(SIGCHLD, & saWant, (struct sigaction *)0)) { fprintf(stderr, "%s: sigaction: CHLD: %s\n", progname, strerror(errno)); exit(EX_OSERR); } /* Accepting a -HUP signal to re-load the tableau data adds * uncertianty for no gain here, just install -TERM -- ksb */ (void)memset((void *)& saWant, '\000', sizeof(saWant)); saWant.sa_handler = (void *)DeadMan; #if HAVE_SIGACTION saWant.sa_sigaction = (void *)DeadMan; #endif saWant.sa_flags = SA_RESTART; if (-1 == sigaction(SIGINT, & saWant, (struct sigaction *)0)) { fprintf(stderr, "%s: sigaction: INT: %s\n", progname, strerror(errno)); exit(EX_OSERR); } (void)sigaction(SIGTERM, & saWant, (struct sigaction *)0); if ((char *)0 != pcBindHere) { if ('/' == *pcBindHere) { pcMySocket = pcBindHere; } else { register int iGuess; iGuess = (15|(strlen(pcMySpace)+strlen(pcBindHere)))+9; pcMem = malloc(iGuess); snprintf(pcMem, iGuess, "%s/%s", pcMySpace, pcBindHere); pcMySocket = pcMem; } if (-1 == (sRet = MakeServerPort(pcMySocket))) { fprintf(stderr, "%s: bind: %s: %s\n", progname, pcMySocket, strerror(errno)); exit(EX_OSERR); } } else { pcMem = malloc((strlen(pcMySpace)|63)+65); pcTail = strchr(strcpy(pcMem, pcMySpace), '\000'); if (pcTail > pcMem && '/' != pcTail[-1]) { *pcTail++ = '/'; } for (i = 0; 1; ++i) { snprintf(pcTail, 64, "ww%u", i); if (-1 != lstat(pcMem, & stLook)) continue; if (-1 != (sRet = MakeServerPort(pcMem))) break; /* We failed to get a bind in pcMySpace, if we * didn't make a space we still can, once. -- ksb * (Someone chmod'd our super-space ugo-w, sigh.) */ if ((char *)0 != pcMyTemp) { fprintf(stderr, "%s: bind: %s: %s\n", progname, pcMem, strerror(errno)); exit(EX_OSERR); } pcMySpace = pcMyTemp = MyPlace(); return Bindings(uMake); } pcMySocket = pcMem; } /* Notify the inferior processes of our IPC location */ if (fPublish) { divPush(pcMySocket); } else { divDetach(pcMySocket, (const char *)0); } /* Diddle the options variable $US_$level -> $US, I'm a wrapper */ (void)snprintf(acLevel, sizeof(acLevel), "%s_%u", acEnvRead, uMake); if ((char *)0 != (pcTail = getenv(acLevel))) { (void)setenv(acEnvRead, pcTail, 1); } else { (void)unsetenv(acEnvRead); } return sRet; } /* Read a known size block of data from a peer. (ksb) * The block is followed by a lone '\n' sync marker, which becomes the * sentinal '\000' in a list of strings (ps type). */ static int ReadBlock(int sFrom, char *pcMem, int iMemSize) { register int cc; ++iMemSize; while (iMemSize > 0) { if (0 == (cc = read(sFrom, pcMem, iMemSize))) break; if (-1 == cc) return -1; iMemSize -= cc; pcMem += cc; } if ('\n' != pcMem[-1]) { errno = EMSGSIZE; return -1; } pcMem[-1] = '\000'; /* return as a point to strings (ps) */ return 0; } /* True when the environment variable looks like part of the wrapper (ksb) * stack. In other words is not $_ and matches * m/^[A-Za-z0-9]+_(d|[0-9]*|link)=/ */ static int IsWrapEnv(register char *pcPick) { while (isalnum(*pcPick)) ++pcPick; if ('_' != *pcPick++) return 0; if ('d' == pcPick[0] && '=' == pcPick[1]) return 1; if (0 == strncmp(pcPick-1, acIsLink, sizeof(acIsLink)-1)) return 1; while (isdigit(*pcPick)) ++pcPick; return '=' == *pcPick; } /* If the environment record given is in the links list then (ksb) * return a pointer to the recorded depth. List given in cats format: * "ksb\000""2", "ptbw\000""7", (char *)0 */ static const char * IsInLinks(register char *pcThis, register char **ppsList) { register size_t w; register const char *pcLook, *pcDigits; while ((char *)0 != (pcLook = *ppsList++)) { w = strlen(pcLook); if (0 != strncmp(pcThis, pcLook, w)) continue; pcLook += strlen(pcLook)+1; if (0 == strncmp(pcThis+w, acIsLink, sizeof(acIsLink)-1)) return pcLook; pcDigits = pcThis+w; if ('_' == *pcDigits) ++pcDigits; while (isdigit(*pcDigits)) ++pcDigits; if ('=' == *pcDigits) return pcLook; } return (const char *)0; } /* Add the old link count to the current one if any. (ksb) * This adds the old enviornment to the new environment, with the * new one closer the the client process (under -I). */ static char * Remap(register char *pcSet, register char **ppsLinks) { register const char *pcAdd; register int iSum, iMem; register char *pcRet, *pcEq, *pcUnder; if ((char *)0 == (pcEq = strchr(pcSet, '=')) || (char *)0 == (pcAdd = IsInLinks(pcSet, ppsLinks))) return pcSet; /* An addition of 2 intergers can not be longer than the sum * of the digits. (99+1 -> 100) */ iMem = (7|(strlen(pcAdd) + strlen(pcSet))) + 1; if ((char *)0 == (pcRet = calloc(iMem, sizeof(char)))) { fprintf(stderr, "%s: calloc: %s\n", progname, strerror(errno)); exit(EX_OSERR); } /* There cases ksb_link=cur + Add -> ksb_link= * or ksb_depth=/path -> ksb_=/path * or ksb_link=d -> ksb_link=d */ iSum = atoi(pcAdd); if ((char *)0 != (pcUnder = strstr(pcSet, acIsLink)) && pcUnder < strchr(pcSet, '=')) { *pcEq = '\000'; if (!isdigit(pcEq[1])) { /* Could sanity check for pcSet"_"pcEq+1 in the env, * but would computing the new TOS really help anyone? */ *pcEq = '='; return pcSet; } iSum += atoi(pcEq+1); snprintf(pcRet, iMem, "%s=%d", pcSet, iSum); } else { pcUnder = pcEq; while ('_' != *--pcUnder) ; iSum += atoi(pcUnder+1); pcUnder[1] = '\000'; snprintf(pcRet, iMem, "%s%d%s", pcSet, iSum, pcEq); pcEq = pcUnder+1; } *pcEq = '%'; /* debug, mark string as used */ return pcRet; } /* Sort the environment in alpha order (ksb) */ static int OrderEnv(const void *pvLeft, const void *pvRight) { return strcmp(0[(char **)pvLeft], 0[(char **)pvRight]); } /* Assuming both lists are in alpha order merge them. (ksb) * Note that we also replace the socket path an removed the junk. * Also note that the new list must have space allocated for all possible * entries (new + old + sentinal NULL), which ModEnv did for us (below). */ static char ** Fold(char **ppcNew, char **ppcOld) { register char *pcEqNew, *pcEqOld, *pcPick; register int iCmp; /* No place to write new list, ouch. */ if ((char **)0 == ppcNew) { return ppcOld; } /* End of new list, copy old on the end, if any */ if ((char *)0 == *ppcNew) { register char **ppcCopy; for (ppcCopy = ppcNew; (char *)0 != *ppcOld; ++ppcOld) { if (!fAugment && IsWrapEnv(*ppcOld)) continue; *ppcCopy++ = *ppcOld; } *ppcCopy = (char *)0; return ppcNew; } /* End of old list, return new list. */ if ((char **)0 == ppcOld || (char *)0 == *ppcOld) { return ppcNew; } /* If the name is wrapper name rm it from the old list */ pcPick = *ppcOld; if (!fAugment && IsWrapEnv(*ppcOld)) { return Fold(ppcNew, ppcOld+1); } if ((char *)0 != (pcEqNew = strchr(*ppcNew, '='))) { *pcEqNew = '\000'; } if ((char *)0 != (pcEqOld = strchr(*ppcOld, '='))) { *pcEqOld = '\000'; } iCmp = strcmp(*ppcNew, *ppcOld); if ((char *)0 != pcEqNew) { *pcEqNew = '='; } if ((char *)0 != pcEqOld) { *pcEqOld = '='; } if (0 == iCmp) { /* same variable name, keep new one */ pcPick = *ppcNew; Fold(ppcNew+1, ppcOld+1); } else if (0 > iCmp) { /* new is sorted before old */ pcPick = *ppcNew; Fold(ppcNew+1, ppcOld); } else { /* new is after old, slide new down */ register int i; register char *pc, *pcNext; i = 0; pcPick = *ppcOld; pcNext = (char *)0; do { pc = ppcNew[i]; ppcNew[i++] = pcNext; pcNext = pc; } while ((char *)0 != pcNext); ppcNew[i] = pcNext; (void)Fold(ppcNew+1, ppcOld+1); } *ppcNew = pcPick; return ppcNew; } /* Add or replace the wrapper list in the current environment (ksb) * N.B. The caller may _not_ free the results here. And substrings of * psList may be included in the new environment, or not. */ static char ** ModEnv(int iCnt, char *psList, char **ppcEnv, char *pcReplace, char *pcWith) { register int i, iList, iCntl, iMax, iRepLen = 0; register char **ppcRet, **ppcCntls, *pcMod; ppcCntls = (char **)0; iCntl = 0; for (iList = 0; (char **)0 != ppcEnv && (char *)0 != ppcEnv[iList]; ++iList) { /* count'em */ if (0 != strstr(ppcEnv[iList], acIsLink)) ++iCntl; } qsort((void *)ppcEnv, iList, sizeof(char *), OrderEnv); /* Worst case size of new environment is old + wrap-env + NULL * If we augment a stack we keep the previous link value to * offset to the additional elements depth (except _d, which we * just overwrite). * Record all old _link values, as they were under fAugment. */ iMax = iList + iCnt + 1; if ((char **)0 == (ppcRet = calloc((iMax|1)+1, sizeof(char *)))) { fprintf(stderr, "%s: calloc: %s\n", progname, strerror(errno)); exit(EX_OSERR); } if (!fAugment) { /* nada */ } else if ((char **)0 == (ppcCntls = calloc(++iCntl, sizeof(char *)))) { fprintf(stderr, "%s: calloc: %s\n", progname, strerror(errno)); exit(EX_OSERR); } else { register char **ppc, *pcEq, *pcSnip, *pcMem; ppc = ppcCntls; iCntl = 0; /* Never add "Bogus=my_link=fake" but allow "my_link=d", * because that is harmless and useful. --ksb */ for (iList = 0; (char **)0 != ppcEnv && (char *)0 != ppcEnv[iList]; ++iList) { if (0 == (pcSnip = strstr(ppcEnv[iList], acIsLink))) continue; pcEq = strchr(ppcEnv[iList], '='); if (pcEq < pcSnip /* || !(isdigit(pcEq[1]) || 'd' == pcEq[1])*/) continue; iMax = strlen(ppcEnv[iList]); if ((char *)0 == (pcMem = calloc(iMax, sizeof(char)))) { fprintf(stderr, "%s: calloc: %s\n", progname, strerror(errno)); exit(EX_OSERR); } snprintf(pcMem, iMax, "%.*s%c%s", (int)(pcSnip-ppcEnv[iList]), ppcEnv[iList], '\000', pcEq+1); *ppc++ = pcMem; } *ppc = (char *)0; } if ((char *)0 != pcReplace) { iRepLen = strlen(pcReplace); } for (i = 0; '\000' != *psList; (ppcRet[i++] = pcMod), psList += strlen(psList)+1) { register char *pcEq, *pcNew; pcMod = psList; if ((char *)0 == pcReplace || !IsWrapEnv(pcMod)) { continue; } pcEq = strchr(pcMod, '='); /* IsWrapEnv saw one */ if (0 != strncmp(pcEq+1, pcReplace, iRepLen)) { continue; } *pcEq++ = '\000'; pcEq += iRepLen; /* "env_name" + '=' + "new-text" + "suffix" + '\000' */ iMax = strlen(pcMod)+1+strlen(pcWith)+strlen(pcEq)+1; if ((char *)0 == (pcNew = malloc((iMax|7)+1))) { fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno)); exit(EX_OSERR); } snprintf(pcNew, iMax, "%s=%s%s", pcMod, pcWith, pcEq); pcMod = pcNew; psList = pcEq; /* skip debris we left */ } ppcRet[i] = (char *)0; if ((char **)0 != ppcCntls && (char *)0 != ppcCntls[0]) { for (iList = 0; iList < i; ++iList) { ppcRet[iList] = Remap(ppcRet[iList], ppcCntls); } } qsort((void *)ppcRet, i, sizeof(char *), OrderEnv); return Fold(ppcRet, ppcEnv); /* We didn't free ppcCntls, which is not a big deal and surely not * worth an alloca(3) call. We might loose ~20 bytes of memory. */ } /* return the concat of the two ps's. (ksb) */ static char * Cats(char *psOrig, char *psAdd) { register size_t w, wLeft, wRight; register char *ps, *pcMem; if ((char *)0 == psOrig) return psAdd; if ((char *)0 == psAdd) return psOrig; /* Count chars in each ps, not the sentinal '\000' in Orig */ for (wLeft = 0, ps = psOrig; '\000' != *ps; ps += w, wLeft += w) w = strlen(ps)+1; for (wRight = 1, ps = psAdd; '\000' != *ps; ps += w, wRight += w) w = strlen(ps)+1; w = ((wLeft+wRight)|7)+1; if ((char *)0 == (pcMem = malloc(w))) { fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno)); exit(EX_OSERR); } (void)memcpy(pcMem+wLeft, psAdd, wRight); return memcpy(pcMem, psOrig, wLeft); } /* If you have a wrapw connection on stdin we'll take it: * said service must provide 'R', 'U', and '.' commands. * 'E' would be nitfy for -W request too. If we can't * replace stdin with tty or null we'll use stderr, * traditionally stderr is read/write for emergencies. */ static int /* fd */ FindMyself(char *pcExplicit, const char **ppcOut) { register int sRet; register const char *pcOuter; if ((char *)0 != pcTags && '-' == pcTags[0] && '\000' == pcTags[1]) { pcOuter = "stdin"; if (-1 == (sRet = dup(0))) { fprintf(stderr, "%s: dup: %s\n", progname, strerror(errno)); exit(EX_OSERR); } close(0); if (-1 == open("/dev/tty", O_RDONLY, 0) && -1 == open("/dev/null", O_RDONLY, 0)) dup(2); } else { pcOuter = divSelect(); if (-1 == (sRet = divConnect(pcOuter, RightsWrap, (void *)0)) && -1 == (sRet = MakeClientPort(pcOuter))) { fprintf(stderr, "%s: %s: cannot establish client connection\n", progname, pcOuter); exit(EX_DATAERR); } } if ((const char **)0 != ppcOut) { *ppcOut = pcOuter; } return sRet; } /* We are the client side, like xclate we include a client wrapper. (ksb) * Unlike other wrappers, the client doesn't really need an active * manager, a file works as weill. How odd. */ static void Client(int argc, char **argv) { register int sHandle, iMemSize; register char *pcArgv0, *pcN, *psEnv; register pid_t wReap; register unsigned uN; auto char *pcRep; auto int wStatus; auto struct sigaction saWant; auto struct stat stCheck; static char *apcDefCmd[] = { "/bin/sh", (char *)0 }; if ((char **)0 == argv || (char *)0 == argv[0]) { argv = apcDefCmd; if ((char *)0 != (pcN = getenv("SHELL"))) apcDefCmd[0] = pcN; } (void)memset((void *)& saWant, '\000', sizeof(saWant)); saWant.sa_handler = (void *)Death; #if HAVE_SIGACTION saWant.sa_sigaction = (void *)Death; #endif saWant.sa_flags = SA_RESTART; if (-1 == sigaction(SIGCHLD, & saWant, (struct sigaction *)0)) { fprintf(stderr, "%s: sigaction: %s\n", progname, strerror(errno)); exit(EX_OSERR); } /* Allow a non-sockets to contain the proposed environment adds * inv. both sides set psEnv and update pcTags */ stCheck.st_mode = S_IFLNK; /* if we don't call stat */ if ((char *)0 != pcTags && !('-' == pcTags[0] && '\000' == pcTags[1]) && -1 != stat(pcTags, &stCheck) && !S_ISSOCK(stCheck.st_mode) && -1 != (sHandle = open(pcTags, O_RDONLY, 0))) { register char *psNew, *psRead; if (S_ISDIR(stCheck.st_mode)) { fprintf(stderr, "%s: %s: %s\n", progname, pcTags, strerror(EISDIR)); exit(EX_SOFTWARE); } /* When the file is empty we force no wrappers in, and * by default we remove the existing, that's usefull too. */ psEnv = strdup(""); pcRep = (char *)0; while ((char *)0 != ReadTable(sHandle, (char *)0)) { if ((char *)0 == (psRead = ReadTable(sHandle, (char *)0))) { fprintf(stderr, "%s: %s: Premature EOF\n", progname, pcTags); exit(EX_SOFTWARE); } if ((char *)0 == (psNew = Cats(psEnv, psRead))) { fprintf(stderr, "%s: out of memory\n", progname); exit(EX_OSERR); } free((void *)psRead); psRead = (char *)0; free((void *)psEnv); psEnv = psNew; } pcTags = (char *)0; close(sHandle); fQuit = 0; /* you can't quit a file */ } else { auto const char *pcOuter; if ((char *)0 == pcTags) { pcTags = divSelect(); } sHandle = FindMyself(pcTags, &pcOuter); /* Expect "msize\n" "varN=valueN\000"* "\000" file on any * "-Message" replies. 'R' becomes 'E' to restore whole env. */ if (-1 == (iMemSize = PTBCmdInt(sHandle, fWhole ? 'E' : 'R'))) { fprintf(stderr, "%s: write: %s: %s\n", progname, pcOuter, strerror(errno)); exit(EX_PROTOCOL); } /* Under -t (aka from sshw) we need to remap things that point * to our original socket to mention the local proxy name. */ if ((char *)0 == (psEnv = malloc((iMemSize|7)+1))) { fprintf(stderr, "%s: malloc: %ld: %s\n", progname, (long)iMemSize, strerror(errno)); exit(EX_OSERR); } if (-1 == ReadBlock(sHandle, psEnv, iMemSize)) { fprintf(stderr, "%s: %s: read environment: %s\n", pcOuter, progname, strerror(errno)); exit(EX_PROTOCOL); } if ((char *)0 != pcTags) { if (1 != write(sHandle, "U", 1)) { fprintf(stderr, "%s: write: %d: %s\n", progname, sHandle, strerror(errno)); exit(EX_OSERR); } pcRep = ReadTable(sHandle, (char *)0); } } for (uN = 1, pcN = psEnv; '\000' != *pcN; ++uN) { pcN += strlen(pcN)+1; } if (!fQuit) { if (-1 != sHandle) close(sHandle); sHandle = -1; } environ = ModEnv(uN, psEnv, environ, pcRep, pcTags); switch (-1 == sHandle ? 0 : (wInferior = fork())) { case -1: fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); exit(EX_OSERR); case 0: if (-1 == sHandle) CleanUp(); else close(sHandle); pcArgv0 = argv[0]; if (0 != strcmp(acMyDefName, progname)) { argv[0] = progname; } (void)execvp(pcArgv0, argv); fprintf(stderr, "%s: execvp: %s: %s\n", progname, argv[0], strerror(errno)); exit(EX_OSERR); default: break; } while (0 < (wReap = wait3(& wStatus, 0, (struct rusage *)0))) { if (wInferior != wReap) continue; wInferior = 0; wInfExits = wStatus; } /* Tell the master to abandon forever (:) mode, we get back the * number of client connected (ourself included) which we ignore. * If we got back "-No\n", then the master was not in : mode -- ksb */ if (fQuit) { auto char acTrash[64]; write(sHandle, "Q", 1); read(sHandle, acTrash, sizeof(acTrash)); /* The number of clients left is not useful to us. */ } close(sHandle); exit(wInfExits); } /* The client sent us a suffix on our socket name send them back (ksb) * a socket connected to the original wrapper. We assume that all * wrappers can accept a socket without any special options set _before_ * the connection is established. LLL add options if ever needed. * pcReq is m/[\d]+$/ for a plain connection m/[\d+][GND]/ reserved * for example 2N might send the pid creds ptbw wants. The "+" * command never makes it here to catch the postfix letters. */ static int MapReply(char *pcReq, char *pcOpts) /*ARGSUSED*/ { register int iCmp, i; register char *pcLook; if ((char **)0 == ppcTableau) { return -1; } iCmp = atoi(pcReq); for (i = 0; (char *)0 != (pcLook = ppcTableau[i]); ++i) { if (iCmp == atoi(pcLook)) break; } if ((char *)0 == pcLook || (char *)0 == (pcLook = strchr(pcLook, ':'))) { return -1; } return divConnect(++pcLook, RightsWrap, (void *)0); } /* Receive an array of descriptors through a unix domain socket (ksb/bj) * return number of descriptors found. Works on plain text too. * Copied from RightsRecvFd with a spin on it -- ksb. */ int MaybeRights(s, piFds, iFdCount, pcText, piTextLen) int s; int *piFds; unsigned iFdCount; char *pcText; size_t *piTextLen; { register int error, iLen; auto struct msghdr msg; auto struct iovec iov; #if HAVE_SCM_RIGHTS register struct cmsghdr *pcmsg; register int e; iLen = sizeof(struct cmsghdr) + sizeof(int)*iFdCount; if ((struct cmsghdr *)0 == (pcmsg = (struct cmsghdr *) malloc(iLen))) { errno = ENOMEM; return -1; } pcmsg->cmsg_len = iLen; pcmsg->cmsg_level = SOL_SOCKET; pcmsg->cmsg_type = SCM_RIGHTS; msg.msg_control = (caddr_t) pcmsg; msg.msg_controllen = pcmsg->cmsg_len; #else msg.msg_accrights = (caddr_t) piFds; msg.msg_accrightslen = sizeof(int) * iFdCount; #endif /* HAVE_SCM_RIGHTS */ /* no name (assume socket is connected) */ msg.msg_name = (char *)0; msg.msg_namelen = 0; /* some sync data data and our file descriptors */ iov.iov_base = pcText; iov.iov_len = (size_t *)0 == piTextLen ? 0 : *piTextLen; msg.msg_iov = &iov; msg.msg_iovlen = 1; error = recvmsg(s, &msg, 0); if ((size_t *)0 != piTextLen) { *piTextLen = error; } #if HAVE_SCM_RIGHTS iLen = (0 == msg.msg_controllen) ? 0 : (msg.msg_controllen - sizeof(struct cmsghdr)); { e = errno; memcpy(piFds, pcmsg+1, iLen); free((void *)pcmsg); errno = e; } #else iLen = msg.msg_accrightslen; #endif /* HAVE_SCM_RIGHTS */ return iLen/sizeof(int); } /* Record the wrapper environment in a plan file for -t (ksb) * 'U', 'R' -> file */ static void Scribe(int argc, char **argv) { register int sHandle, iCc; auto const char *pcOuter; auto char acPump[8*1024]; auto struct stat stSock; if (0 == argc) { /* use stdout */ } else if (1 != argc) { fprintf(stderr, "%s: -R: too many output filenames\n", progname); exit(EX_USAGE); } else if ('-' == argv[0][0] && '\000' == argv[0][1]) { /* use stdout */ } else if ((FILE *)0 == freopen(argv[0], "wb", stdout)) { exit(EX_CANTCREAT); } /* We handle the case were -t is a file, but if you really * want to copy a file you should use cp(1). */ if ((char *)0 == pcTags) { pcTags = divSelect(); } if (-1 == lstat(pcTags, & stSock) || S_ISSOCK(stSock.st_mode)) { sHandle = FindMyself(pcTags, &pcOuter); if (3 != write(sHandle, fWhole ? "UE." : "UR.", 3)) { fprintf(stderr, "%s: %s: write: %s\n", progname, pcOuter, strerror(errno)); exit(EX_PROTOCOL); } } else if (S_ISDIR(stSock.st_mode)) { fprintf(stderr, "%s: %s: is a directory\n", progname, pcTags); exit(EX_DATAERR); } else if (-1 == (sHandle = open(pcTags, O_RDONLY, 0444))) { fprintf(stderr, "%s: open: %s: %s\n", progname, pcTags, strerror(errno)); exit(EACCES == errno ? EX_NOPERM : EX_OSERR); } while (0 < (iCc = read(sHandle, acPump, sizeof(acPump)))) { if (-1 == fwrite(acPump, iCc, sizeof(char), stdout)) { fprintf(stderr, "%s: fwrite: %s\n", progname, strerror(errno)); exit(EX_CANTCREAT); } } fflush(stdout); /* paranoid */ exit(0); } /* The client sends us a number, we send back that diversion (ksb) * as access rights. They send '\000', and maybe a 'Q' * or 'V' command. We send fd rights and text back only. For a * new client (a direct connection to establish a new environment) * dump our Tableau and our _link variables to the client in a * big dump they can give to setenv. */ static void FiniteStates(sCtl) { register int iClients, i, iTok, iGot; auto fd_set fdsTemp, fdsReading; auto int iMaxPoll, fdSend, aiSocks[2]; auto char acSend[8192]; /* short reply strings */ typedef struct FNnode { fd_set fdOwn; } FINITE_STATE; aiSocks[0] = aiSocks[1] = -1; iClients = 0; iMaxPoll = sCtl; FD_ZERO(& fdsReading); FD_SET(sCtl, & fdsReading); while (0 != wInferior || 0 != iClients) { fdsTemp = fdsReading; if (-1 == (iGot = select(iMaxPoll+1, &fdsTemp, (fd_set *)0, (fd_set *)0, (struct timeval *)0))) { continue; } if (0 < iGot && FD_ISSET(sCtl, & fdsTemp)) { register int sClient; if (-1 == (sClient = accept(sCtl, (struct sockaddr *)0, (void *)0))) { if (EINTR == errno) continue; /* Someone broke our listening socket. */ FD_CLR(sCtl, & fdsReading); close(sCtl); continue; } if (sClient >= iMaxPoll) { iMaxPoll = sClient; } ++iClients; FD_SET(sClient, & fdsReading); --iGot; } /* Start at stderr+1, don't scan 0, 1, 2 every time -- ksb */ for (i = 3; 0 != iGot && i <= iMaxPoll; ++i) { register int iLen; auto char acCmd[64]; if (!FD_ISSET(i, & fdsTemp) || sCtl == i) { continue; } --iGot; iLen = read(i, acCmd, 1); if (1 != iLen || '.' == acCmd[0]) { drop: close(i); FD_CLR(i, & fdsReading); --iClients; continue; } switch (acCmd[0]) { case 'T': /* send man ppcTableau prefixes */ SendTable(i, (const char **)ppcTableau); continue; case 'R': /* send man client restore env */ SendTable(i, (const char **)ppcClientRestore); continue; case 'V': /* ask for our version (std) */ if (4 != write(i, acProto, sizeof(acProto)-1)) goto drop; continue; case 'Q': /* if in : mode quit after this (std) */ if (getpid() == wInferior) { wInferior = 0; wInfExits = EX_OK; } if (0 == wInferior) { iTok = iClients; break; } write(i, "-No\n", 4); continue; case '+': /* build a new connection between us (spec) */ /* read the terminating \000 or \n */ iLen = read(i, acCmd, 1); if (1 == iLen && isdigit(acCmd[0])) { /* allow "+2" for "2", sigh */ goto digit; } /* we might have "+G" or "+N", ignore cmd */ if (-1 == socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, aiSocks)) { /* oh, crap! */ write(i, "-Fail\n", 6); continue; } if (-1 == RightsSendFd(i, &aiSocks[1], 1, "+Me\n")) { write(i, "-Ouch\n", 6); close(aiSocks[1]); close(aiSocks[0]); continue; } if (aiSocks[0] >= iMaxPoll) { iMaxPoll = aiSocks[0]; } ++iClients; FD_SET(aiSocks[0], & fdsReading); /* XXX is +N, send our creds, not yet */ close(aiSocks[1]); continue; case '\"': /* just for debugging, not optimized */ write(2, progname, strlen(progname)); write(2, ": ", 2); while (1 == read(i, acCmd, 1) && '\"' != acCmd[0]) { write(2, acCmd, 1); } write(2, "\n", 1); continue; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': digit: /* an indirect client */ while (1 == read(i, acCmd+iLen, 1) && isdigit(acCmd[iLen])) { if (sizeof(acCmd)-1 == ++iLen) break; } acCmd[iLen] = '\000'; if (-1 == (fdSend = MapReply(acCmd, (char *)0))) { write(i, "-No such diversion\n", 19); continue; } write(i, "@", 1); if (-1 == RightsSendFd(i, &fdSend, 1, acCmd)) { /* ouch, I don't know */ } close(fdSend); continue; case 'E': /* ask for our new environt */ SendTable(i, (const char **)ppcEnvClient); continue; case 'Y': /* ask for the original environment */ snprintf(acSend, sizeof(acSend), "%d\n", uOldSize); write(i, acSend, strlen(acSend)); write(i, ppcOldEnv[0], uOldSize); write(1, "\n", 1); continue; case 'U': /* ask for our socket name (std) */ SendString(i, pcMySocket); continue; case 'C': /* ptbc ask for meta comments (std) */ if (8 != write(i, "5\nNone\000\n", 8)) goto drop; continue; case 'M': /* ask for our pid, std */ iTok = getpid(); break; case '-': default: /* unknonw command */ write(i, "-No\n", 4); case '\n': /* consume Alex's \n fetish */ case '\000': continue; } snprintf(acSend, sizeof(acSend), "%d\n", iTok); write(i, acSend, strlen(acSend)); } } } /* Setup the master process and turn it loose (ksb) */ static void Master(int argc, char **argv) { register const char *pcArgv0, *pcOuterLevel; register unsigned uMasterLevel; auto int sCtl; static char *apcDefArgv[2]; if (0 == argc || (char **)0 == argv || (char *)0 == argv[0]) { argc = 1; argv = apcDefArgv; if ((char *)0 == (apcDefArgv[0] = getenv("SHELL"))) { apcDefArgv[0] = "sh"; } apcDefArgv[1] = (char *)0; } if ((char *)0 == (pcOuterLevel = divCurrent((char *)0))) { uMasterLevel = 1; } else { uMasterLevel = atoi(pcOuterLevel)+1; } atexit(CleanUp); SafePlace(pcOuterLevel); sCtl = Bindings(uMasterLevel); SetupTableau(environ, pcMySocket); if (':' == argv[0][0] && '\000' == argv[0][1] && (char *)0 == argv[1]) { wInferior = getpid(); } else switch (wInferior = fork()) { case -1: fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); (void)chdir("/"); exit(EX_OSERR); case 0: (void)close(sCtl); pcArgv0 = argv[0]; if (0 != strcmp(acMyDefName, progname)) { argv[0] = progname; } environ = ppcEnvClient; (void)execvp(pcArgv0, argv); fprintf(stderr, "%s: execvp: %s: %s\n", progname, argv[0], strerror(errno)); exit(EX_OSERR); default: break; } /* We are not really a daemon, but we don't want to be in the way, * so free us from stdin, stdout, and the current working dir. * We keep stderr for messages, but shouldn't spew any. */ (void)close(0); (void)open("/dev/null", O_RDONLY, 0666); (void)close(1); (void)open("/dev/null", O_WRONLY, 0666); (void)chdir(pcMySpace); FiniteStates(sCtl); close(sCtl); (void)chdir("/"); exit(wInfExits); }