#!mkcmd
# $Id: efmd.m,v 1.21 2012/09/03 19:10:59 ksb Exp $
# Process a list file (string) against selected hosts to stdout
#
# $Compile: ${mkcmd:-mkcmd} -I../msrc -I../hxmd -G -n efmd %f
comment "%c %kCompile: $%{cc-cc%} -g -Wall -o %%[f.-$] %%f %k%k"
from '<sys/types.h>'
from '<sys/param.h>'
from '<sys/socket.h>'
from '<sys/mman.h>'
from '<sys/wait.h>'
from '<fcntl.h>'
from '<sys/time.h>'
from '<sys/uio.h>'
from '<string.h>'
from '<unistd.h>'
from '<errno.h>'
from '<sys/stat.h>'
from '<signal.h>'
from '<sysexits.h>'
from '<time.h>'
from '<netdb.h>'


basename "efmd" ""

from '"machine.h"'
require "util_errno.m" "util_ppm.m" "util_pipefit.m"

require "std_help.m" "std_version.m"

augment action 'V' {
	user "Version();"
}
augment boolean 'v' {
	hidden
}
%i
static const char rcsid[] =
	"$Id: efmd.m,v 1.21 2012/09/03 19:10:59 ksb Exp $";

#if !defined(DEF_HXMD_LIB)
#define DEF_HXMD_LIB    ".:/usr/local/lib/hxmd:/usr/local/lib/distrib"
#endif

static const char *apcM4Path[] = {
	"/usr/local/bin/m4",
#if defined(M4_SYS_PATH)
	M4_SYS_PATH,
#endif
	"/usr/bin/m4",
	"/usr/ccs/bin/m4", // Fall back to a crufty old m4 on Solaris --jad
	(char *)0
};
static const char *pcM4Found = (const char *)0;

/* hxmd forces slot-0 to be +x, we don't
 */
#undef SLOT_0_ORBITS
#if !defined(HX_BYTES_PER_FILE)
#define	HX_BYTES_PER_FILE	(MAXPATHLEN+4)
#endif

static const char
	acHxmdLib[] = "HXMD_LIB";
static char
	acHxmdSearch[] = DEF_HXMD_LIB,
	acMySpace[HX_BYTES_PER_FILE];
%%
function 'DIU' {
	named "AddSwitch"
	param "m4-opts"
	help "pass an option down to m4"
}
type "argv" 'j' {
	named "pPPMM4Prep"
	param "m4prep"
	help "include specified files in every m4 process\'s arguments"
}
char* 'd' {
	named "pcDebug"
	param "flags"
	help "debug flags we pass on to m4 ('?' for help)"
}
# take arguments mostly like hxmd does
integer 'F' {
	named 'iCmdLits'
	init "1"
	param 'literal'
	help "the number of parameters which are literal text (default %qi)"
}
type "argv" 'T' {
	named "pPPMTop"
	param "header"
	help "m4 commands included at the top of the markup"
}
boolean 'L' {
	exclude 'FnT'
	named "fListOnly"
	help "output only the selected host list"
	list {
		hidden named "List"
		param ""
		help "ignored parameters"
	}
}

# We need to be compatible with any local HXMD macro munging
char* 'M' {
	hidden named "pcXM4Define"
	init '"HXMD_"'
	help "macro filename prefix for m4 cross file references"
}
int named "bHasRoot" {
	init dynamic "0 == getuid()"
}

after "AfterGlow(argc, argv);"
list {
	named "List"
	param "arguments"
	help "attribute names or files to map and extract"
}
init "EfmdStart();"
%c
static PPM_BUF PPMM4;
static unsigned int uM4c = 0;

/* Set up the standard zero configuration hook, start the m4 args and	(ksb)
 * instance our temporary name-space under $TMPDIR.  Then $(which m4).
 */
