#!mkcmd # $Id: hostdb.m,v 1.85 2012/08/30 00:21:34 ksb Exp $ # host seletion options and config file reader (ksb) # The implemetor must require "evector.m" before us, or a replacement. from '' from '' from '' from '' from '' require "util_fsearch.m" key "util_fsearch_cvt" { "acHxmdSearch" } require "util_ppm.m" accum[":"] 'C' { named "pcConfigs" param "configs" help "files of hosts and attribute macros, colon separated" } function 'B' { named "AddBoolean" param "macros" help "boolean check, macros must be defined to select host" } type "argv" 'G' { named "pPPMGuard" param "guard" help "expressions to select hosts by name" } function 'E' { named "AddExclude" param "compares" help "macro value must satisfy given relation to select hosts" } accum[":"] 'X' { named "pcExConfig" param "ex-configs" help "add attribute macro data to defined hosts, colon separated" } type "argv" 'Y' { named "pPPMSpells" param "top" help "m4 commands included at the top of the guard processing" } accum[":"] 'Z' { named "pcZeroConfig" param "zero-config" help "configuration file(s) to set default macro values" } char* 'k' { named "pcHOST" init '"HOST"' param "key" help "change the merge macro name from %qi to key" } accum ["\\t"] 'o' { named "pcOutMerge" track "fGaveMerge" init "(char *)0" param "attributes" help "output format of the merged configuration file" after "if (fGaveMerge && %ypi == %n) {%n = \"\";}" } %hi typedef struct MHnode { unsigned udefs; /* number of times we were defined */ unsigned iline; /* line number, also "boolean forbids" */ char *pcfrom; /* config file we were first mentioned */ char *pcrecent; /* most recently mentioned file */ const char *pchost; /* our host name from HOST */ int uu; /* the value we think %u has for the cmd*/ int istatus; /* exit code from xclate, under -r|-K */ MACRO_DEF *pMD; /* our macro definition list */ struct MHnode *pMHnext; /* traverse active hosts in order */ } META_HOST; %% %i static char rcs_hostdb[] = "$Id: hostdb.m,v 1.85 2012/08/30 00:21:34 ksb Exp $"; static const char *FindCache(const char *, const char *); static void HostDefs(FILE *, META_HOST *); static char *HostSlot(char *); static const char *HostTarget(META_HOST *pMHFor); static META_HOST *NextHost(), *HostInit(char **); static void BindHost(); static void FdCopy(int, int , char *); static void AddBoolean(int, char *); static void AddExclude(int, char *); static char **ReserveStdin(char **, char *); void RecurBanner(FILE *fp); void BuildM4(FILE *fp, char **ppcBody, META_HOST *pMHFor, char *pcPre, char *pcPost); static PPM_BUF PPMAutoDef; static char *pcHostSearch; static char acTilde[MAXPATHLEN+4]; static int M4Pushdef(const char *, const char *, void *); static int M4ListMacro(const char *, const char *, void *); static META_HOST *FindHost(char *pcLook, size_t wLen); %% init 4 "HostStart(%K1v);" %c /* find the cache file based on the mktemp prefix we want (ksb) */ static const char * FindCache(const char *pcBase, const char *pcSlot) { register const char *pcEnd; for (/* param */; '\000' != pcSlot; pcSlot += strlen(pcSlot)+1) { if ((char *)0 == (pcEnd = strrchr(pcSlot, '/'))) pcEnd = pcSlot; else ++pcEnd; if (0 == strncmp(pcBase, pcEnd, strlen(pcBase)-1)) return pcSlot; } fprintf(stderr, "%s: hostdb code out of sync with me (no slot named %s)\n", progname, pcBase); exit(EX_SOFTWARE); /*NOTREACHED*/ } /* It is sad that sendfile is so specific (ksb) */ static void FdCopy(int fdIn, int fdOut, char *pcTag) { auto char acBuffer[8192]; register int iCc, iOc; register char *pc; while (0 < (iCc = read(fdIn, acBuffer, sizeof(acBuffer)))) { for (pc = acBuffer; iCc > 0; pc += iOc, iCc -= iOc) { if (0 < (iOc = write(fdOut, pc, iCc))) continue; fprintf(stderr, "%s: write: %s: %s\n", progname, pcTag, strerror(errno)); exit(EX_OSERR); } } } /* Reserve the stdin stream for some use, one of (ksb) * zero configuration, files, configuration * or fetch the pointer (and reset the state machine). */ static char ** ReserveStdin(char **ppcIn, char *pcWhat) { static char *pcHolding = (char *)0; static char **ppcCur; if ((char **)0 == ppcIn) { ppcIn = ppcCur; ppcCur = (char **)0; pcHolding = (char *)0; return ppcIn; } if ((char *)0 != pcHolding) { fprintf(stderr, "%s: stdin: invalid multiple uses \"%s\" and \"%s\" conflict\n", progname, pcHolding, pcWhat); exit(EX_USAGE); } pcHolding = pcWhat; return ppcCur = ppcIn; } static char *pcWhichFile; static unsigned iLine; /* Copy the m4 string, stop at the first unbalanced close quote (ksb) * return the number of characters in the string. */ static void M4String(FILE *fpCursor, char *pcConvert) { register unsigned uDepth; register int c, iErrLine; iErrLine = iLine; for (uDepth = 1; EOF != (c = getc(fpCursor)); ++pcConvert) { *pcConvert = c; if ('\n' == c) { ++iLine; } else if ('`' == c) { ++uDepth; } else if ('\'' == c && 0 == --uDepth) { break; } } if ('\000' == c) { fprintf(stderr, "%s: %s:%d unterminated m4 string\n", progname, pcWhichFile, iErrLine); exit(EX_DATAERR); } *pcConvert = '\000'; } /* Convert the C string into internal data, find the length of (ksb) * the input representation. */ static void CString(FILE *fpCursor, char *pcConvert) { register int i, c; auto int iErrLine; iErrLine = iLine; while (EOF != (c = getc(fpCursor))) { *pcConvert++ = c; if ('\"' == c) { --pcConvert; break; } if ('\\' != c) continue; c = getc(fpCursor); switch (c) { case EOF: break; case '\n': /* \ followed by newline is the empty string */ ++iLine; --pcConvert; continue; case 'e': /* \e -> \ */ continue; case 'n': pcConvert[-1] = '\n'; continue; case 't': pcConvert[-1] = '\t'; continue; case 'b': pcConvert[-1] = '\b'; continue; case 'r': pcConvert[-1] = '\r'; continue; case 'f': pcConvert[-1] = '\f'; continue; case 'v': pcConvert[-1] = '\v'; continue; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': pcConvert[-1] = c - '0'; for (i = 1; i < 3; ++i) { c = getc(fpCursor); if ('0' > c || c > '7') { ungetc(c, fpCursor); break; } pcConvert[-1] <<= 3; pcConvert[-1] += c - '0'; } continue; case '8': case '9': case '\\': case '\"': case '\'': default: pcConvert[-1] = c; continue; } break; } if ('\000' == c) { fprintf(stderr, "%s: %s:%d unterminated C string\n", progname, pcWhichFile, iErrLine); exit(EX_DATAERR); } *pcConvert = '\000'; } #define HT_EOF -1 /* no more tokens */ #define HT_STRING '\"' /* either "c-string" or `m4-string' */ #define HT_EQ '=' /* literal (token in character) */ #define HT_NL '\n' /* literal */ #define HT_UNDEF '.' /* literal */ #define HT_SCOPED ':' /* writen as .. w/o quotes */ #define HT_HEADER '%' /* literal */ #define HT_WORD 'W' /* word not a valid m4 macro name */ #define HT_MACRO 'M' /* word | macro */ /* Tokenize the configuration file (ksb) */ int Token(FILE *fpCursor, char *pcText) { register int iRet, i; *pcText = '\000'; while (EOF != (iRet = getc(fpCursor))) { if ('\n' == iRet || !isspace(iRet)) { break; } } switch (iRet) { case EOF: return HT_EOF; case '=': case '\n': case '%': break; case '\"': CString(fpCursor, pcText); break; case '#': while (EOF != (iRet = getc(fpCursor))) { if ('\n' == iRet) break; } break; case '`': iRet = '\"'; M4String(fpCursor, pcText); break; case '\'': fprintf(stderr, "%s: %s:%u: stray \', unbalanced m4 quotes?\n", progname, pcWhichFile, iLine); return Token(fpCursor, pcText); case '.': /* the undefined value, or a string like ".eq." */ i = getc(fpCursor); if (EOF != i) { ungetc(i, fpCursor); } if (EOF == i || isspace(i) || '#' == i) { break; } if ('.' == i) { *pcText++ = '.'; i = getc(fpCursor); /* skip . */ i = getc(fpCursor); if (EOF == i || isspace(i) || '#' == i) { if (EOF != i) ungetc(i, fpCursor); iRet = HT_SCOPED; break; } ungetc(i, fpCursor); iRet = '.'; } /*fallthrough*/ default: *pcText++ = iRet; iRet = (isalpha(iRet) || '_' == iRet) ? HT_MACRO : HT_WORD; for (/*nada*/; EOF != (i = getc(fpCursor)); ++pcText) { *pcText = i; if ('=' == i || isspace(i)) break; if (HT_MACRO == iRet && !isalpha(i) && !isdigit(i) && '_' != i) iRet = HT_WORD; } if (EOF != i) { ungetc(i, fpCursor); } *pcText = '\000'; break; } return iRet; } /* Extract the first absolute path from a $PATH-like string (ksb) */ static void FirstAbsolute(char *pcTemp, size_t wLen, char *pcFrom) { register int fSaw, fColon, c; fSaw = 0, fColon = 1; while ('\000' != (c = *pcFrom++)) { if (fColon) { if (fSaw) { *pcTemp = '\000'; return; } fColon = 0; fSaw = ('/' == c); } if (':' == c) { fColon = 1; continue; } if (!fSaw) { continue; } if (0 == wLen--) { return; } *pcTemp++ = c; } *pcTemp = '\000'; } static PPM_BUF PPMString, PPMHeaders, PPMRestore, PPMHosts, PPMBooleans, PPMWardPre, PPMWardFix; /* make sure the PPMs are all setup for the space we'll need to start (ksb) */ static void HostStart(char *pcSearch) { register char **ppc, *pc, *pcHxPath; register META_HOST *pMH; pcHostSearch = pcSearch; acTilde[0] = '\000'; if ((char *)0 != (pcHxPath = getenv(acHxmdLib))) { FirstAbsolute(acTilde, sizeof(acTilde)-1, pcHxPath); } if ('\000' == acTilde[0]) { FirstAbsolute(acTilde, sizeof(acTilde)-1, pcSearch); } acTilde[sizeof(acTilde)-1] = '\000'; util_ppm_init(& PPMAutoDef, sizeof(char *), 32); ppc = (char **)util_ppm_size(& PPMAutoDef, 16); ppc[0] = (char *)0; util_ppm_init(& PPMString, sizeof(char), 1024); util_ppm_init(& PPMRestore, sizeof(char *), 64); ppc = (char **)util_ppm_size(& PPMRestore, 16); ppc[0] = (char *)0; util_ppm_init(& PPMHeaders, sizeof(char *), 64); ppc = (char **)util_ppm_size(& PPMHeaders, 16); ppc[0] = (char *)0; util_ppm_init(& PPMHosts, sizeof(META_HOST), 512); pMH = (META_HOST *)util_ppm_size(& PPMHosts, 1024); pMH[0].iline = 0; pMH[0].pcfrom = (char *)0; pMH[0].pcrecent = (char *)0; pMH[0].pchost = (char *)0; pMH[0].pMD = (MACRO_DEF *)0; util_ppm_init(& PPMBooleans, sizeof(char *), 16); ppc = (char **)util_ppm_size(& PPMBooleans, 16); ppc[0] = (char *)0; util_ppm_init(& PPMWardPre, sizeof(char), 256); pc = (char *)util_ppm_size(& PPMWardPre, 256); *pc = '\000'; util_ppm_init(& PPMWardFix, sizeof(char), 256); pc = (char *)util_ppm_size(& PPMWardFix, 256); *pc = '\000'; } /* Read host configuration file, distrib format or our format (ksb) * file ::= line * '\000' * line ::= ( '#' comment | marco '=' term | definition | '%' headers ) '\n' * comment ::= [^\n]* * macro ::= [a-zA-Z_][a-zA-Z0-9_]* * term ::= '.' | [^space]* | "C-string" | `m4 string' * headers ::= ('%' | macro | '.' ) * * definition ::= term term* * * Just start with minimal default header: %HOST * When you want distrib's set them in the config file, please. */ static const char acUndef[] = ".", acScoped[] = ".."; static void ReadHcf(FILE *fpCursor, char *pcSpace, int fAllowDef) { register int iTok, iCols, iNext, iLast; register const char **ppcHeader, **ppcRestore; register char *pcKeep; /* Start a new file with new header information, then parse it. */ iCols = 0; ppcHeader = util_ppm_size(& PPMHeaders, 16); ppcHeader[iCols++] = pcHOST; ppcHeader[iCols] = (char *)0; ppcRestore = util_ppm_size(& PPMRestore, 16); ppcRestore[0] = (const char *)0; ppcRestore[1] = (const char *)0; iLine = 1; iTok = Token(fpCursor, pcSpace); while (HT_EOF != iTok) { switch (iTok) { case HT_EQ: /* errors */ fprintf(stderr, "%s: %s:%u stray operator `%c'\n", progname, pcWhichFile, iLine, iTok); exit(EX_DATAERR); case HT_NL: /* track blank/comment lines */ ++iLine; iTok = Token(fpCursor, pcSpace); continue; case HT_HEADER: /* redefine headers */ for (iCols = 0; HT_NL != (iTok = Token(fpCursor, pcSpace)); ++iCols) { register int i, fWarned; ppcHeader = util_ppm_size(& PPMHeaders, ((iCols+1)|15)+1); if (HT_UNDEF == iTok) { ppcHeader[iCols] = acUndef; continue; } if (HT_HEADER == iTok) { ppcHeader[iCols] = pcHOST; } else if (HT_MACRO != iTok) { fprintf(stderr, "%s: %s:%u header construction must include only m4 macro names, or undef (%s)\n", progname, pcWhichFile, iLine, acUndef); exit(EX_DATAERR); } else { ppcHeader[iCols] = strdup(pcSpace); } /* Check for duplicate macros in header */ fWarned = 0; for (i = 0; !fWarned && i < iCols; ++i) { if (0 != strcmp(ppcHeader[i], ppcHeader[iCols])) continue; fprintf(stderr, "%s: %s:%u header contains the same macro name \"%s\" more than once\n", progname, pcWhichFile, iLine, ppcHeader[iCols]); fWarned = 1; } } if (0 == iCols) { fprintf(stderr, "%s: %s:%u header without any columns\n", progname, pcWhichFile, iLine); exit(EX_DATAERR); } ppcHeader[iCols] = (char *)0; /* Prime the restore vector to be long enough */ if ((void *)0 == util_ppm_size(& PPMRestore, (iCols|15)+1)) { fprintf(stderr, "%s: %s:%u: out of memory\n", progname, pcWhichFile, iLine); exit(EX_OSERR); } continue; case HT_UNDEF: case HT_SCOPED: *pcSpace = '\000'; break; case HT_WORD: case HT_MACRO: case HT_STRING: break; } iLast = iTok; pcKeep = strdup(pcSpace); iTok = Token(fpCursor, pcSpace); if (HT_EQ == iTok) { iTok = Token(fpCursor, pcSpace); switch (iTok) { case HT_WORD: case HT_MACRO: case HT_STRING: BindMacro(pcKeep, strdup(pcSpace)); break; case HT_SCOPED: /* "COLOR=.." redundantly keeps same value */ BindMacro(pcKeep, CurrentDef(pcKeep, (char *)0)); break; case HT_UNDEF: BindMacro(pcKeep, (char *)0); break; default: fprintf(stderr, "%s: %s:%u %s= syntax error\n", progname, pcWhichFile, iLine, pcKeep); exit(EX_DATAERR); } iTok = Token(fpCursor, pcSpace); continue; } /* We need one value for each macro header */ ppcHeader = util_ppm_size(& PPMHeaders, 0); ppcRestore = util_ppm_size(& PPMRestore, 0); ppcRestore[0] = (acUndef == ppcHeader[0]) ? (char *)0 : (HT_UNDEF == iLast) ? (char *)0 : pcKeep; for (iNext = 1; HT_EOF != iTok && HT_NL != iTok; ++iNext) { if ((char *)0 == ppcHeader[iNext]) { fprintf(stderr, "%s: %s:%u: out of columns for value `%s' (column %d)\n", progname, pcWhichFile, iLine, pcSpace, iNext+1); exit(EX_CONFIG); } if (acUndef == ppcHeader[iNext]) { ppcRestore[iNext] = acUndef; } else if (HT_UNDEF == iTok) { ppcRestore[iNext] = (char *)0; } else if (HT_SCOPED == iTok) { ppcRestore[iNext] = acScoped; } else if (HT_EQ == iTok) { ppcRestore[iNext] = "="; } else { ppcRestore[iNext] = strdup(pcSpace); } iTok = Token(fpCursor, pcSpace); } /* Warn about missing columns under -d M */ for (/*above*/; iNext < iCols; ++iNext) { if ((char *)0 != strchr(pcDebug, 'M')) fprintf(stderr, "%s: %s:%d missing value for `%s' definition\n", progname, pcWhichFile, iLine, ppcHeader[iNext]); ppcRestore[iNext] = acScoped; } for (iNext = 0; iNext < iCols; ++iNext) { register const char *pc; if (acUndef == ppcHeader[iNext]) continue; if (acScoped == (pc = ppcRestore[iNext])) continue; ppcRestore[iNext] = CurrentDef(ppcHeader[iNext], (char *)0); BindMacro(ppcHeader[iNext], pc); } BindHost(fAllowDef); /* unbind and restore */ for (iNext = 0; iNext < iCols; ++iNext) { register const char *pc; if (acUndef == ppcHeader[iNext]) continue; if (acScoped == (pc = ppcRestore[iNext])) continue; BindMacro(ppcHeader[iNext], ppcRestore[iNext]); } } for (ppcHeader = util_ppm_size(& PPMHeaders, 0); (char *)0 != *ppcHeader; ++ppcHeader) { if (acUndef != *ppcHeader) BindMacro(*ppcHeader, (char *)0); } } static char *apcMake[] = { "cache", /* cache for main */ #define pcstdin apctemp[0] "scratch", /* the -E, -G temp file, fill to filter */ #define pcscratch apctemp[1] "make", /* recipe file from cache.host dir */ #define pcmake apctemp[2] "filter" /* a valid slot to fill (\000\000) */ #define pcfilter apctemp[3] }; static struct { /* very private data */ unsigned ucount; /* how many hosts we saw */ unsigned uselected; /* how many we picked */ char *apctemp[sizeof(apcMake)/sizeof(char *)]; /* apcMake shadow*/ } HostDb; /* Define a new host, or update some bindings (ksb) */ static void BindHost(int fAlways) { register META_HOST *pMHCur; register MACRO_DEF *pMDHost; register const char *pcHost; register unsigned i; pMDHost = CurrentMacros(); if ((char *)0 == (pcHost = MacroValue(pcHOST, pMDHost))) { fprintf(stderr, "%s: %s:%u no %s value defined\n", progname, pcWhichFile, iLine, pcHOST); return; } if ((META_HOST *)0 == (pMHCur = (META_HOST *)util_ppm_size(& PPMHosts, (HostDb.ucount|1023)+1))) { fprintf(stderr, "%s: out of memory\n", progname); exit(EX_OSERR); } for (i = 0; i < HostDb.ucount; ++i) { if (0 == strcasecmp(pMHCur[i].pchost, pcHost)) { break; } } if (i < HostDb.ucount) { /* We could free the old pMD, with very litle space saved */ pMHCur[i].pMD = MergeMacros(pMHCur[i].pMD, pMDHost); if (pcWhichFile != pMHCur[i].pcrecent) { pMHCur[i].udefs += fAlways; } pMHCur[i].pcrecent = pcWhichFile; if ((char *)0 != strchr(pcDebug, 'C')) { fprintf(stderr, "%s %s:%u redefined from %s:%u (B=%u)\n", pMHCur[i].pchost, pcWhichFile, iLine, pMHCur[i].pcfrom, pMHCur[i].iline, pMHCur[i].udefs); } return; } if (!fAlways) { return; } pMHCur[HostDb.ucount].iline = iLine; pMHCur[HostDb.ucount].pcfrom = pcWhichFile; pMHCur[HostDb.ucount].pcrecent = pcWhichFile; pMHCur[HostDb.ucount].udefs = 1; pMHCur[HostDb.ucount].pchost = pcHost; pMHCur[HostDb.ucount].uu = -1; pMHCur[HostDb.ucount].pMD = pMDHost; pMHCur[HostDb.ucount].pMHnext = (META_HOST *)0; ++HostDb.ucount; } /* Find a host by name (ksb) */ static META_HOST * FindHost(char *pcLook, size_t wLen) { register unsigned u; register META_HOST *pMHAll; if ((char *)0 != strchr(pcDebug, 'L')) { fprintf(stderr, "%.*s\n", (int)wLen, pcLook); } pMHAll = (META_HOST *)util_ppm_size(& PPMHosts, 0); for (u = 0; u < HostDb.ucount; ++u) { if (0 == strncasecmp(pcLook, pMHAll[u].pchost, wLen) && '\000' == pMHAll[u].pchost[wLen]) return & pMHAll[u]; } return (META_HOST *)0; } /* Add a new config file to the mix (ksb) * If the `file' is a socket we connect, expect a +length\n * as the first line, then read the configuration from the socket. * This is undocumented in version 1, but will be spiffy in version 2. */ static void ReadConfig(char *pcConfig, int fAllowDef) { register char *pcFound, *pcSearch; register FILE *fpConfig; auto struct stat stLen; auto char *apcLook[2]; auto char acBanner[1024]; apcLook[0] = pcConfig; apcLook[1] = (char *)0; if ((char *)0 != (pcSearch = getenv(acHxmdLib)) && '\000' == *pcSearch) { pcSearch = (char *)0; } if ((char *)0 == (pcFound = util_fsearch(apcLook, pcSearch))) { fprintf(stderr, "%s: %s: cannot find file\n", progname, pcConfig); exit(EX_NOINPUT); } else if (0 != stat(pcFound, &stLen)) { fprintf(stderr, "%s: stat: %s: %s\n", progname, pcFound, strerror(errno)); exit(EX_NOINPUT); } else if (S_ISFIFO(stLen.st_mode)) { if ((FILE *)0 == (fpConfig = fopen(pcFound, "r"))) { fprintf(stderr, "%s: fopen: %s: %s\n", progname, pcFound, strerror(errno)); exit(EX_OSERR); } if ((char *)0 == fgets(acBanner, sizeof(acBanner), fpConfig)) { fprintf(stderr, "%s: %s: no data\n", progname, pcFound); fclose(fpConfig); return; } if ('+' != acBanner[0]) { fprintf(stderr, "%s: %s: %s", progname, pcFound, acBanner); fclose(fpConfig); exit(EX_SOFTWARE); } stLen.st_size = strtol(acBanner+1, (char **)0, 0); pcWhichFile = pcFound; } else if (S_ISSOCK(stLen.st_mode)) { register int s; auto struct sockaddr_un run; if (-1 == (s = socket(AF_UNIX, SOCK_STREAM, 0))) { fprintf(stderr, "%s: socket: %s\n", progname, strerror(errno)); exit(EX_UNAVAILABLE); } run.sun_family = AF_UNIX; (void)strcpy(run.sun_path, pcFound); if (-1 == connect(s, (struct sockaddr *)&run, EMU_SOCKUN_LEN(pcFound))) { fprintf(stderr, "%s: connect: %s: %s\n", progname, pcFound, strerror(errno)); exit(EX_UNAVAILABLE); } if ((FILE *)0 == (fpConfig = fdopen(s, "r"))) { fprintf(stderr, "%s: fdopen: %d: %s\n", progname, s, strerror(errno)); exit(EX_OSERR); } if ((char *)0 == fgets(acBanner, sizeof(acBanner), fpConfig)) { fprintf(stderr, "%s: %s: no data\n", progname, pcFound); fclose(fpConfig); return; } if ('+' != acBanner[0]) { fprintf(stderr, "%s: %s: %s", progname, pcFound, acBanner); fclose(fpConfig); exit(EX_SOFTWARE); } stLen.st_size = strtol(acBanner+1, (char **)0, 0); pcWhichFile = pcFound; } else if ((FILE *)0 == (fpConfig = fopen(pcFound, "r"))) { fprintf(stderr, "%s: fopen: %s: %s\n", progname, pcConfig, strerror(errno)); exit(EX_OSERR); } else if (-1 == fstat(fileno(fpConfig), & stLen)) { fprintf(stderr, "%s: fstat: %d on %s: %s\n", progname, fileno(fpConfig), pcConfig, strerror(errno)); exit(EX_OSERR); } else { pcWhichFile = pcFound; } /* We know the longest string possible in file is st_size-4 * (viz. X=`string'), I'm lazy so reuse one longer than that. */ if (0 != stLen.st_size) { ReadHcf(fpConfig, util_ppm_size(& PPMString, (stLen.st_size|3)+1), fAllowDef); } if (stdin != fpConfig) { fclose(fpConfig); } } /* Our compares come from 'E', turn them into m4 expressions (ksb) * -E [!]macro=value * -E [!]macro!value * "ifelse(macro,`value',`" "')" * "ifelse(macro,`value',`',`" "')" * -E [!]macro { == | != | < | > | <= | >= } value * -E [!][-]int { == | != | < | > | <= | >= } value * "ifelse(eval(macro {op} value), 1,`" "')" * macro = word | word( params ) * We add at most strlen("ifelse(eval(" . ",1,`" . "',`") + stlen(pcGuard) * to the prefix and less to the Suffix, which is always "')" * N.B. use quotes to get PUNCT=`=' */ static void AddExclude(int iOpt, char *pcOrig) { register unsigned uFix, uAdd; register char *pcPrefix, *pcSuffix, *pcMacro, *pcEos, *pcCompare; register int fComplement, fMath, fNegate; auto char acOp[4]; pcCompare = pcOrig; for (fComplement = 0; '!' == *pcCompare || isspace(*pcCompare); ++pcCompare) { if ('!' == *pcCompare) fComplement ^= 1; } /* we can't write on the arguments as we might exec with them in * mmsrc, which share this code --ksb */ pcCompare = strdup(pcCompare); uAdd = strlen(pcCompare)+32; pcPrefix = (char *)util_ppm_size(& PPMWardPre, 0); pcSuffix = (char *)util_ppm_size(& PPMWardFix, 0); pcPrefix = (char *)util_ppm_size(& PPMWardPre, ((strlen(pcPrefix)+uAdd)|255)+1); uFix = strlen(pcSuffix); pcSuffix = (char *)util_ppm_size(& PPMWardFix, ((uFix+uAdd)|255)+1); fNegate = 0; while ('-' == *pcCompare) { fNegate ^= 1; ++pcCompare; } /* Eat host or macro name and params after that name. Allows things like * -E "COLOR(HOST)=TINT(RACK)" * -E "www.example.com=HOST" # better -E HOST=www.example.com * We DO NOT look for m4 quotes here around parans, there is a limit to * my madness (as unbalanced parens are never required), sorry. * Maybe use -D!OPEN="(" then -E "defn(\`OPEN')=PUNCT" * We also allow ':' for an IPv6 address. */ for (pcMacro = pcCompare; isalnum(*pcCompare) || '_' == *pcCompare || '.' == *pcCompare || ':' == *pcCompare || '-' == *pcCompare; ++pcCompare) { /*nada*/ } if ('(' == *pcCompare) { register int iCnt = 1; ++pcCompare; while (0 != iCnt) { switch (*pcCompare++) { case ')': --iCnt; continue; case '(': ++iCnt; continue; case '\000': fprintf(stderr, "%s: -E: unbalanced parenthesis in macro parameter list: %s\n", progname, pcOrig); exit(EX_USAGE); default: continue; } } } if (fNegate) { *--pcMacro = '-'; } do { acOp[0] = *pcCompare; *pcCompare++ = '\000'; } while (isspace(acOp[0])); acOp[1] = '\000'; acOp[2] = '\000'; fMath = 0; switch (acOp[0]) { case '=': /* == or = */ if ('=' == *pcCompare) { acOp[1] = '='; fMath = 1; ++pcCompare; } break; case '!': /* != or ! -- slang for !macro=value */ if ('=' == *pcCompare) { acOp[1] = '='; fMath = 1; ++pcCompare; } else { fComplement ^= 1; } break; case '<': /* < or <= */ case '>': /* > or >= */ fMath = 1; if ('=' == *pcCompare) { acOp[1] = '='; ++pcCompare; } break; default: fprintf(stderr, "%s: -E: %s: unknown compare operator\n", progname, acOp); exit(EX_USAGE); } /* Append to the prefix the new condition, prepend to the suffix "')" */ pcEos = pcPrefix + strlen(pcPrefix); if (fMath) { snprintf(pcEos, uAdd, "ifelse(eval(%s%s(%s)),%s,`", /*)*/ pcMacro, acOp, pcCompare, fComplement ? "0" : "1"); } else { snprintf(pcEos, uAdd, "ifelse(%s,%s%s,`", /*)*/ pcMacro, pcCompare, fComplement ? ",`'" : ""); } memmove(pcSuffix+2, pcSuffix, uFix+1); pcSuffix[0] = '\''; pcSuffix[1] = /*(*/ ')'; } /* We'll take [!]*word[,[!]*word]* (ksb) * We won't do any '|'s, use a -G (or -o) for that, dude. Build an * intersection list (-B2) or even run two hxmd's. We can't write on Name. */ static void AddBoolean(int iOpt, char *pcName) { register char *pcFilter, *pcScan, **ppc; register int fComplement, i; register unsigned uCount; if ((char *)0 == (pcName = strdup(pcName))) { fprintf(stderr, "%s: strdup: %s\n", progname, strerror(errno)); exit(EX_OSERR); } ppc = (char **)util_ppm_size(& PPMBooleans, 1); for (uCount = 0; (char *)0 != ppc[uCount]; ++uCount) { /*nada*/ } /* Suggested and rejected ideas, so explain them here: (ksb) * -B -1 could be "listed in all but 1 file", but this is error-prone * given a -Z config may define hosts you do not expect we * wouldn't know how to count them. Viz. would we bump the * number of possible defs when the file didn't define any, or * only if it defined at least one? Too fragile. * -B THIS|THAT is better coded as (w/o shell quotes applied): * -E ok=ifdef(`THIS',ok,ifdef(`THAT',ok)) * -B THIS^THAT better code as: * -E ok=ifelse(ifdef(`THIS',yes),ifdef(`THAT',,yes),ok) * * This doesn't catch every possible misspelling (e.g. -B F!HOPE), * use -d B to trace strange failure. */ for (pcScan = pcFilter = pcName; '\000' != (i = *pcScan); ++pcScan) { if (isspace(i)) continue; if (isalnum(i) || '_' == i || '!' == i) { /*nada*/ } else if (',' == i || '&' == i) { i = ','; } else if ('|' == i || '^' == i) { fprintf(stderr, "%s: -B %s: use -E for disjunctions\n", progname, pcName); exit(EX_UNAVAILABLE); } else if ('-' == i) { fprintf(stderr, "%s: -B %s: negative counts not supported (error prone)\n", progname, pcName); exit(EX_UNAVAILABLE); } else { fprintf(stderr, "%s: -B %s: illegal character \"%c\" in macro name\n", progname, pcName, i); exit(EX_USAGE); } *pcFilter++ = i; } *pcFilter = '\000'; pcScan = pcName; do { for (fComplement = 0; '!' == *pcScan; ++pcScan) { fComplement ^= 1; } pcName = pcScan; while (isalnum(*pcScan) || '_' == *pcScan) { ++pcScan; } i = *pcScan; *pcScan = '\000'; /* All digits is a define number */ for (pcFilter = pcName; isdigit(*pcFilter); ++pcFilter) { /*nada*/; } if (isdigit(*pcName) && '\000' != *pcFilter) { fprintf(stderr, "%s: %s: m4 macro names cannot start with a digit\n", progname, pcName); exit(EX_USAGE); } if (fComplement) { *--pcName = '!'; } /* add to the list */ ppc = (char **)util_ppm_size(& PPMBooleans, uCount+1); ppc[uCount++] = strdup(pcName); *pcScan = i; if (',' == i) { ++pcScan; } } while ('\000' != *pcScan); ppc[uCount] = (char *)0; } /* Break a colon separated list into the strings, trash the orginal. (ksb) * We consume /:+/ as the separator, empty file names are not meaningful. * If you give us a directory we look for the default name in it, and * the name "--" (or "--/") is replaced with the first full path in the * search list (kept in acTilde by HostStart). */ static char ** BreakList(PPM_BUF *pPPM, char *pcData, char *pcWho, char *pcDup) { register unsigned int u; register char *pcThis, **ppcList; auto char acTemp[MAXPATHLEN+4]; if ((char *)0 == pcData || '\000' == pcData[0]) { return (char **)0; } ppcList = util_ppm_size(pPPM, 32); for (u = 0; '\000' != *pcData; ++u) { ppcList = util_ppm_size(pPPM, ((u+1)|15)+1); ppcList[u] = pcThis = pcData; while ('\000' != *++pcData) { if (':' == *pcData) break; } while (':' == *pcData) { *pcData++ = '\000'; } if ('-' != pcThis[0]) { continue; } if ('\000' == pcThis[1]) { if ((char *)0 != pcDup) { ppcList[u] = pcDup; } else { ReserveStdin(& ppcList[u], pcWho); } continue; } if ('-' != pcThis[1] || !('/' == pcThis[2] || '\000' == pcThis[2])) { continue; } /* "--/file" or "--" maps to a file under the first absolute * path in the search list -- ksb */ (void)strncpy(acTemp, acTilde, sizeof(acTemp)); if ('/' == pcThis[2]) { (void)strncat(acTemp, pcThis+2, sizeof(acTemp)); } ppcList[u] = strdup(acTemp); } ppcList[u] = (char *)0; return ppcList; } /* Reproduce the list BreakList saw, with "-" translated to a file. (ksb) * We have to use full-path, which is not obvious from the context here. --ksb * You can "break" this with "./" as a prefix -- I wouldn't do that. * Technically we can still generate a pathname longer than MAXPATHLEN * I doubt that in the real world we'll use a lot of ../../../ * stuff in the filenames. --ksb */ static int MakeList(PPM_BUF *pPPM, char **ppcOut, const char *pcSuffix) { register char **ppcList, *pcMem, *pcRet, *pcComp; register unsigned uLen, u, fDotSlash, fFail; register char *pcSearch; auto struct stat stDir, stFound; auto char *apcLook[2], acCWD[MAXPATHLEN+4]; static char *pcCacheCWD = (char *)0; *ppcOut = (char *)0; if ((char *)0 != (pcSearch = getenv(acHxmdLib)) && '\000' == *pcSearch) { pcSearch = (char *)0; } /* We cache getcwd return to avoid slow disk searches for "../../"... */ if ((char *)0 != pcCacheCWD) { /* We have it from last call. */ } else if ((char *)0 == getcwd(acCWD, sizeof(acCWD))) { fprintf(stderr, "%s: getcwd: %s\n", progname, strerror(errno)); return 2; } else if ((char *)0 == (pcCacheCWD = strdup(acCWD))) { fprintf(stderr, "%s: strdup: %s\n", progname, strerror(ENOMEM)); return 3; } if ((char **)0 == (ppcList = util_ppm_size(pPPM, 0)) || (char *)0 == *ppcList) { return 0; } fFail = 0; uLen = 0; for (u = 0; (char *)0 != ppcList[u]; ++u) { uLen += MAXPATHLEN+1; } pcRet = pcMem = malloc((uLen|7)+1); /* We have a buffer that is long enough for any legal list, * fill it with the paths to the files given, but convert them * to absolute paths. */ for (u = 0; (char *)0 != ppcList[u]; ++u, pcMem += strlen(pcMem)) { register char *pcRepl, *pcChop; register unsigned uRepl; if (0 != u) { *pcMem++ = ':'; } pcComp = ppcList[u]; apcLook[0] = pcComp; apcLook[1] = (char *)0; fDotSlash = 0; while ('.' == pcComp[0] && '/' == pcComp[1]) { do ++pcComp; while ('/' == pcComp[0]); fDotSlash = 1; } if (fDotSlash) { if (-1 == stat(pcComp, &stDir)) { fprintf(stderr, "%s: stat: %s: %s\n", progname, pcComp, strerror(errno)); fFail = 2; continue; } } else if ((char *)0 == (pcComp = util_fsearch(apcLook, pcSearch))) { fprintf(stderr, "%s: %s: is not in our %s\n", progname, apcLook[0], acHxmdLib); fFail = 1; continue; } else if (-1 == stat(pcComp, &stDir)) { fprintf(stderr, "%s: stat: %s: %s\n", progname, pcComp, strerror(errno)); fFail = 2; continue; } else if (S_ISREG(stDir.st_mode) || S_ISFIFO(stDir.st_mode) || S_ISSOCK(stDir.st_mode)) { /* OK */ } else if (S_ISDIR(stDir.st_mode)) { /* build: comp.'/'.argv[0].pcSuffix.'\000' */ uRepl = strlen(pcComp)+1+strlen(progname)+strlen(pcSuffix)+1; pcRepl = malloc((uRepl|7)+1); snprintf(pcRepl, uRepl, "%s/%s%s", pcComp, progname, pcSuffix); ppcList[u] = pcRepl; if (-1 == stat(pcRepl, &stDir)) { fprintf(stderr, "%s: stat: %s: %s\n", progname, pcRepl, strerror(errno)); fFail = 3; } pcComp = pcRepl; } else if (S_ISCHR(stDir.st_mode)) { /* we hope it's /dev/null, not /dev/tty */ } else { fprintf(stderr, "%s: %s: not a plain file\n", progname, pcComp); fFail = 4; continue; } if ('/' == pcComp[0]) { (void)strcpy(pcMem, pcComp); pcMem += strlen(pcMem); continue; } (void)strcpy(pcMem, pcCacheCWD); /* The path search could pick "./file" or "../file" */ pcRepl = pcComp; while ('.' == pcComp[0] && ('/' == pcComp[1] || ('.' == pcComp[1] && '/' == pcComp[2]))) { if ('.' == *++pcComp) { ++pcComp; if ((char *)0 != (pcChop = strrchr(pcMem+1, '/'))) *pcChop = '\000'; } do { ++pcComp; } while ('/' == pcComp[0]); } (void)strcat(pcMem, "/"); (void)strcat(pcMem, pcComp); /* If our remapping is not the same file, backout to the * relative path. Must be symbolic link in the way, but * getcwd(3) should not return a symbolic link, we raced? */ if (-1 == stat(pcMem, &stFound) || stDir.st_ino != stFound.st_ino || stDir.st_dev != stFound.st_dev) { if ((char *)0 != strchr(pcDebug, 'M')) { fprintf(stderr, "%s: path compression failed (%s != %s), patched\n", progname, pcMem, pcRepl); } (void)strcpy(pcMem, pcRepl); } } if (0 != fFail) { return 1; } *pcMem = '\000'; *ppcOut = pcRet; return 0; } /* See if a file is included in the given list (ksb) * first check by name, then by inode/device. */ static int CheckDup(char *pcTarget, char **ppcIn) { register char *pc; register int i; auto struct stat stTry, stTarget; if ((char **)0 == ppcIn) { return 0; } for (i = 0; (char *)0 != (pc = ppcIn[i]); ++i) { if (pc == pcTarget || 0 == strcmp(pc, pcTarget)) return 1; } /* We can't tell by device it if doesn't exist or no perm */ if (-1 == stat(pcTarget, & stTarget)) { return 0; } for (i = 0; (char *)0 != (pc = ppcIn[i]); ++i) { if (-1 == stat(pc, & stTry)) continue; if (stTry.st_ino != stTarget.st_ino || stTry.st_dev != stTarget.st_dev) continue; return 1; } return 0; } /* Return a slot sequence with our temp files listed for cleanup. (ksb) * We need to make them in our tempdir so the Hxmd parent process can * rm them after the Child is done with them. */ static char * HostSlot(char *pcCache) { register int fd; register char *pcRet, *pcAdv; register int iBuild, i; auto PPM_BUF PPMConfigs, PPMZConfigs, PPMXConfigs; register char **ppcList, **ppcZList, **ppcXList; register char **ppcStdin; register MACRO_DEF *pMDDefault; iBuild = sizeof(apcMake)/sizeof(char *); if ((char *)0 == (pcRet = calloc(iBuild*(MAXPATHLEN+2), sizeof(char)))) { fprintf(stderr, "%s: calloc(%u,%u) %s\n", progname, (unsigned)2*(MAXPATHLEN+2), (unsigned)sizeof(char), strerror(errno)); return (char *)0; } pcAdv = pcRet; for (i = 0; i < iBuild; ++i) { snprintf(pcAdv, MAXPATHLEN, "%s/%sXXXXXX", pcCache, apcMake[i]); if (-1 == (fd = mkstemp(pcAdv))) { fprintf(stderr, "%s: mkstemp: %s: %s\n", progname, pcAdv, strerror(errno)); return (char *)0; } close(fd); HostDb.apctemp[i] = pcAdv; pcAdv += strlen(pcAdv)+1; } *pcAdv = '\000'; util_ppm_init(& PPMConfigs, sizeof(char *), 0); ppcList = BreakList(& PPMConfigs, pcConfigs, "configuration file", (char *)0); util_ppm_init(& PPMZConfigs, sizeof(char *), 0); ppcZList = BreakList(& PPMZConfigs, pcZeroConfig, "zero configuration", (char *)0); for (i = 0, pcAdv = (char *)0; (char **)0 != ppcZList && (char *)0 != ppcZList[i]; ++i) { if (HostDb.pcstdin != ppcZList[i]) continue; pcAdv = ppcZList[i]; break; } util_ppm_init(& PPMXConfigs, sizeof(char *), 0); ppcXList = BreakList(& PPMXConfigs, pcExConfig, "extra configuration", pcAdv); /* you _cannot_ need stdin before you get here */ if ((char **)0 != (ppcStdin = ReserveStdin((char **)0, "fetch"))) { if (-1 == (fd = open(HostDb.pcstdin, O_RDWR, 0600))) { fprintf(stderr, "%s: open: %s: %s\n", progname, HostDb.pcstdin, strerror(errno)); return (char *)0; } FdCopy(0, fd, "save stdin"); close(fd); *ppcStdin = HostDb.pcstdin; } if (MakeList(& PPMConfigs, &pcConfigs, ".cf") || MakeList(& PPMZConfigs, &pcZeroConfig, ".zf") || MakeList(& PPMXConfigs, &pcExConfig, ".xf")) { return (char *)0; } ResetMacros(); pMDDefault = CurrentMacros(); if ((char **)0 != ppcZList) { while ((char *)0 != (pcAdv = *ppcZList++)) { ReadConfig(pcAdv, !CheckDup(pcAdv, ppcXList)); pMDDefault = MergeMacros(CurrentMacros(), pMDDefault); ResetMacros(); } } if ((char **)0 != ppcList) { while ((char *)0 != (pcAdv = *ppcList++)) { ReadConfig(pcAdv, 1); ResetMacros(); } } if ((char **)0 != ppcXList) { while ((char *)0 != (pcAdv = *ppcXList++)) { ReadConfig(pcAdv, 0); ResetMacros(); } } if ((MACRO_DEF *)0 != pMDDefault) { register unsigned u; register META_HOST *pMH; pMH = (META_HOST *)util_ppm_size(& PPMHosts, 1); for (u = 0; (META_HOST *)0 != pMH && u < HostDb.ucount; ++u) { pMH[u].pMD = MergeMacros(pMH[u].pMD, pMDDefault); } } return pcRet; } /* Output the m4 macros to allow a recursive call to hxmd, -Z and -C/-X. (ksb) * We figure one can get HOST when you need it, or as for other spells. */ void RecurBanner(FILE *fp) { if ((char *)0 != pcZeroConfig) { fprintf(fp, "define(`%sOPT_Z',`%s')", pcXM4Define, pcZeroConfig); } if ((char *)0 != pcConfigs) { fprintf(fp, "define(`%sOPT_C',`%s')", pcXM4Define, pcConfigs); } if ((char *)0 != pcExConfig) { fprintf(fp, "define(`%sOPT_X',`%s')", pcXM4Define, pcExConfig); } fprintf(fp, "define(`%sU_COUNT',`%u')dnl\n", pcXM4Define, HostDb.ucount); if (0 != HostDb.uselected) { if ((char *)0 != pcOutMerge) { fprintf(fp, "define(`%sU_MERGED',`%s')", pcXM4Define, HostDb.pcfilter); } fprintf(fp, "define(`%sU_SELECTED',`%u')dnl\n", pcXM4Define, HostDb.uselected); } } /* Output a macro define from the configuration file (ksb) */ static int M4Define(const char *pcName, const char *pcValue, void *fpOut) { if ((char *)0 != pcValue) { fprintf((FILE *)fpOut, "define(`%s',`%s')", pcName, pcValue); } return 0; } /* Output all the defines for a host to the m4 process, then the (ksb) * caller adds an "include(argv[x])dnl", some other text. */ static void HostDefs(FILE *fp, META_HOST *pMH) { RecurBanner(fp); if ((META_HOST *)0 == pMH) { fprintf(fp, "dnl null meta host pointer\n"); return; } if (-1 != pMH->uu) { fprintf(fp, "define(`%sU',%u)", pcXM4Define, (unsigned)pMH->uu); } fprintf(fp, "define(`%sB',%u)", pcXM4Define, pMH->udefs); ScanMacros(pMH->pMD, M4Define, (void *)fp); fprintf(fp, "dnl\n"); } static int M4Pushdef(const char *pcName, const char *pcValue, void *fpOut) { if ((char *)0 != pcValue) fprintf((FILE *)fpOut, "pushdef(`%s',`%s')", pcName, pcValue); return 0; } /* Hackish, I just wanted to list the macros to popdef (ksb) * but lame a hades Linux m4 only pops a single define. Sigh. */ static int M4ListMacro(const char *pcName, const char *pcValue, void *fpOut) { if ((char *)0 != pcValue) { #if M4_POPDEF_MANY fprintf((FILE *)fpOut, ","); #else fprintf((FILE *)fpOut, ")popdef("); #endif fprintf((FILE *)fpOut, "`%s'", pcName); } return 0; } static META_HOST *pMHOrder; /* Pick up the next host. The next one that has not been deselected (ksb) * by -B, -E, or -G below. */ static META_HOST * NextHost() { register META_HOST *pMHRet; if ((META_HOST *)0 != (pMHRet = pMHOrder)) { pMHOrder = pMHOrder->pMHnext; } return pMHRet; } /* We are accessing a cache and need a target to request (ksb) */ static const char * HostTarget(META_HOST *pMHFor) { return MacroValue(pcHOST, pMHFor->pMD); } /* Drop a common define-set, action, undefine-set out for the host (ksb) */ void BuildM4(FILE *fp, char **ppcBody, META_HOST *pMHFor, char *pcPre, char *pcPost) { fprintf(fp, "pushdef(`%sB',%u)", pcXM4Define, pMHFor->udefs); ScanMacros(pMHFor->pMD, M4Pushdef, (void *)fp); if ((char *)0 != pcPre) fprintf(fp, "dnl\n%s", pcPre); else fprintf(fp, "dnl\n"); for (/*param*/; (char *)0 != *ppcBody; ++ppcBody) fprintf(fp, "%s\n", *ppcBody); /* The blank line on the end of this is ignored when we import -- ksb */ if ((char *)0 != pcPost) fprintf(fp, "%s\n", pcPost); fprintf(fp, "popdef(`%sB'", pcXM4Define); ScanMacros(pMHFor->pMD, M4ListMacro, (void *)fp); fprintf(fp, ")dnl\n"); } /* Output a macro value under our configuration file quote rules, (ksb) * when pcValue has a default value we'll use that for undefined. * Don't pass pcMacro as a null pointer, please. */ static void DropMacro(FILE *fpDrop, char *pcMacro, MACRO_DEF *pMD, const char *pcValue) { register const char *pcCursor; register int c, iM4Qs, iCQs, iIFS, iNonPrint, fM4Ever; if ((MACRO_DEF *)0 != pMD && (char *)0 != (pcCursor = MacroValue(pcMacro, pMD))) { pcValue = pcCursor; } else if ((char *)0 == pcValue) { fprintf(fpDrop, acScoped); return; } fM4Ever = 0; iM4Qs = iIFS = iCQs = iNonPrint = 0; for (pcCursor = pcValue; '\000' != (c = *pcCursor++); /* nada */) { if (isspace(c) || '=' == c || '#' == c) ++iIFS; else if (!isprint(c)) ++iNonPrint; else if ('\'' == c) fM4Ever = 1, --iM4Qs; else if ('`' == c) fM4Ever = 1, ++iM4Qs; else if ('\"' == c || '\\' == c) ++iCQs; } if ('\000' == pcValue[0] || ('.' == pcValue[0] && '\000' == pcValue[1])) { fprintf(fpDrop, "`%s\'", pcValue); } else if (pcValue[0] != '.' && !fM4Ever && 0 == iCQs && 0 == iIFS && 0 == iNonPrint) { fprintf(fpDrop, "%s", pcValue); } else if (0 == iM4Qs && 0 == iNonPrint) { fprintf(fpDrop, "`%s\'", pcValue); } else if (0 == iCQs && 0 == iNonPrint) { fprintf(fpDrop, "\"%s\"", pcValue); } else { fputc('\"', fpDrop); for (pcCursor = pcValue; '\000' != (c = *pcCursor++); /* nada */) { switch (c) { case '\n': fputc('\\', fpDrop); c = 'n'; break; case '\t': fputc('\\', fpDrop); c = 't'; break; case '\b': fputc('\\', fpDrop); c = 'b'; break; case '\r': fputc('\\', fpDrop); c = 'r'; break; case '\f': fputc('\\', fpDrop); c = 'f'; break; case '\v': fputc('\\', fpDrop); c = 'v'; break; case '\"': case '\'': case '\\': fputc('\\', fpDrop); break; default: if (isprint(c)) break; fprintf(fpDrop, "\\%03o", (unsigned)c); continue; } putc(c, fpDrop); } fputc('\"', fpDrop); } } /* A merged configuration file is used to recurse or chain to (ksb) * another instance of hxmd (or msrc). The -o option builds one * and the _U_MERGED macro presents the filename. */ static void BuildMerge(const char *pcOutput, char *pcCurMerge, META_HOST *pMHList) { register FILE *fpDrop; register char *pcScan, *pcCur, *pcSep1, **ppc, *pcKeyMacro; register int c, fSkip; pcKeyMacro = pcHOST; if ((FILE *)0 == (fpDrop = fopen(pcOutput, "w"))) { fprintf(stderr, "%s: fopen: %s: %s\n", progname, pcOutput, strerror(errno)); exit(EX_OSERR); } /* output the -D macros */ for (ppc = util_ppm_size(&PPMAutoDef, 0); (char *)0 != (pcScan = *ppc); ++ppc) { fSkip = !(isalnum(*pcScan) || '_' == *pcScan); if (fSkip) { fputc('#', fpDrop); ++pcScan; } while (isalnum(*pcScan) || '_' == *pcScan) { fputc(*pcScan++, fpDrop); } if (fSkip) { fputc('\n', fpDrop); continue; } while (isspace(*pcScan)) { ++pcScan; } if ('\000' == *pcScan) { pcScan = "="; } fputc(*pcScan++, fpDrop); DropMacro(fpDrop, "-D", (MACRO_DEF *)0, pcScan); fputc('\n', fpDrop); } /* If they asked for the key macro in the header don't replicate it for * them. They are picking the order of the headers, I guess. (ksb) */ for (pcScan = strstr(pcCurMerge, pcKeyMacro); (char *)0 != pcScan; pcScan = strstr(pcScan+1, pcKeyMacro)) { if (pcScan != pcCurMerge && !isspace(pcScan[-1])) continue; if ('\000' != (c = pcScan[strlen(pcKeyMacro)]) && !isspace(c)) continue; pcKeyMacro = (char *)0; break; } /* Render the head for -o "HOST:COLOR" as HOST_COLOR. This lets * us rename a macro "@LOC" -> "_LOC", later (assuming that `@' * can be removed from the _LOC string) a compare of LOC and _LOC * with -C + -X and/or -E is easy. In the header map non-macro * chars to `_' and prefix numeric macro names with `d'. (ksb) */ fprintf(fpDrop, "%%"); pcSep1 = " "; if ((char *)0 != pcKeyMacro) { fprintf(fpDrop, "%s", pcKeyMacro); } else { ++pcSep1; } if ('\000' != pcCurMerge[0]) { register int fMac; if (isspace(*pcCurMerge) && '\000' != *pcSep1) ++pcSep1; fprintf(fpDrop, "%s", pcSep1); fMac = 0; for (pcCur = pcCurMerge; '\000' != (c = *pcCur); ++pcCur) { if (isalpha(c) || '_' == c) { fMac = 1; putc(c, fpDrop); continue; } if (isdigit(c)) { if (0 == fMac) putc('d', fpDrop); putc(c, fpDrop); fMac = 1; continue; } if (isspace(c)) { putc(c, fpDrop); fMac = 0; continue; } if (2 != fMac) putc('_', fpDrop); fMac = 2; } } else if ('\000' != *pcSep1) { ++pcSep1; } fputc('\n', fpDrop); for (/*param*/; (META_HOST *)0 != pMHList; pMHList = pMHList->pMHnext) { if ((char *)0 != pcKeyMacro) { DropMacro(fpDrop, pcKeyMacro, pMHList->pMD, (char *)0); } fprintf(fpDrop, "%s", pcSep1); for (pcCur = pcScan = pcCurMerge; '\000' != *pcScan; ++pcScan) { c = *pcScan; if (isalnum(c) || '_' == c) { continue; } if (pcCur != pcScan) { *pcScan = '\000'; DropMacro(fpDrop, pcCur, pMHList->pMD, (char *)0); *pcScan = c; } pcCur = pcScan+1; putc(c, fpDrop); } if (pcScan != pcCur) { DropMacro(fpDrop, pcCur, pMHList->pMD, (char *)0); } fputc('\n', fpDrop); } fclose(fpDrop); } /* Figure out which hosts to select from the whole list, (ksb) * then return the first one. */ static META_HOST * HostInit(char **ppcM4) { register char **ppcBool, **ppc, *pc; register unsigned u; register META_HOST *pMHAll, **ppMHTail; register FILE *fpTempt; auto char *apcSource[2]; auto char *pcPrefix, *pcSuffix, **ppcGuards; auto char *apcDefault[2]; auto size_t wTotal, wLen; auto int wExit; if ((FILE *)0 == (fpTempt = fopen(HostDb.pcscratch, "w"))) { fprintf(stderr, "%s: fopen: %s: %s\n", progname, HostDb.pcscratch, strerror(errno)); return (META_HOST *)0; } RecurBanner(fpTempt); for (ppc = util_ppm_size(pPPMSpells, 0); (char *)0 != *ppc; ++ppc) { fprintf(fpTempt, "%s\n", *ppc); } pcPrefix = (char *)util_ppm_size(& PPMWardPre, 0); pcSuffix = (char *)util_ppm_size(& PPMWardFix, 0); ppcGuards = (char **)util_ppm_size(pPPMGuard, 0); if ((char *)0 == ppcGuards[0]) { apcDefault[0] = pcHOST; apcDefault[1] = (char *)0; ppcGuards = apcDefault; } ppcBool = (char **)util_ppm_size(& PPMBooleans, 0); pMHAll = (META_HOST *)util_ppm_size(& PPMHosts, 0); for (u = 0; u < HostDb.ucount; ++u) { for (ppc = ppcBool; (char *)0 != (pc = *ppc); ++ppc) { register int fComplement, fTruth; fComplement = ('!' == *pc); if (fComplement) ++pc; if (isdigit(*pc)) fTruth = atoi(pc) == pMHAll[u].udefs; else fTruth = (char *)0 != MacroValue(pc, pMHAll[u].pMD); if (0 == (fTruth ^ fComplement)) break; } if ((char *)0 != pc) { if ((char *)0 != strchr(pcDebug, 'B')) { fprintf(stderr, "%s: %s rejected by -B %s\n", progname, pMHAll[u].pchost, pc); } pMHAll[u].iline = 0; continue; } BuildM4(fpTempt, ppcGuards, & pMHAll[u], pcPrefix, pcSuffix); } fclose(fpTempt); if ((char *)0 != strchr(pcDebug, 'G')) { fprintf(stderr, "%s: host expressions in \"%s\" (pausing 5 second)\n", progname, HostDb.pcscratch); sleep(5); } apcSource[0] = HostDb.pcscratch; apcSource[1] = (char *)0; /* We check the return code here, if this m4 failed we * don't want to live. */ SetCurPhase("selection"); if (0 != (wExit = FillSlot(HostDb.pcfilter, apcSource, ppcM4, (META_HOST *)0, (const int *)0, (const char *)0))) { fprintf(stderr, "%s: guard %s: failed with code %d\n", progname, ppcM4[0], WEXITSTATUS(wExit)); exit(wExit); } SetCurPhase((char *)0); /* This is also where we get the order for the host, use divert * to migrate hosts up or down the list. */ ppMHTail = & pMHOrder; if ((FILE *)0 == (fpTempt = fopen(HostDb.pcfilter, "r"))) { fprintf(stderr, "%s: fopen: %s: %s\n", progname, HostDb.pcfilter, strerror(errno)); exit(EX_OSERR); } /* Read in a line as a hostname, trim off white-space and look for * it in the config host list. If not found skip it, also skip * if on the list already or fobidden by -B. */ while ((char *)0 != (pc = fgetln(fpTempt, & wTotal))) { register META_HOST *pMHFound; register char *pcCur; for (/*go*/; 0 < wTotal; wTotal -= wLen){ while (0 < wTotal && isspace(*pc)) { ++pc, --wTotal; } pcCur = pc; for (wLen = 0; wLen < wTotal; ++pc, ++wLen) { if (isspace(*pc)) break; } if (0 == wLen) { break; } if ((META_HOST *)0 == (pMHFound = FindHost(pcCur, wLen))) { if ((char *)0 != strchr(pcDebug, 'G')) { fprintf(stderr, "%s: -G raised \"%.*s\", no such key\n", progname, (int)wLen, pcCur); } continue; } if ((META_HOST *)0 != pMHFound->pMHnext || ppMHTail == & pMHFound->pMHnext) { continue; } if (0 == pMHFound->iline) { if ((char *)0 != strchr(pcDebug, 'B')) { fprintf(stderr, "%s: -B squelched %s from guard list\n", progname, pMHFound->pchost); } continue; } *ppMHTail = pMHFound; ppMHTail = & pMHFound->pMHnext; pMHFound->uu = HostDb.uselected++; } } fclose(fpTempt); *ppMHTail = (META_HOST *)0; if ((char *)0 != pcOutMerge) { BuildMerge(HostDb.pcfilter, pcOutMerge, pMHOrder); } return NextHost(); } %%