static void
EfmdStart()
{
	register char **ppcInit, *pcEnvTemp;
	register const char **ppcScan;
	auto char acTemp[MAXPATHLEN+4];
	auto struct stat stCheck;

	if ((char *)0 == pcZeroConfig ? (0 == strcmp("-", progname) || 0 == strcmp("stdin", progname)) : (0 == strcmp("-", pcZeroConfig))) {
		pcZeroConfig = "-";
	} else if ((char *)0 == pcZeroConfig && 0 != strcmp("efmd", progname)) {
		snprintf(acTemp, sizeof(acTemp)-1, "%s.cf", progname);
		pcZeroConfig = strdup(acTemp);
	}

	/* We might change our minds later, but assume a -o option now,
	 * and include the printf format to install it later. /fGaveMerge/
	 */
	util_ppm_init(& PPMM4, sizeof(char *), 256);
	ppcInit = util_ppm_size(& PPMM4, 256);
	uM4c = 0;
	ppcInit[uM4c++] = "m4";
	ppcInit[uM4c++] = "-D";
	ppcInit[uM4c++] = "%sU_MERGED=%s";
	ppcInit[uM4c] = (char *)0;

	if ((char *)0 == (pcEnvTemp = getenv("TMPDIR")) || '\000' == *pcEnvTemp) {
		pcEnvTemp = "/tmp";
	}
	snprintf(acMySpace, MAXPATHLEN, "%s/efmdXXXXXX", pcEnvTemp);

	/* Find the m4 binary the Customer wants to use: from the environment
	 * or the first one in our internal list.  N.B. If you set this to a
	 * script that runs m4 on stdin then pipes the output to a shell you
	 * may used HXMD_U_MERGED in the script, else you shan't. -- ksb
	 */
	pcM4Found = getenv("M4_PATH");
	for (ppcScan = apcM4Path; (const char *)0 == pcM4Found && (const char *)0 != *ppcScan; ++ppcScan) {
		if (-1 == stat(*ppcScan, &stCheck) || 0 == (stCheck.st_mode & 0111))
			continue;
		pcM4Found = *ppcScan;
	}
	if ((const char *)0 == pcM4Found) {
		pcM4Found = ppcInit[0];
	}
}

/* We output almost the same version block as hxmd, because need	(ksb)
 * to be in-sync with hxmd, and someone might have to debug that.
 */
static void
Version()
{
	register char *pcEnvSearch, *pcFrom, *pcSearch;
	auto struct stat stCheck;

	printf("%s:  slot code: %s\n", progname, rcs_slot);
	printf("%s:  hostdb code: %s\n", progname, rcs_hostdb);
	printf("%s:  evector code: %s\n", progname, rcs_evector);
	printf("%s: default column headers: %%%s\n", progname, pcHOST);
	printf("%s: m4 path: %s", progname, pcM4Found);
	if (-1 == stat(pcM4Found, &stCheck)) {
		printf(" [nonexistant]");
	} else if (0 == (stCheck.st_mode & 0111)) {
		printf(" [execute not set]");
	}
	printf("\n%s: auto-define prefix for B,OPT_{C,X,Z},U,U_MERGED,U_SELECTED: %s\n", progname, pcXM4Define);
	printf("%s: template for private file space: %s\n", progname, acMySpace);
	if ((char *)0 != (pcEnvSearch = getenv(acHxmdLib)) && '\000' != *pcEnvSearch) {
		pcFrom = "environment";
		pcSearch = pcEnvSearch;
	} else {
		pcFrom = "default";
		pcSearch = pcHostSearch;
	}
	printf("%s: search list from $%s\n", progname, acHxmdLib);
	printf("%s: %s search list: %s\n", progname, pcFrom, pcSearch);
	printf("%s: %s double-dash directory: %s\n", progname, pcFrom, acTilde);
	printf("%s: cache recipe file: %s\n", progname, acDownRecipe);
}

#if !defined(HOST_NAME_MAX)
#define	HOST_NAME_MAX	(MAXHOSTNAMELEN)
#endif

/* Pass a single letter switch or switch with a param down to m4.	(ksb)
 * Copied from mmsrc's version, since that's closest.
 */
static void
AddSwitch(int iOpt, char *pcArg)
{
	register char **ppc;

	/* Tap -D option stream for some clues we need in the merged file
	 */
	if ('D' == iOpt && (char *)0 != pcArg) {
		register unsigned u;
		register char *pcReal;

		/* Eat any -D!FOO's exclaimation mark
		 */
		pcReal = pcArg;
		if ('_' != *pcArg && !isalnum(*pcArg)) {
			++pcReal;
		}
		ppc = util_ppm_size(& PPMAutoDef, 0);
		for (u = 0; (char *)0 != ppc[u]; ++u) {
			/* search for end */
		}
		ppc = util_ppm_size(& PPMAutoDef, ((u+1)|15)+1);
		ppc[u++] = pcArg;
		ppc[u] = (char *)0;
		pcArg = pcReal;
	}
	ppc = util_ppm_size(& PPMM4, (uM4c|7)+9);
	if ('\000' != iOpt) {
		ppc[uM4c] = strdup("-_");
		ppc[uM4c++][1] = iOpt;
	}
	if ((char *)0 != pcArg) {
		ppc[uM4c++] = ('I' == iOpt && '-' == pcArg[0] && '-' == pcArg[1] && '\000' == pcArg[2]) ? acTilde : pcArg;
	}
	ppc[uM4c] = (char *)0;
}

/* Default missing data, debug level create temp space pattern	(ksb)
 */
static void
AfterGlow(int argc, char **argv)
{
	if ((char *)0 != pcDebug) {
		AddSwitch('d', pcDebug);
	} else {
		pcDebug = "";
	}
	fVerbose = 0;
	if ((char *)0 != strchr(pcDebug, '?')) {
		printf("%s: -d [?]*[aceflqtxV]*\n?\tdisplay these options and exit\nall others are provided by m4\n", progname);
		exit(0);
	}
	if ((char *)0 == mkdtemp(acMySpace)) {
		fprintf(stderr, "%s: mkdtemp: %s: %s\n", progname, acMySpace, strerror(errno));
		exit(EX_TEMPFAIL);
	}
}

/* We just process the command-line params once for each HOST		(ksb)
 * selected, and output the stream to m4 as a filter.  What you
 * do with the data is up to you.  This replaces "distrib -E".
 */
static void
List(int argc, char **argv)
{
	register int i;
	register char *pcFound;
	auto char **ppcM4, **ppcPrep, *pcHostSlot, *apcLook[2], **ppc, **ppcIsCache;
	auto const char *pcRecipe;
	auto int wExit;
	auto pid_t wM4, wWait;
	auto META_HOST *pMHAll;
	auto unsigned uLeftLits, uRightLits, uRightBegin;
	auto char acTemp[MAXPATHLEN+4];
	extern char **environ;

	/* Build the M4 phase buffer for HXMD_PHASE=(selection|%d|cmd),
	 */
	ppc = util_ppm_size(& PPMM4, (uM4c|7)+9);
	ppc[uM4c++] = strdup("-D");
	ppc[uM4c++] = SetupPhase("PHASE", 64);
	SetCurPhase("selection");

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

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

	uRightBegin = argc;
	uLeftLits = uRightLits = 0;
	if (0 <= iCmdLits) {
		uLeftLits = iCmdLits;
	} else {
		uRightLits = -iCmdLits;
		uRightBegin -= uRightLits;
	}
	if (!fListOnly && argc < (i = (uLeftLits+uRightLits))) {
		fprintf(stderr, "%s: not enough parameters for literals (%d < %u)\n", progname, argc, i);
		exit(EX_USAGE);
	}

	if ((char *)0 == (pcHostSlot = HostSlot(acMySpace))) {
		exit(EX_DATAERR);
	}

	/* The common hxmd file search mnemonic -- to me
	 */
	ppcIsCache = (char **)calloc((argc|3)+1, sizeof(char *));
	wExit = 0;
	apcLook[1] = (char *)0;
	pcRecipe = (char *)0;
	for (i = 0; i < argc; ++i) {
		auto struct stat stCheck;
		register size_t iLen;

		ppcIsCache[i] = (char *)0;
		if (i < uLeftLits || i >= uRightBegin) {
			continue;
		}
		if ('-' == argv[i][0] && '\000' == argv[i][1]) {
			ReserveStdin(& argv[i], "files");
			continue;
		}
		apcLook[0] = argv[i];
		if ('.' == argv[i][0] && '/' == argv[i][1]) {
			/* explicity do not search for file */
		} else if ((char *)0 == (pcFound = util_fsearch(apcLook, getenv(acHxmdLib)))) {
			fprintf(stderr, "%s: %s: cannot find file\n", progname, argv[i]);
			wExit = EX_NOINPUT;
		} else {
			argv[i] = pcFound;
		}
		if (0 != stat(argv[i], & stCheck)) {
			fprintf(stderr, "%s: stat: %s: %s\n", progname, argv[i], strerror(errno));
			exit(EX_NOINPUT);
		}
		if (S_ISFIFO(stCheck.st_mode) || S_ISREG(stCheck.st_mode)) {
			continue;
		}
		if (!S_ISDIR(stCheck.st_mode)) {
			fprintf(stderr, "%s: %s: must a FIFO, regular file, or cache directory\n", progname, argv[i]);
			exit(EX_DATAERR);
		}
		snprintf(acTemp, sizeof(acTemp), "%s/%s", argv[i], acDownRecipe);
		if ((char *)0 == (argv[i] = strdup(acTemp))) {
			fprintf(stderr, "%s: strdup: %s\n", progname, strerror(errno));
			exit(EX_TEMPFAIL);
		}
		if (0 != stat(argv[i] , &stCheck)) {
			fprintf(stderr, "%s: stat: %s: %s\n", progname, argv[i], strerror(errno));
			exit(EX_DATAERR);
		}
		if (!S_ISFIFO(stCheck.st_mode) && !S_ISREG(stCheck.st_mode)) {
			fprintf(stderr, "%s: %s: must be a regular file or FIFO\n", progname, argv[i]);
			exit(EX_DATAERR);
		}

		/* We must get "./rXXXXXX/makeXXXXXX" at least, if $TMPDIR
		 * is set to dot.
		 */
		pcRecipe = FindCache("make", pcHostSlot);
		iLen = strlen(pcRecipe); /* make room for extra \000 */
		if (iLen < 22) {
			fprintf(stderr, "%s: hostdb.m is broken\n", progname);
			exit(EX_SOFTWARE);
		}
		if ((char *)0 == (ppcIsCache[i] = calloc((7|(iLen+2))+1, sizeof(char)))) {
			fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno));
			exit(EX_OSERR);
		}
		/* stuff/make______\000  ->  stuff/makeXXXXXX\000\000
		 * calloc put the extra \000 on the end for us, for FillSlot.
		 */
		(void)strcpy(ppcIsCache[i], pcRecipe);
		(void)strcpy(ppcIsCache[i]+(iLen-6), "XXXXXX");
	}
	if (0 != wExit) {
		exit(wExit);
	}

	/* Read configuration data, first the -C/-X/-Z files.
	 * Either replace the format with the payload, or shift off
	 * the forced -D for the -o file.  Then produce the host list.
	 */
	ppcM4 = util_ppm_size(& PPMM4, 0);
	if (fGaveMerge) {
		register char *pcMem;
		register const char *pcMerged;
		register size_t i;

		/* We know that hostdb uses the "filter" mktemp for the merge
		 */
		pcMerged = FindCache("filter", pcHostSlot);
		i = (15|(strlen(ppcM4[2])+strlen(pcXM4Define)+strlen(pcMerged)))+1;
		if ((char *)0 == (pcMem = malloc(i))) {
			fprintf(stderr, "%s: malloc: %ld: %s\n", progname, (long)i, strerror(errno));
			exit(EX_OSERR);
		}
		snprintf(pcMem, i, ppcM4[2], pcXM4Define, pcMerged);
		ppcM4[2] = pcMem;
	} else {
		ppcM4[2] = ppcM4[0];
		ppcM4 += 2;
	}
	pMHAll = HostInit(ppcM4);
	if (fListOnly) {
		for (; (META_HOST *)0 != pMHAll; pMHAll = pMHAll->pMHnext) {
			printf("%s\n", pMHAll->pchost);
		}
		exit(0);
	}

	/* Get our m4 processing setup al-la distrib -E
	 */
	SetCurPhase("output");
	if (-1 == (wM4 = PipeFit(pcM4Found, ppcM4, environ, 1))) {
		exit(EX_SOFTWARE);
	}
	if (!fExec) {
		register char *pc, *pcEq;
		register int iKind = '.';

		for (ppc = ppcM4; (char *)0 != (pc = *ppc); ++ppc) {
			if ('-' == pc[0]) {
				iKind = pc[1];
				continue;
			}
			switch (iKind) {
			case 'D':
				if ((char *)0 == (pcEq = strchr(pc, '=')))
					printf("define(`%s')dnl\n", pc);
				else
					printf("define(`%.*s',`%s')dnl\n", (int)(pcEq-pc), pc, pcEq+1);
				break;
			case 'I':
				printf("dnl add include path `%s'\n", pc);
				break;
			case 'U':
				printf("undefine(`%s')dnl\n", pc);
				break;
			case 'f':
				printf("include(`%s')dnl\n", pc);
				break;
			case '.':
			default:
				break;
			}
			iKind = 'f';
		}
		printf("dnl-n set\n");
	}
	RecurBanner(stdout);
	for (ppcPrep = util_ppm_size(pPPMTop, 0); (char *)0 != *ppcPrep; ++ppcPrep) {
		printf("%s\n", *ppcPrep);
	}
	for (; (META_HOST *)0 != pMHAll; pMHAll = pMHAll->pMHnext) {
		register int cc, fd;
		auto char acBuf[16384];
		auto int wRecipe;

		if (-1 != pMHAll->uu) {
			fprintf(stdout, "pushdef(`%sU',%u)", pcXM4Define, (unsigned)pMHAll->uu);
		}
		fprintf(stdout, "pushdef(`%sB',%u)", pcXM4Define, pMHAll->udefs);
		ScanMacros(pMHAll->pMD, M4Pushdef, (void *)stdout);
		fprintf(stdout, "dnl\n");
		for (i = 0; i < argc; ++i) {
			if (i < uLeftLits || i >= uRightBegin) {
				printf("%s\n", argv[i]);
				continue;
			}
			if ((char *)0 != ppcIsCache[i]) {
				register int fd;
				auto int aiFake[2];

				(void)strcpy(acTemp, ppcIsCache[i]);
				if (-1 == (fd = mkstemp(acTemp))) {
					fprintf(stderr, "%s: mkstemp: %s: %s\n", progname, acTemp, strerror(errno));
					exit(EX_OSERR);
				}
				close(fd);

				aiFake[0] = 1;
				aiFake[1] = 0;
				if (0 != (wRecipe = FillSlot(acTemp, &argv[i], ppcM4, pMHAll, aiFake, pcRecipe))) {
					fprintf(stderr, "%s: %s: m4 failed with %d\n", progname, argv[i], wRecipe);
					exit(wRecipe);
				}
				fprintf(stdout, "paste(`%s')syscmd(`rm -f %s')dnl\n", acTemp, acTemp);
				fflush(stdout);
				continue;
			}
			if (-1 == (fd = open(argv[i], O_RDONLY, 0000))) {
				close(1);
				fprintf(stderr, "%s: open: %s: %s\n", progname, argv[i], strerror(errno));
				exit(EX_NOINPUT);
			}
			while (0 < (cc = read(fd, acBuf, sizeof(acBuf)))) {
				fwrite(acBuf, 1, cc, stdout);
			}
			close(fd);
		}
		if (-1 != pMHAll->uu) {
			fprintf(stdout, "popdef(`%sU')", pcXM4Define);
		}
		fprintf(stdout, "popdef(`%sB'", pcXM4Define);
		ScanMacros(pMHAll->pMD, M4ListMacro, (void *)stdout);
		fprintf(stdout, ")dnl\n");
	}
	fflush(stdout);
	fclose(stdout);
	(void)open("/dev/null", O_WRONLY, 0000);
	wExit = EX_UNAVAILABLE << 8;
	while (-1 != (wWait = wait(&wExit))) {
		if (wWait == wM4)
			break;
	}
	/* Remove the temp files, they could be using the merged file so
	 * wait until they exit.
	 */
	CleanSlot(pcHostSlot);
	(void)rmdir(acMySpace);
	exit(WEXITSTATUS(wExit));
}
%%
