
/***************************************************************************
 *                                                                         *
 *                                Porting Note                             *
 *                                                                         *
 * Add the value of BOOTSTRAPCFLAGS to the cpp_argv table so that it will  *
 * be passed to the template file.                                         *
 *                                                                         *
 ***************************************************************************/
/*
 *
Copyright (c) 1985, 1986, 1987, 1998 The Open Group

Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation.

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of The Open Group shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from The Open Group.
 *
 * Original Author:
 *	Todd Brunhoff
 *	Tektronix, inc.
 *	While a guest engineer at Project Athena, MIT
 *
 * imake: the include-make program.
 *
 * Usage: imake [-Idir] [-Ddefine] [-T template] [-f imakefile ] [-C Imakefile.c ] [-s] [-e] [-v] [make flags]
 *
 * Imake takes a template file (Imake.tmpl) and a prototype (Imakefile)
 * and runs cpp on them producing a Makefile.  It then optionally runs make
 * on the Makefile.
 * Options:
 *		-D	define.  Same as cpp -D argument.
 *		-U	undefine.  Same as cpp -U argument.
 *		-W	warning.  Same as cpp -W argument.
 *		-I	Include directory.  Same as cpp -I argument.
 *		-T	template.  Designate a template other
 *			than Imake.tmpl
 *		-f	specify the Imakefile file
 *		-C	specify the name to use instead of Imakefile.c
 *		-s[F]	show.  Show the produced makefile on the standard
 *			output.  Make is not run is this case.  If a file
 *			argument is provided, the output is placed there.
 *		-e[F]	execute instead of show; optionally name Makefile F
 *		-v	verbose.  Show the make command line executed.
 *
 * Environment variables:
 *
 *		IMAKEINCLUDE	Include directory to use in addition to "."
 *		IMAKECPP	Cpp to use instead of /lib/cpp
 *		IMAKEMAKE	make program to use other than what is
 *				found by searching the $PATH variable.
 * Other features:
 *	imake reads the entire cpp output into memory and then scans it
 *	for occurences of "@@".  If it encounters them, it replaces it with
 *	a newline.  It also trims any trailing white space on output lines
 *	(because make gets upset at them).  This helps when cpp expands
 *	multi-line macros but you want them to appear on multiple lines.
 *	It also changes occurences of "XCOMM" to "#", to avoid problems
 *	with treating commands as invalid preprocessor commands.
 *
 *	The macros MAKEFILE and MAKE are provided as macros
 *	to make.  MAKEFILE is set to imake's makefile (not the constructed,
 *	preprocessed one) and MAKE is set to argv[0], i.e. the name of
 *	the imake program.
 *
 * Theory of operation:
 *   1. Determine the name of the imakefile from the command line (-f)
 *	or from the content of the current directory (Imakefile or imakefile).
 *	Call this <imakefile>.  This gets added to the arguments for
 *	make as MAKEFILE=<imakefile>.
 *   2. Determine the name of the template from the command line (-T)
 *	or the default, Imake.tmpl.  Call this <template>
 *   3. Determine the name of the imakeCfile from the command line (-C)
 *	or the default, Imakefile.c.  Call this <imakeCfile>
 *   4. Store lines of input into <imakeCfile>:
 *	- A c-style comment header (see ImakefileCHeader below), used
 *	  to recognize temporary files generated by imake.
 *	- If DEFAULT_OS_NAME is defined, format the utsname struct and
 *	  call the result <defaultOsName>.  Add:
 *		#define DefaultOSName <defaultOsName>
 *	- If DEFAULT_OS_MAJOR_REV is defined, format the utsname struct
 *	  and call the result <defaultOsMajorVersion>.  Add:
 *		#define DefaultOSMajorVersion <defaultOsMajorVersion>
 *	- If DEFAULT_OS_MINOR_REV is defined, format the utsname struct
 *	  and call the result <defaultOsMinorVersion>.  Add:
 *		#define DefaultOSMinorVersion <defaultOsMinorVersion>
 *	- If DEFAULT_OS_TEENY_REV is defined, format the utsname struct
 *	  and call the result <defaultOsTeenyVersion>.  Add:
 *		#define DefaultOSTeenyVersion <defaultOsTeenyVersion>
 *	- If DEFAULT_MACHINE_ARCITECTURE is defined, format the utsname struct
 *	  and define the corresponding macro.  (For example on the amiga,
 *	  this will define amiga in addition to m68k).
 *	- If the file "localdefines" is readable in the current
 *	  directory, print a warning message to stderr and add:
 *		#define IMAKE_LOCAL_DEFINES	"localdefines"
 *		#include IMAKE_LOCAL_DEFINES
 *	- If the file "admindefines" is readable in the current
 *	  directory, print a warning message to stderr and add:
 *		#define IMAKE_ADMIN_DEFINES	"admindefines"
 *		#include IMAKE_ADMIN_DEFINES
 *	- The following lines:
 *		#define INCLUDE_IMAKEFILE	< <imakefile> >
 *		#define IMAKE_TEMPLATE		" <template> "
 *		#include IMAKE_TEMPLATE
 *	- If the file "adminmacros" is readable in the current
 *	  directory, print a warning message to stderr and add:
 *		#define IMAKE_ADMIN_MACROS	"adminmacros"
 *		#include IMAKE_ADMIN_MACROS
 *	- If the file "localmacros" is readable in the current
 *	  directory, print a warning message to stderr and add:
 *		#define IMAKE_LOCAL_MACROS	"localmacros"
 *		#include IMAKE_LOCAL_MACROS
 *   5. Start up cpp and provide it with this file.
 *	Note that the define for INCLUDE_IMAKEFILE is intended for
 *	use in the template file.  This implies that the imake is
 *	useless unless the template file contains at least the line
 *		#include INCLUDE_IMAKEFILE
 *   6. Gather the output from cpp, and clean it up, expanding @@ to
 *	newlines, stripping trailing white space, cpp control lines,
 *	and extra blank lines, and changing XCOMM to #.  This cleaned
 *	output is placed in a new file, default "Makefile", but can
 *	be specified with -s or -e options.
 *   7. Optionally start up make on the resulting file.
 *
 * The design of the template makefile should therefore be:
 *	<set global macros like CFLAGS, etc.>
 *	<include machine dependent additions>
 *	#include INCLUDE_IMAKEFILE
 *	<add any global targets like 'clean' and long dependencies>
 */

#include "config.h"

#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
/* This needs to be before _POSIX_SOURCE gets defined */
# include <sys/param.h>
# include <sys/types.h>
# include <sys/sysctl.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <X11/Xfuncproto.h>
#include <X11/Xosdefs.h>
#include <string.h>
#include <ctype.h>
#ifdef WIN32
# include "Xw32defs.h"
#endif
#include <sys/types.h>
#include <fcntl.h>
#ifdef X_NOT_POSIX
# ifndef WIN32
#  include <sys/file.h>
# endif
#else
# include <unistd.h>
#endif
#ifdef ISC
# include <unistd.h>
#endif
#if defined(X_NOT_POSIX) || defined(_POSIX_SOURCE)
# include <signal.h>
#else
# define _POSIX_SOURCE
# include <signal.h>
# undef _POSIX_SOURCE
#endif
#if !defined(SIGCHLD) && defined(SIGCLD)
# define SIGCHLD		SIGCLD
#endif
#include <sys/stat.h>
#ifndef X_NOT_POSIX
# ifdef _POSIX_SOURCE
#  ifdef __SCO__
#   include <sys/procset.h>
#   include <sys/siginfo.h>
#  endif
#  include <sys/wait.h>
# else
#  define _POSIX_SOURCE
#  include <sys/wait.h>
#  undef _POSIX_SOURCE
# endif
# define waitCode(w)	WEXITSTATUS(w)
# define waitSig(w)	WTERMSIG(w)
typedef int		waitType;
#else /* X_NOT_POSIX */
# ifdef SYSV
#  define waitCode(w)	(((w) >> 8) & 0x7f)
#  define waitSig(w)	((w) & 0xff)
typedef int		waitType;
# else /* SYSV */
#  ifdef WIN32
#   include <process.h>
typedef int		waitType;
#  else
#   include <sys/wait.h>
#   define waitCode(w)	((w).w_T.w_Retcode)
#   define waitSig(w)	((w).w_T.w_Termsig)
typedef union wait	waitType;
#  endif
# endif
# ifndef WIFSIGNALED
#  define WIFSIGNALED(w) waitSig(w)
# endif
# ifndef WIFEXITED
#  define WIFEXITED(w) waitCode(w)
# endif
#endif /* X_NOT_POSIX */
#include <stdlib.h>
#include <errno.h>
#ifdef __minix_vmd
# define USE_FREOPEN		1
#endif

#ifndef WIN32
# include <sys/utsname.h>
#else
# include <windows.h>
#endif
#ifndef SYS_NMLN
# ifdef _SYS_NMLN
#  define SYS_NMLN _SYS_NMLN
# else
#  define SYS_NMLN 257
# endif
#endif
#if defined(linux) || defined(__GNU__) || defined(__GLIBC__)
# include <limits.h>
# include <stdio.h>
#endif
#ifdef __QNX__
# include <unix.h>
#endif

#if defined(__NetBSD__)		/* see code clock in init() below */
# include <sys/utsname.h>
#endif

typedef unsigned char boolean;
#define TRUE		1
#define FALSE		0

#include "imakemdep.h"
#ifdef CROSSCOMPILE
# include "imakemdep_cpp.h"
#endif

#if defined CROSSCOMPILE || defined FIXUP_CPP_WHITESPACE
int InRule = FALSE;
#endif
#if defined CROSSCOMPILE || defined INLINE_SYNTAX
int InInline = 0;
#endif
#if defined CROSSCOMPILE || defined MAGIC_MAKE_VARS
int xvariable = 0;
int xvariables[10];
#endif

#ifndef PATH_MAX
# define PATH_MAX 1024
#endif

/*
 * Some versions of cpp reduce all tabs in macro expansion to a single
 * space.  In addition, the escaped newline may be replaced with a
 * space instead of being deleted.  Blech.
 */
void KludgeOutputLine(char **), KludgeResetRule(void);

#ifndef CROSSCOMPILE
# ifdef USE_CC_E
#  ifndef DEFAULT_CC
#   define DEFAULT_CC "cc"
#  endif
# else
#  ifndef DEFAULT_CPP
#   ifdef CPP_PROGRAM
#    define DEFAULT_CPP CPP_PROGRAM
#   else
#    define DEFAULT_CPP "/lib/cpp"
#   endif
#  endif
# endif
#endif

const char *cpp = NULL;

const char	*tmpMakefile;
const char	*tmpMakefileTemplate = "/tmp/Imf.XXXXXX";
const char	*tmpImakefile;
const char	*tmpImakefileTemplate = "/tmp/IIf.XXXXXX";
const char	*make_argv[ ARGUMENTS ] = {
#ifdef WIN32
    "nmake"
#else
    "make"
#endif
};

int	make_argindex;
int	cpp_argindex;
const char	*Imakefile = NULL;
const char	*Makefile = "Makefile";
const char	*Template = "Imake.tmpl";
const char	*ImakefileC = "Imakefile.c";
boolean haveImakefileC = FALSE;
const char	*cleanedImakefile = NULL;
const char	*program;
const char	*FindImakefile(const char *Imakefile);
char	*ReadLine(FILE *tmpfd, const char *tmpfname);
const char	*CleanCppInput(const char *imakefile);
char	*Strdup(const char *cp);
char	*Emalloc(int size);
void	LogFatal(const char *x0, ...) _X_ATTRIBUTE_PRINTF(1, 2);
void	LogMsg(const char *x0, ...) _X_ATTRIBUTE_PRINTF(1, 2);

void	showit(FILE *fd);
void	wrapup(void);
void	init(void);
void	AddMakeArg(const char *arg);
void	AddCppArg(const char *arg);
#ifdef CROSSCOMPILE
char	*CrossCompileCPP(void);
#endif
void	SetOpts(int argc, char **argv);
void	CheckImakefileC(const char *masterc);
void	cppit(const char *imakefile, const char *template, const char *masterc,
	      FILE *outfd, const char *outfname);
void	makeit(void);
void	CleanCppOutput(FILE *tmpfd, const char *tmpfname);
boolean isempty(char *line);
void	writetmpfile(FILE *fd, const char *buf, int cnt, const char *fname);
#ifdef SIGNALRETURNSINT
int	catch(int sig);
#else
void	catch(int sig);
#endif
void	showargs(const char **argv);
boolean optional_include(FILE *inFile, const char *defsym, const char *fname);
void	  doit(FILE *outfd, const char *cmd, const char **argv);
boolean define_os_defaults(FILE *inFile);
#ifdef CROSSCOMPILE
static void get_cross_compile_dir(FILE *inFile);
#endif
#ifdef CROSSCOMPILEDIR
const char *CrossCompileDir = CROSSCOMPILEDIR;
#else
const char *CrossCompileDir = "";
#endif
boolean CrossCompiling = FALSE;



boolean verbose = FALSE;
boolean show = TRUE;

int
main(int argc, char *argv[])
{
	FILE	*tmpfd = NULL;
	char	makeMacro[ BUFSIZ ];
	char	makefileMacro[ BUFSIZ ];
	int	lenCrossCompileDir = 0;

	program = argv[0];
	init();

	lenCrossCompileDir = strlen(CrossCompileDir);
	if (lenCrossCompileDir) {
	    if (lenCrossCompileDir > (PATH_MAX - 20))
	      LogFatal("Cross compile directory path too long %s\n",
		       CrossCompileDir);
	    else
		CrossCompiling = TRUE;
	}

	SetOpts(argc, argv);
	Imakefile = FindImakefile(Imakefile);
	CheckImakefileC(ImakefileC);
	if (Makefile) {
		tmpMakefile = Makefile;
		if ((tmpfd = fopen(tmpMakefile, "w+")) == NULL)
		   LogFatal("Cannot create temporary file %s.", tmpMakefile);
	} else {
#ifdef HAVE_MKSTEMP
		int fd;
#endif
		char *tmpMakefileName = Strdup(tmpMakefileTemplate);
#ifndef HAVE_MKSTEMP
		if (mktemp(tmpMakefileName) == NULL ||
		    (tmpfd = fopen(tmpMakefileName, "w+")) == NULL) {
		   LogFatal("Cannot create temporary file %s.", tmpMakefileName);
		}
#else
		fd = mkstemp(tmpMakefileName);
		if (fd == -1 || (tmpfd = fdopen(fd, "w+")) == NULL) {
		   if (fd != -1) {
		      unlink(tmpMakefileName); close(fd);
		   }
		   LogFatal("Cannot create temporary file %s.", tmpMakefileName);
		}
#endif
		tmpMakefile = tmpMakefileName;
	}
	AddMakeArg("-f");
	AddMakeArg( tmpMakefile );
	sprintf(makeMacro, "MAKE=%s", program);
	AddMakeArg( makeMacro );
	sprintf(makefileMacro, "MAKEFILE=%s", Imakefile);
	AddMakeArg( makefileMacro );

	cleanedImakefile = CleanCppInput(Imakefile);
	cppit(cleanedImakefile, Template, ImakefileC, tmpfd, tmpMakefile);

	if (show) {
		if (Makefile == NULL)
			showit(tmpfd);
	} else
		makeit();
	wrapup();
	exit(0);
}

void
showit(FILE *fd)
{
	char	buf[ BUFSIZ ];
	int	red;

	fseek(fd, 0, SEEK_SET);
	while ((red = fread(buf, 1, BUFSIZ, fd)) > 0)
		writetmpfile(stdout, buf, red, "stdout");
	if (red < 0)
	    LogFatal("Cannot read %s.", tmpMakefile);
}

void
wrapup(void)
{
	if (tmpMakefile != Makefile)
		unlink(tmpMakefile);
	if (cleanedImakefile && cleanedImakefile != Imakefile)
		unlink(cleanedImakefile);
	if (haveImakefileC)
		unlink(ImakefileC);
}

#ifdef SIGNALRETURNSINT
int
#else
void
#endif
catch(int sig)
{
	errno = 0;
	LogFatal("Signal %d.", sig);
}

/*
 * Initialize some variables.
 */
void
init(void)
{
	register char	*p;

	make_argindex=0;
	while (make_argv[ make_argindex ] != NULL)
		make_argindex++;
	cpp_argindex = 0;
	while (cpp_argv[ cpp_argindex ] != NULL)
		cpp_argindex++;

#if defined CROSSCOMPILE
	if (sys == netBSD)
	  if (CrossCompiling) {
	    LogFatal("fix imake to do crosscompiling for NetBSD\n","");
	  } else
#endif
#if defined(__NetBSD__) || defined CROSSCOMPILE
	{
		struct utsname uts;
		static char argument[512];

		/*
		 * Sharable imake configurations require a
		 * machine identifier.
		 */
		if (uname(&uts) != 0)
			LogFatal("uname(3) failed; can't tell what %s",
			    "kind of machine you have.");

		memset(argument, 0, sizeof(argument));
		(void)snprintf(argument, sizeof(argument) - 1,
		    "-D__%s__", uts.machine);

		AddCppArg(argument);
	}
#endif /* __NetBSD__ */

	/*
	 * See if the standard include directory is different than
	 * the default.  Or if cpp is not the default.  Or if the make
	 * found by the PATH variable is not the default.
	 */
	if ((p = getenv("IMAKEINCLUDE"))) {
		if (*p != '-' || *(p+1) != 'I')
			LogFatal("Environment var IMAKEINCLUDE %s",
				"must begin with -I");
		AddCppArg(p);
		for (; *p; p++)
			if (*p == ' ') {
				*p++ = '\0';
				AddCppArg(p);
			}
	}
	if ((p = getenv("IMAKECPP")))
		cpp = p;
	if ((p = getenv("IMAKEMAKE")))
		make_argv[0] = p;

	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
		signal(SIGINT, catch);
#ifdef SIGCHLD
	signal(SIGCHLD, SIG_DFL);
#endif
}

void
AddMakeArg(const char *arg)
{
	errno = 0;
	if (make_argindex >= ARGUMENTS-1)
		LogFatal("Out of internal storage.");
	make_argv[ make_argindex++ ] = arg;
	make_argv[ make_argindex ] = NULL;
}

void
AddCppArg(const char *arg)
{
	errno = 0;
	if (cpp_argindex >= ARGUMENTS-1)
		LogFatal("Out of internal storage.");
	cpp_argv[ cpp_argindex++ ] = arg;
	cpp_argv[ cpp_argindex ] = NULL;
}

void
SetOpts(int argc, char **argv)
{

	errno = 0;
	/*
	 * Now gather the arguments for make
	 */
	for(argc--, argv++; argc; argc--, argv++) {
	    /*
	     * We intercept these flags.
	     */
	    if (argv[0][0] == '-') {
		if (argv[0][1] == 'D') {
		    AddCppArg(argv[0]);
		} else if (argv[0][1] == 'I') {
		    AddCppArg(argv[0]);
		} else if (argv[0][1] == 'U') {
		    AddCppArg(argv[0]);
		} else if (argv[0][1] == 'W') {
		    AddCppArg(argv[0]);
		} else if (argv[0][1] == 'f') {
		    if (argv[0][2])
			Imakefile = argv[0]+2;
		    else {
			argc--, argv++;
			if (! argc)
			    LogFatal("No description arg after -f flag");
			Imakefile = argv[0];
		    }
		} else if (argv[0][1] == 's') {
		    if (argv[0][2])
			Makefile = ((argv[0][2] == '-') && !argv[0][3]) ?
			    NULL : argv[0]+2;
		    else {
			argc--, argv++;
			if (!argc)
			    LogFatal("No description arg after -s flag");
			Makefile = ((argv[0][0] == '-') && !argv[0][1]) ?
			    NULL : argv[0];
		    }
		    show = TRUE;
		} else if (argv[0][1] == 'e') {
		   Makefile = (argv[0][2] ? argv[0]+2 : NULL);
		   show = FALSE;
		} else if (argv[0][1] == 'T') {
		    if (argv[0][2])
			Template = argv[0]+2;
		    else {
			argc--, argv++;
			if (! argc)
			    LogFatal("No description arg after -T flag");
			Template = argv[0];
		    }
		} else if (argv[0][1] == 'C') {
		    if (argv[0][2])
			ImakefileC = argv[0]+2;
		    else {
			argc--, argv++;
			if (! argc)
			    LogFatal("No imakeCfile arg after -C flag");
			ImakefileC = argv[0];
		    }
		} else if (argv[0][1] == 'v') {
		    verbose = TRUE;
		} else
		    AddMakeArg(argv[0]);
	    } else
		AddMakeArg(argv[0]);
	}

#ifndef CROSSCOMPILE
# ifdef USE_CC_E
	    if (!cpp)
	    {
		AddCppArg("-E");
#  ifdef __GNUC__
		if (verbose)
		    AddCppArg("-v");
#  endif
		cpp = DEFAULT_CC;
	    }
# else
	    if (!cpp)
		cpp = DEFAULT_CPP;
# endif
#else
	    if (!cpp)
		cpp = CrossCompileCPP();
#endif

	cpp_argv[0] = cpp;
	AddCppArg(ImakefileC);
}

const char *
FindImakefile(const char *Imakefile)
{
	if (Imakefile) {
		if (access(Imakefile, R_OK) < 0)
			LogFatal("Cannot find %s.", Imakefile);
	} else {
		if (access("Imakefile", R_OK) < 0) {
			if (access("imakefile", R_OK) < 0)
				LogFatal("No description file.");
			else
				Imakefile = "imakefile";
		} else
			Imakefile = "Imakefile";
	}
	return(Imakefile);
}

static void _X_ATTRIBUTE_PRINTF(1, 0)
vLogMsg(const char *fmt, va_list args)
{
	int error_number = errno;

	if (error_number) {
		fprintf(stderr, "%s: ", program);
		fprintf(stderr, "%s\n", strerror(error_number));
	}
	fprintf(stderr, "%s: ", program);
	vfprintf(stderr, fmt, args);
	fprintf(stderr, "\n");
}

void
LogFatal(const char *fmt, ...)
{
	static boolean entered = FALSE;
	va_list args;

	if (entered)
		return;
	entered = TRUE;

	va_start(args, fmt);
	vLogMsg(fmt, args);
	va_end(args);
	fprintf(stderr, "  Stop.\n");
	wrapup();
	exit(1);
}

void
LogMsg(const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	vLogMsg(fmt, args);
	va_end(args);
}

void
showargs(const char **argv)
{
	for (; *argv; argv++)
		fprintf(stderr, "%s ", *argv);
	fprintf(stderr, "\n");
}

#define ImakefileCHeader "/* imake - temporary file */"

void
CheckImakefileC(const char *masterc)
{
	char mkcbuf[1024];
	FILE *inFile;

	if (access(masterc, F_OK) == 0) {
		inFile = fopen(masterc, "r");
		if (inFile == NULL)
			LogFatal("Refuse to overwrite: %s", masterc);
		if ((fgets(mkcbuf, sizeof(mkcbuf), inFile) &&
		     strncmp(mkcbuf, ImakefileCHeader,
			     sizeof(ImakefileCHeader)-1)))
		{
			fclose(inFile);
			LogFatal("Refuse to overwrite: %s", masterc);
		}
		else
			fclose(inFile);
	}
}

#define LocalDefineFmt	"#define %s \"%s\"\n"
#define IncludeFmt	"#include %s\n"
#define ImakeDefSym	"INCLUDE_IMAKEFILE"
#define ImakeTmplSym	"IMAKE_TEMPLATE"
#define OverrideWarning "Warning: local file \"%s\" overrides global macros."

boolean
optional_include(FILE *inFile, const char *defsym, const char *fname)
{
	errno = 0;
	if (access(fname, R_OK) == 0) {
		LogMsg(OverrideWarning, fname);
		return (fprintf(inFile, LocalDefineFmt, defsym, fname) < 0 ||
			fprintf(inFile, IncludeFmt, defsym) < 0);
	}
	return FALSE;
}

void
doit(FILE *outfd, const char *cmd, const char **argv)
{
	int		pid;
	waitType	status;

	/*
	 * Fork and exec the command.
	 */
#ifdef WIN32
	if (outfd)
		dup2(fileno(outfd), 1);
	status = _spawnvp(_P_WAIT, cmd, argv);
	if (status < 0)
		LogFatal("Cannot spawn %s.", cmd);
	if (status > 0)
		LogFatal("Exit code %d.", status);
#else
	pid = fork();
	if (pid < 0)
		LogFatal("Cannot fork.");
	if (pid) {	/* parent... simply wait */
		while (wait(&status) > 0) {
			errno = 0;
			if (WIFSIGNALED(status))
				LogFatal("Signal %d.", waitSig(status));
			if (WIFEXITED(status) && waitCode(status))
				LogFatal("Exit code %d.", waitCode(status));
		}
	}
	else {	/* child... dup and exec cmd */
		if (verbose)
			showargs(argv);
		if (outfd)
			dup2(fileno(outfd), 1);
		execvp(cmd, argv);
		LogFatal("Cannot exec %s.", cmd);
	}
#endif
}

#if !defined WIN32
static void
parse_utsname(struct utsname *name, const char *fmt, char *result, const char *msg)
{
  char buf[SYS_NMLN * 5 + 1];
  char *ptr = buf;
  int arg;

  if (!name)
      LogFatal(msg,fmt);

  /* Assemble all the pieces into a buffer. */
  for (arg = 0; fmt[arg] != ' '; arg++)
    {
      /* Our buffer is only guaranteed to hold 5 arguments. */
      if (arg >= 5)
	LogFatal(msg, fmt);

      switch (fmt[arg])
	{
	case 's':
	  if (arg > 0)
	    *ptr++ = ' ';
	  strcpy(ptr, name->sysname);
	  ptr += strlen(ptr);
	  break;

	case 'n':
	  if (arg > 0)
	    *ptr++ = ' ';
	  strcpy(ptr, name->nodename);
	  ptr += strlen(ptr);
	  break;

	case 'r':
	  if (arg > 0)
	    *ptr++ = ' ';
	  strcpy(ptr, name->release);
	  ptr += strlen(ptr);
	  break;

	case 'v':
	  if (arg > 0)
	    *ptr++ = ' ';
	  strcpy(ptr, name->version);
	  ptr += strlen(ptr);
	  break;

	case 'm':
	  if (arg > 0)
	    *ptr++ = ' ';
	  strcpy(ptr, name->machine);
	  ptr += strlen(ptr);
	  break;

	default:
	  LogFatal(msg, fmt);
	}
    }

  /* Just in case... */
  if (strlen(buf) >= sizeof(buf))
    LogFatal("Buffer overflow parsing uname.");

  /* Parse the buffer.  The sscanf() return value is rarely correct. */
  *result = '\0';
  (void) sscanf(buf, fmt + arg + 1, result);
}

/* Trim leading 0's and periods from version names.  The 0's cause
   the number to be interpreted as octal numbers.  Some version strings
   have the potential for different numbers of .'s in them.
 */

static char *
trim_version(char *p)
{

	if (p != 0 && *p != '\0')
	{
		while ((*p == '0' || *p == '.') && *(p + 1) != '\0')
			++p;
	}
	return (p);
}
#endif

#if defined(linux) || defined(__GLIBC__)
const char *libc_c=
"#include <stdio.h>\n"
"#include <ctype.h>\n"
"\n"
"#if 1\n"
"#pragma weak gnu_get_libc_version\n"
"#pragma weak __libc_version\n"
"#pragma weak __linux_C_lib_version\n"
"#endif\n"
"\n"
"extern const char * gnu_get_libc_version (void);\n"
"extern const char * __linux_C_lib_version;\n"
"extern const char __libc_version [];\n"
"\n"
"int\n"
"main ()\n"
"{\n"
"  int libcmajor = 0, libcminor = 0, libcteeny = 0;\n"
"  const char * ptr = NULL;\n"
"  int glibcmajor = 0;\n"
"\n"
"  if (gnu_get_libc_version != 0)\n"
"  {\n"
"    ptr = gnu_get_libc_version ();\n"
"    glibcmajor = 4;\n"
"  }\n"
"  else if (&__libc_version != 0)\n"
"  {\n"
"    ptr = __libc_version;\n"
"    glibcmajor = 4;\n"
"  }\n"
"  else if (&__linux_C_lib_version != 0)\n"
"  {\n"
"    ptr = __linux_C_lib_version;\n"
"  }\n"
"  else\n"
"  {\n"
"    libcmajor = 0; libcminor = 0; libcteeny = 0;\n"
"  }\n"
"\n"
"  if (ptr)\n"
"  {\n"
"    while (!isdigit (*ptr))\n"
"      ptr++;\n"
"\n"
"    sscanf (ptr, \"%d.%d.%d\", &libcmajor, &libcminor, &libcteeny);\n"
"    libcmajor += glibcmajor;\n"
"  }\n"
"\n"
"  printf(\"#define DefaultLinuxCLibMajorVersion %d\\n\", libcmajor);\n"
"  printf(\"#define DefaultLinuxCLibMinorVersion %d\\n\", libcminor);\n"
"  printf(\"#define DefaultLinuxCLibTeenyVersion %d\\n\", libcteeny);\n"
"\n"
"  return 0;\n"
"}\n"
;

static void
get_libc_version(FILE *inFile)
{
  char aout[4096], *tmpdir;
  FILE *fp;
  const char *format = "%s -o %s -x c -";
  char *cc;
  int len;
  char *command;

  /* If $TMPDIR is defined and has an acceptable length,
   * use that as tmp dir, else use /tmp.  That fixes
   * problems with /tmp mounted "noexec".
   */
  if((tmpdir = getenv("TMPDIR")) != NULL && strlen(tmpdir) < (4096-13))
    strcpy(aout, tmpdir);
  else
    strcpy(aout, "/tmp");
  strcat(aout, "/imakeXXXXXX");

  /* Pre-create temp file safely */
  {
    /* Linux + ELF has mkstemp() */
    int tmpfd;
    if ((tmpfd = mkstemp(aout)) == -1) {
      perror("mkstemp");
      abort();
    }
    close(tmpfd);
  }
  cc = getenv ("CC");
  if (cc == NULL)
    cc = "gcc";
  len = strlen (aout) + strlen (format) + strlen (cc);
  if (len < 128) len = 128;
  if((command = alloca (len)) == NULL)
    abort();

  if (snprintf (command , len, format, cc, aout) == len)
    abort ();

  fp = popen (command, "w");
  if (fp == NULL || fprintf (fp, "%s\n", libc_c) < 0
      || pclose (fp) != 0)
    abort ();

  fp = popen (aout, "r");
  if (fp == NULL)
    abort ();

  while (fgets (command, len, fp))
    fputs (command, inFile);

  len = pclose (fp);
  remove (aout);
  if (len)
    abort ();
}
#endif

#if defined(__OpenBSD__) || defined(__DragonFly__)
static void
get_stackprotector(FILE *inFile)
{
  FILE *fp;
  char *cc;
  char command[1024], buf[1024];

  cc = getenv("CC");
  if (cc == NULL) {
    cc = "cc";
  }
  snprintf(command, sizeof(command), "%s -v 2>&1", cc);
  fp = popen(command, "r");
  if (fp == NULL)
    abort();
  while (fgets(buf, sizeof(buf), fp)) {
    if (strstr(buf, "propolice") != NULL) {
      fprintf(inFile, "#define ProPoliceSupport YES\n");
      break;
    }
  }
  pclose(fp);
}
#endif


#if defined CROSSCOMPILE || defined linux || defined(__GLIBC__)
static void
get_distrib(FILE *inFile)
{
  struct stat sb;

  static const char*   suse = "/etc/SuSE-release";
  static const char* redhat = "/etc/redhat-release";
  static const char* debian = "/etc/debian_version";

  fprintf (inFile, "%s\n", "#define LinuxUnknown    0");
  fprintf (inFile, "%s\n", "#define LinuxSuSE       1");
  fprintf (inFile, "%s\n", "#define LinuxCaldera    2");
  fprintf (inFile, "%s\n", "#define LinuxCraftworks 3");
  fprintf (inFile, "%s\n", "#define LinuxDebian     4");
  fprintf (inFile, "%s\n", "#define LinuxInfoMagic  5");
  fprintf (inFile, "%s\n", "#define LinuxKheops     6");
  fprintf (inFile, "%s\n", "#define LinuxPro        7");
  fprintf (inFile, "%s\n", "#define LinuxRedHat     8");
  fprintf (inFile, "%s\n", "#define LinuxSlackware  9");
  fprintf (inFile, "%s\n", "#define LinuxTurbo      10");
  fprintf (inFile, "%s\n", "#define LinuxWare       11");
  fprintf (inFile, "%s\n", "#define LinuxYggdrasil  12");

# ifdef CROSSCOMPILE
  if (CrossCompiling) {
      fprintf (inFile, "%s\n",
	       "#define DefaultLinuxDistribution LinuxUnknown");
      fprintf (inFile, "%s\n", "#define DefaultLinuxDistName Unknown");
      return;
  }
# endif
  if (lstat (suse, &sb) == 0) {
    fprintf (inFile, "%s\n", "#define DefaultLinuxDistribution LinuxSuSE");
    fprintf (inFile, "%s\n", "#define DefaultLinuxDistName SuSE");
    return;
  }
  if (lstat (redhat, &sb) == 0) {
    fprintf (inFile, "%s\n", "#define DefaultLinuxDistribution LinuxRedHat");
    fprintf (inFile, "%s\n", "#define DefaultLinuxDistName RedHat");
    return;
  }
  if (lstat (debian, &sb) == 0) {
    fprintf (inFile, "%s\n", "#define DefaultLinuxDistribution LinuxDebian");
    fprintf (inFile, "%s\n", "#define DefaultLinuxDistName Debian");
    /* You could also try to get the version of the Debian distrib by looking
     * at the content of /etc/debian_version */
    return;
  }
  /* what's the definitive way to tell what any particular distribution is? */

  fprintf (inFile, "%s\n", "#define DefaultLinuxDistribution LinuxUnknown");
  fprintf (inFile, "%s\n", "#define DefaultLinuxDistName Unknown");
  /* would like to know what version of the distribution it is */
}

static void
get_ld_version(FILE *inFile)
{
  FILE* ldprog;
  signed char c;
  int ldmajor, ldminor;
  const char *ld = "ld -v";

# ifdef CROSSCOMPILE
  if (CrossCompiling) {
      char cmd[PATH_MAX];
      strcpy (cmd, CrossCompileDir);
      strcat (cmd,"/");
      strcat (cmd,ld);
      ldprog = popen (cmd, "r");
  } else
# endif
      ldprog = popen (ld, "r");

  if (ldprog) {
    do {
      c = fgetc (ldprog);
    } while (c != EOF && !isdigit (c));
    ungetc (c, ldprog);
    (void) fscanf (ldprog, "%d.%d", &ldmajor, &ldminor);
    /* Start conversion to a more rational number */
    if ((ldmajor > 2) || ((ldmajor == 2) && (ldminor > 9)))
	ldmajor *= 100;
    else
	ldmajor *= 10;

    fprintf(inFile, "#define DefaultLinuxBinUtilsMajorVersion %d\n",
	    ldmajor + ldminor);
    pclose (ldprog);
  }
}
#endif

#if defined __FreeBSD__
static void
get_binary_format(FILE *inFile)
{
  int mib[2];
  size_t len;
  int osrel = 0;
  FILE *objprog = NULL;
  int iself = 0;
  char buf[10];
  char cmd[PATH_MAX];

  mib[0] = CTL_KERN;
  mib[1] = KERN_OSRELDATE;
  len = sizeof(osrel);
  sysctl(mib, 2, &osrel, &len, NULL, 0);
  if (CrossCompiling) {
      strcpy (cmd, CrossCompileDir);
      strcat (cmd, "/");
      strcat (cmd,"objformat");
  } else
      strcpy (cmd, "objformat");

  if (osrel >= 300004 &&
      (objprog = popen(cmd, "r")) != NULL &&
      fgets(buf, sizeof(buf), objprog) != NULL &&
      strncmp(buf, "elf", 3) == 0)
    iself = 1;
  if (objprog)
    pclose(objprog);

  fprintf(inFile, "#define DefaultToElfFormat %s\n", iself ? "YES" : "NO");
}
#endif

#if defined(sun) && defined(__SVR4)
/* Runs Sun compiler command and parses output - this is a bit of a hack
 * as it depends on the particular output format of the -V flag, but it's
 * worked for many releases.
 *
 * Input : cmd - command to run (called with -V flag)
 *	   path - path to command to run (use $PATH if NULL)
 * Output: cmajor & cminor - major and minor versions if found
 * Returns: 0 if successful, -1 if not.
 */
static int
ask_sun_compiler_for_versions(const char *cmd, const char *path,
  int *cmajor, int *cminor)
{
  char buf[BUFSIZ];
  char cmdtorun[PATH_MAX];
  char* vptr;
  FILE* ccproc;
  const char vflag[] = " -V 2>&1";
  int retval = -1;

  int len = strlen(cmd) + sizeof(vflag);

  if (path != NULL) {
      len += strlen(path) + 1;
  }

  if (len < sizeof(cmdtorun)) {
      if (path != NULL) {
	  sprintf(cmdtorun, "%s/%s %s", path, cmd, vflag);
      } else {
	  sprintf(cmdtorun, "%s %s", cmd, vflag);
      }

      if ((ccproc = popen (cmdtorun, "r")) != NULL) {
	  if (fgets (buf, sizeof(buf), ccproc) != NULL) {
	      vptr = strrchr (buf, 'C');
	      if (vptr) {
		  for (; (*vptr != '\0') && !isdigit(*vptr); vptr++) {
		      /* Do nothing - just scanning for first digit */
		  }
		  if (*vptr != '\0') {
		      if (sscanf (vptr, "%d.%d", cmajor, cminor) == 2) {
			  retval = 0;
		      }
		  }
	      }
	      if (retval != 0) {
		  fprintf(stderr,
		    "warning: could not parse version number in output of:\n"
		    "         %s\n", cmdtorun);
	      }
	      while (fgets (buf, sizeof(buf), ccproc) != NULL) {};
	  }
	  pclose (ccproc);
      }
  }
  return retval;
}

/* Find Sun compilers and their versions if present */
static void
get_sun_compiler_versions (FILE *inFile)
{
  const char* sunpro_path = "/opt/SUNWspro/bin";
  int cmajor, cminor, found = 0;

  /* If cross-compiling, only check CrossCompilerDir for compilers.
   * If not cross-compiling, first check cc in users $PATH,
   * then try /opt/SUNWspro if not found in the users $PATH
   */

# if defined CROSSCOMPILE
  if (CrossCompiling) {
      if (ask_sun_compiler_for_versions("cc", CrossCompileDir,
	&cmajor, &cminor) == 0) {
	      found = 1;
      }
  }
  else
# endif
  {
      if (ask_sun_compiler_for_versions("cc", NULL, &cmajor, &cminor) == 0) {
	  found = 1;
      } else if (ask_sun_compiler_for_versions("cc", sunpro_path,
	&cmajor, &cminor) == 0) {
	  found = 1;
	  fprintf(inFile, "#define DefaultSunProCCompilerDir %s", sunpro_path);
      }
  }

  if (found) {
      fprintf (inFile,
	"#define DefaultSunProCCompilerMajorVersion %d\n", cmajor);
      fprintf (inFile,
	"#define DefaultSunProCCompilerMinorVersion %d\n", cminor);
  }

  /* Now do it again for C++ compiler (CC) */
  found = 0;
# if defined CROSSCOMPILE
  if (CrossCompiling) {
      if (ask_sun_compiler_for_versions("CC", CrossCompileDir,
	&cmajor, &cminor) == 0) {
	      found = 1;
      }
  }
  else
# endif
  {
      if (ask_sun_compiler_for_versions("CC", NULL, &cmajor, &cminor) == 0) {
	  found = 1;
      } else if (ask_sun_compiler_for_versions("CC", sunpro_path,
	&cmajor, &cminor) == 0) {
	  found = 1;
	  fprintf(inFile,
		"#define DefaultSunProCplusplusCompilerDir %s", sunpro_path);
      }
  }

  if (found) {
      fprintf (inFile,
	"#define DefaultSunProCplusplusCompilerMajorVersion %d\n",
	cmajor);
      fprintf (inFile,
	"#define DefaultSunProCplusplusCompilerMinorVersion %d\n",
	cminor);
  }
}
#endif

#if defined CROSSCOMPILE || defined  __GNUC__
static void
get_gcc_version(FILE *inFile, char *name)
{
    fprintf (inFile, "#define HasGcc 1\n");
# ifdef CROSSCOMPILE
    if (CrossCompiling)
    {
	if (gnu_c > 1) {
	    fprintf (inFile, "#define HasGcc2 1\n");
	    if (gnu_c > 2)
		fprintf (inFile, "#define HasGcc3 1\n");
	}
	fprintf (inFile, "#define GccMajorVersion %d\n", gnu_c);
	fprintf (inFile, "#define GccMinorVersion %d\n", gnu_c_minor);
    } else
# endif
    {
# if __GNUC__ > 1
	fprintf (inFile, "#define HasGcc2 1\n");
#  if __GNUC__ > 2
	fprintf (inFile, "#define HasGcc3 1\n");
#  endif
# endif
	fprintf (inFile, "#define GccMajorVersion %d\n", __GNUC__);
	fprintf (inFile, "#define GccMinorVersion %d\n", __GNUC_MINOR__);
    }
# if defined(HAS_MERGE_CONSTANTS)
    fprintf (inFile, "#define HasGccMergeConstants %d\n", HAS_MERGE_CONSTANTS);
# endif
}
#endif

static boolean
get_gcc(char *cmd)
{
  struct stat sb;
    static const char* gcc_path[] = {
#if defined(linux) || \
     defined(__NetBSD__) || \
     defined(__OpenBSD__) || \
     defined(__FreeBSD__) || \
     defined(__DragonFly__) || \
     defined(__APPLE__) || \
     defined(__CYGWIN__) || \
     defined(__MINGW32__) || \
     defined(__GNU__) || \
     defined(__GLIBC__)
	"/usr/bin/cc",	/* for Linux PostIncDir */
#endif
	"/usr/local/bin/gcc",
	"/opt/gnu/bin/gcc",
	"/usr/pkg/bin/gcc"
    };

#ifdef CROSSCOMPILE
    static const char* cross_cc_name[] = {
	"cc",
	"gcc"
    };

    if (CrossCompiling) {
	int i;
	for (i = 0; i < sizeof (cross_cc_name) / sizeof cross_cc_name[0]; i++){
	    strcpy (cmd, CrossCompileDir);
	    strcat (cmd, "/");
	    strcat (cmd, cross_cc_name[i]);
	    if (lstat (cmd, &sb) == 0) {
		return TRUE;
		break;
	    }
	}
    } else
#endif
      {
	int i;
	for (i = 0; i < sizeof (gcc_path) / sizeof gcc_path[0]; i++) {
	    if (lstat (gcc_path[i], &sb) == 0) {
		strcpy (cmd, gcc_path[i]);
		return TRUE;
	    }
	}
      }
    return FALSE;
}

#ifdef CROSSCOMPILE
static void
get_gcc_incdir(FILE *inFile, char* name)
{
  FILE* gccproc;
  char buf[PATH_MAX];
  char cmd[PATH_MAX];
  char* ptr;

  strcpy(cmd,name);

  buf[0] = '\0';
  strcat (cmd, " --print-libgcc-file-name");
  if ((gccproc = popen (cmd, "r")) != NULL) {
      if (fgets (buf, PATH_MAX, gccproc) != NULL) {
	  ptr = strstr (buf, "libgcc.a");
	  if (ptr) strcpy (ptr, "include");
      }
      (void) pclose (gccproc);
  }

  if (buf[0])
      fprintf (inFile, "#define DefaultGccIncludeDir \"%s\"\n", buf);
}
#endif

boolean
define_os_defaults(FILE *inFile)
{
#if defined CROSSCOMPILE || !defined(WIN32)
# ifdef CROSSCOMPILE
#  ifdef __GNUC__
  if (1)
#  else
  if ((sys != win32) && (sys != emx))
#  endif
# endif
    {
# if (defined(DEFAULT_OS_NAME) || defined(DEFAULT_OS_MAJOR_REV) || \
     defined(DEFAULT_OS_MINOR_REV) || defined(DEFAULT_OS_TEENY_REV))
	struct utsname *name = NULL;
	struct utsname uts_name;
	char buf[SYS_NMLN * 5 + 1];

	/* Obtain the system information. */
#  ifdef CROSSCOMPILE
      if (!CrossCompiling)
#  endif
      {
	  if (uname(&uts_name) < 0)
	      LogFatal("Cannot invoke uname");
	  else
	      name = &uts_name;
      }
#  if defined CROSSCOMPILE && (defined linux || defined(__GLIBC__))
      else {
	  strncpy(uts_name.sysname,cross_uts_sysname,SYS_NMLN);
	  strncpy(uts_name.release,cross_uts_release,SYS_NMLN);
	  strncpy(uts_name.version,cross_uts_version,SYS_NMLN);
	  strncpy(uts_name.machine,cross_uts_machine,SYS_NMLN);
	  name = &uts_name;
      }
#  endif
#  ifdef __FreeBSD__
       /* Override for compiling in chroot of other OS version, such as
        * in the bento build cluster.
        */
       {
	 char *e;
	 if ((e = getenv("OSREL")) != NULL &&
	     strlen(name->sysname) + strlen(e) + 1 < SYS_NMLN) {
	  strcpy(name->release, e);
	  strcpy(name->version, name->sysname);
	  strcat(name->version, " ");
	  strcat(name->version, e);
	 }
       }
#  endif

#  if defined DEFAULT_OS_NAME
#   if defined CROSSCOMPILE
      if (!CrossCompiling)
#   endif
	{
	  parse_utsname(name, DEFAULT_OS_NAME, buf,
			"Bad DEFAULT_OS_NAME syntax %s");
#   ifdef DEFAULT_OS_NAME_FROB
	  DEFAULT_OS_NAME_FROB(buf, sizeof buf);
#   endif
	  if (buf[0] != '\0')
	    fprintf(inFile, "#define DefaultOSName %s\n", buf);
	}
#  endif

#  if defined CROSSCOMPILE
	if (CrossCompiling && defaultOsName) {
	  parse_utsname(name, defaultOsName, buf,
			"Bad DEFAULT_OS_NAME syntax %s");
	  if (defaultOsNameFrob)
	    defaultOsNameFrob(buf, sizeof buf);
	  if (buf[0] != '\0')
	    fprintf(inFile, "#define DefaultOSName %s\n", buf);
	}
#  endif

#  ifdef DEFAULT_OS_MAJOR_REV
#   if defined CROSSCOMPILE
	if (!CrossCompiling)
#   endif
	  {
	    parse_utsname(name, DEFAULT_OS_MAJOR_REV, buf,
			  "Bad DEFAULT_OS_MAJOR_REV syntax %s");
#   ifdef DEFAULT_OS_MAJOR_REV_FROB
	    DEFAULT_OS_MAJOR_REV_FROB(buf, sizeof buf);
#   endif
	    fprintf(inFile, "#define DefaultOSMajorVersion %s\n",
		    *buf ? trim_version(buf) : "0");
	  }
#  endif

#  if defined CROSSCOMPILE
	if (CrossCompiling && defaultOsMajorRev) {
	  parse_utsname(name, defaultOsMajorRev, buf,
			"Bad defaultOsMajorRev syntax %s");
	  if (defaultOsMajorRevFrob)
	    defaultOsMajorRevFrob(buf, sizeof buf);
	  fprintf(inFile, "#define DefaultOSMajorVersion %s\n",
		  *buf ? trim_version(buf) : "0");
	}
#  endif

#  ifdef DEFAULT_OS_MINOR_REV
#   if defined CROSSCOMPILE
	if (!CrossCompiling)
#   endif
	  {
	    parse_utsname(name, DEFAULT_OS_MINOR_REV, buf,
			  "Bad DEFAULT_OS_MINOR_REV syntax %s");
#   ifdef DEFAULT_OS_MINOR_REV_FROB
	    DEFAULT_OS_MINOR_REV_FROB(buf, sizeof buf);
#   endif
	    fprintf(inFile, "#define DefaultOSMinorVersion %s\n",
		    *buf ? trim_version(buf) : "0");
	  }
#  endif

#  if defined CROSSCOMPILE
	if (CrossCompiling && defaultOsMinorRev) {
	  parse_utsname(name, defaultOsMinorRev, buf,
			"Bad defaultOsMinorRev syntax %s");
	  if (defaultOsMinorRevFrob)
	    defaultOsMinorRevFrob(buf, sizeof buf);
	  fprintf(inFile, "#define DefaultOSMinorVersion %s\n",
		  *buf ? trim_version(buf) : "0");
	}
#  endif

#  ifdef DEFAULT_OS_TEENY_REV
#   if defined CROSSCOMPILE
	if (!CrossCompiling)
#   endif
	  {
	    parse_utsname(name, DEFAULT_OS_TEENY_REV, buf,
			  "Bad DEFAULT_OS_TEENY_REV syntax %s");
#   ifdef DEFAULT_OS_TEENY_REV_FROB
	    DEFAULT_OS_TEENY_REV_FROB(buf, sizeof buf);
#   endif
	    fprintf(inFile, "#define DefaultOSTeenyVersion %s\n",
		    *buf ? trim_version(buf) : "0");
	  }
#  endif

#  if defined CROSSCOMPILE
	if (CrossCompiling && defaultOsTeenyRev) {
	  parse_utsname(name, defaultOsTeenyRev, buf,
			"Bad defaultOsTeenyRev syntax %s");
	  if (defaultOsTeenyRevFrob)
	    defaultOsTeenyRevFrob(buf, sizeof buf);
	  fprintf(inFile, "#define DefaultOSTeenyVersion %s\n",
		  *buf ? trim_version(buf) : "0");
	}
#  endif

#  ifdef DEFAULT_MACHINE_ARCHITECTURE
#   if defined CROSSCOMPILE
	if (!CrossCompiling)
#   endif
	  {
	    parse_utsname(name, DEFAULT_MACHINE_ARCHITECTURE, buf,
			  "Bad DEFAULT_MACHINE_ARCHITECTURE %s");
	    fprintf(inFile, "#ifndef %s\n# define %s\n#endif\n", buf, buf);
	  }
#  endif

#  if defined CROSSCOMPILE
	if (CrossCompiling && defaultMachineArchitecture) {
	  parse_utsname(name, defaultMachineArchitecture, buf,
			"Bad defaultMachineArchitecture syntax %s");
	  fprintf(inFile, "#ifndef %s\n# define %s\n#endif\n", buf, buf);
	}
#  endif
# endif
# if defined CROSSCOMPILE
      if (CrossCompiling)
	get_cross_compile_dir(inFile);
      else
# endif
	  fprintf(inFile, "#define CrossCompiling NO\n");
# if defined CROSSCOMPILE
      if (CrossCompiling && sys == LinuX)
# endif
# if defined CROSSCOMPILE || defined linux || defined(__GLIBC__)
#  if defined(CROSSCOMPILE) && defined(__linux__)
	if (sys == LinuX)
#  endif
	  get_distrib (inFile);
# endif
# if defined linux || defined(__GLIBC__)
#  if defined CROSSCOMPILE
      if (!CrossCompiling)
#  endif
	  get_libc_version (inFile);
#  if defined CROSSCOMPILE
      else {
	  fprintf(inFile,"#define DefaultLinuxCLibMajorVersion %d\n",
		  glibc_major);
	  fprintf(inFile,"#define DefaultLinuxCLibMinorVersion %d\n",
		  glibc_minor);
	  fprintf(inFile,"#define DefaultLinuxCLibTeenyVersion 0\n");
      }
#  endif
# endif /* linux || __GLIBC__ */
# if defined CROSSCOMPILE || defined linux || defined(__GLIBC__)
#  if defined CROSSCOMPILE && defined(__linux__)
      if (sys == LinuX)
#  endif
	  get_ld_version(inFile);
# endif
# if defined (sun) && defined(SVR4)
      get_sun_compiler_versions (inFile);
# endif
# if defined CROSSCOMPILE || defined __GNUC__
#  if defined CROSSCOMPILE
      if (gnu_c)
#  endif
	{
	  char name[PATH_MAX];
	  if (get_gcc(name)) {
	      get_gcc_version (inFile,name);
#  if defined CROSSCOMPILE
	      if (sys != emx)
		  get_gcc_incdir(inFile,name);
#  endif
	  }
	}
# endif
# if defined __FreeBSD__
#  if defined CROSSCOMPILE
      if (sys == freeBSD)
#  endif
	  get_binary_format(inFile);
# endif
    }
#endif /* !WIN32 */
#if defined WIN32
# ifdef CROSSCOMPILE
  else if (sys == win32 && !CrossCompiling)
# endif
    {
      OSVERSIONINFO osvi;
      static char* os_names[] = { "Win32s", "Windows 95", "Windows NT" };

      memset(&osvi, 0, sizeof(OSVERSIONINFO));
      osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
      GetVersionEx (&osvi);

      fprintf (inFile, "#define DefaultOSName Microsoft %s\n",
	       os_names[osvi.dwPlatformId]);

      fprintf(inFile, "#define DefaultOSMajorVersion %d\n", osvi.dwMajorVersion);
      fprintf(inFile, "#define DefaultOSMinorVersion %d\n", osvi.dwMinorVersion);
      fprintf(inFile, "#define DefaultOSTeenyVersion %d\n",
	      osvi.dwBuildNumber & 0xFFFF);
    }
#endif /* WIN32 */
#ifdef CROSSCOMPILE
  else if (sys == emx)
    {
      fprintf(inFile, "#define DefaultOSMajorVersion 4\n");
      fprintf(inFile, "#define DefaultOSMinorVersion 0\n");
      fprintf(inFile, "#define DefaultOSTeenyVersion 0\n");
    }
#endif /* EMX */
#if defined(__OpenBSD__) || defined(__DragonFly__)
  get_stackprotector(inFile);
#endif
  return FALSE;
}

void
cppit(const char *imakefile, const char *template, const char *masterc,
      FILE *outfd, const char *outfname)
{
	FILE	*inFile;

	haveImakefileC = TRUE;
	inFile = fopen(masterc, "w");
	if (inFile == NULL)
		LogFatal("Cannot open %s for output.", masterc);
	if (fprintf(inFile, "%s\n", ImakefileCHeader) < 0 ||
	    define_os_defaults(inFile) ||
	    optional_include(inFile, "IMAKE_LOCAL_DEFINES", "localdefines") ||
	    optional_include(inFile, "IMAKE_ADMIN_DEFINES", "admindefines") ||
	    fprintf(inFile, "#define %s <%s>\n", ImakeDefSym, imakefile) < 0 ||
	    fprintf(inFile, LocalDefineFmt, ImakeTmplSym, template) < 0 ||
	    fprintf(inFile, IncludeFmt, ImakeTmplSym) < 0 ||
	    optional_include(inFile, "IMAKE_ADMIN_MACROS", "adminmacros") ||
	    optional_include(inFile, "IMAKE_LOCAL_MACROS", "localmacros") ||
	    fflush(inFile)) {
		fclose(inFile);
		LogFatal("Cannot write to %s.", masterc);
	}
	else if (fclose(inFile))
		LogFatal("Cannot write to %s.", masterc);
	/*
	 * Fork and exec cpp
	 */
	doit(outfd, cpp, cpp_argv);
	CleanCppOutput(outfd, outfname);
}

void
makeit(void)
{
	doit(NULL, make_argv[0], make_argv);
}

const char *
CleanCppInput(const char *imakefile)
{
	FILE	*outFile = NULL;
	FILE	*inFile;
	char	*buf,		/* buffer for file content */
		*pbuf,		/* walking pointer to buf */
		*punwritten,	/* pointer to unwritten portion of buf */
		*ptoken,	/* pointer to # token */
		*pend,		/* pointer to end of # token */
		savec;		/* temporary character holder */
	int	count;
	struct stat	st;

	/*
	 * grab the entire file.
	 */
	if (!(inFile = fopen(imakefile, "r")))
		LogFatal("Cannot open %s for input.", imakefile);
	if (fstat(fileno(inFile), &st) < 0)
		LogFatal("Cannot stat %s for size.", imakefile);
	buf = Emalloc((int)st.st_size+3);
	count = fread(buf + 2, 1, st.st_size, inFile);
	if (count == 0 && st.st_size != 0)
		LogFatal("Cannot read %s:", imakefile);
	fclose(inFile);
	buf[0] = '\n';
	buf[1] = '\n';
	buf[count + 2] = '\0';

	punwritten = pbuf = buf + 2;
	while (*pbuf) {
	    /* for compatibility, replace make comments for cpp */
	    if (*pbuf == '#' && pbuf[-1] == '\n' && pbuf[-2] != '\\') {
		ptoken = pbuf+1;
		while (*ptoken == ' ' || *ptoken == '\t')
			ptoken++;
		pend = ptoken;
		while (*pend && *pend != ' ' && *pend != '\t' && *pend != '\n' && *pend != '\r')
			pend++;
		savec = *pend;
		*pend = '\0';
		if (strcmp(ptoken, "define") &&
		    strcmp(ptoken, "if") &&
		    strcmp(ptoken, "ifdef") &&
		    strcmp(ptoken, "ifndef") &&
		    strcmp(ptoken, "include") &&
		    strcmp(ptoken, "line") &&
		    strcmp(ptoken, "else") &&
		    strcmp(ptoken, "elif") &&
		    strcmp(ptoken, "endif") &&
		    strcmp(ptoken, "error") &&
		    strcmp(ptoken, "pragma") &&
		    strcmp(ptoken, "undef")) {
		    if (outFile == NULL) {
#ifdef HAVE_MKSTEMP
			int fd;
#endif
			char *tmpImakefileName = Strdup(tmpImakefileTemplate);
#ifndef HAVE_MKSTEMP
			if (mktemp(tmpImakefileName) == NULL ||
			    (outFile = fopen(tmpImakefileName, "w+")) == NULL) {
			    LogFatal("Cannot open %s for write.",
				tmpImakefileName);
			}
#else
			fd=mkstemp(tmpImakefileName);
			if (fd != -1)
			    outFile = fdopen(fd, "w");
			if (outFile == NULL) {
			    if (fd != -1) {
			       unlink(tmpImakefileName); close(fd);
			    }
			    LogFatal("Cannot open %s for write.",
				tmpImakefileName);
			}
#endif
			tmpImakefile = tmpImakefileName;
		    }
		    writetmpfile(outFile, punwritten, pbuf-punwritten,
				 tmpImakefile);
		    if (ptoken > pbuf + 1)
			writetmpfile(outFile, "XCOMM", 5, tmpImakefile);
		    else
			writetmpfile(outFile, "XCOMM ", 6, tmpImakefile);
		    punwritten = pbuf + 1;
		}
		*pend = savec;
	    }
	    pbuf++;
	}
	if (outFile) {
	    writetmpfile(outFile, punwritten, pbuf-punwritten, tmpImakefile);
	    fclose(outFile);

	    return tmpImakefile;
	}

	return(imakefile);
}

void
CleanCppOutput(FILE *tmpfd, const char *tmpfname)
{
	char	*input;
	int	blankline = 0;

	while((input = ReadLine(tmpfd, tmpfname))) {
		if (isempty(input)) {
			if (blankline++)
				continue;
#ifdef CROSSCOMPILE
			if (fixup_whitespace)
#endif
#if defined CROSSCOMPILE || defined FIXUP_CPP_WHITESPACE
			    KludgeResetRule();
#endif
		} else {
			blankline = 0;
#ifdef CROSSCOMPILE
			if (fixup_whitespace)
#endif
#if defined CROSSCOMPILE || defined FIXUP_CPP_WHITESPACE
			    KludgeOutputLine(&input);
#endif
			writetmpfile(tmpfd, input, strlen(input), tmpfname);
		}
		writetmpfile(tmpfd, "\n", 1, tmpfname);
	}
	fflush(tmpfd);
#ifdef NFS_STDOUT_BUG
	/*
	 * On some systems, NFS seems to leave a large number of nulls at
	 * the end of the file.  Ralph Swick says that this kludge makes the
	 * problem go away.
	 */
	ftruncate (fileno(tmpfd), (off_t)ftell(tmpfd));
#endif
}

/*
 * Determine if a line has nothing in it.  As a side effect, we trim white
 * space from the end of the line.  Cpp magic cookies are also thrown away.
 * "XCOMM" token is transformed to "#".
 */
boolean
isempty(char *line)
{
	char	*pend;

	/*
	 * Check for lines of the form
	 *	# n "...
	 * or
	 *	# line n "...
	 */
	if (*line == '#') {
		pend = line+1;
		if (*pend == ' ')
			pend++;
		if (*pend == 'l' && pend[1] == 'i' && pend[2] == 'n' &&
		    pend[3] == 'e' && pend[4] == ' ')
			pend += 5;
		if (isdigit(*pend)) {
			do {
			    pend++;
			} while (isdigit(*pend));
			if (*pend == '\n' || *pend == '\0')
				return(TRUE);
			if (*pend++ == ' ' && *pend == '"')
				return(TRUE);
		}
		while (*pend)
		    pend++;
	} else {
	    for (pend = line; *pend; pend++) {
		if (*pend == 'X' && pend[1] == 'C' && pend[2] == 'O' &&
		    pend[3] == 'M' && pend[4] == 'M' &&
		    (pend == line || pend[-1] == ' ' || pend[-1] == '\t' || pend[-1] == '\r') &&
		    (pend[5] == ' ' || pend[5] == '\t' || pend[5] == '\r' || pend[5] == '\0'))
		{
		    *pend = '#';
		    memmove(pend+1, pend+5, strlen(pend+5)+1);
		}
#ifdef CROSSCOMPILE
		if (magic_make_vars)
#endif
		  {
#if defined CROSSCOMPILE || defined MAGIC_MAKE_VARS
		    if (*pend == 'X' && pend[1] == 'V' && pend[2] == 'A' &&
			pend[3] == 'R')
		    {
			char varbuf[5];
			int i;

			if (pend[4] == 'd' && pend[5] == 'e' && pend[6] == 'f' &&
			    pend[7] >= '0' && pend[7] <= '9')
			{
			    i = pend[7] - '0';
			    sprintf(varbuf, "%0.4d", xvariable);
			    strncpy(pend+4, varbuf, 4);
			    xvariables[i] = xvariable;
			    xvariable = (xvariable + 1) % 10000;
			}
			else if (pend[4] == 'u' && pend[5] == 's' &&
				 pend[6] == 'e' && pend[7] >= '0' &&
				 pend[7] <= '9')
			{
			    i = pend[7] - '0';
			    sprintf(varbuf, "%0.4d", xvariables[i]);
			    strncpy(pend+4, varbuf, 4);
			}
		    }
#endif
		}
	    }
	}
	while (--pend >= line && (*pend == ' ' || *pend == '\t' || *pend == '\r')) ;
	pend[1] = '\0';
	return (*line == '\0');
}

/*ARGSUSED*/
char *
ReadLine(FILE *tmpfd, const char *tmpfname)
{
	static boolean	initialized = FALSE;
	static char	*buf, *pline, *end;
	register char	*p1, *p2;

	if (! initialized) {
#ifdef WIN32
		FILE *fp = tmpfd;
#endif
		int total_red;
		struct stat st;

		/*
		 * Slurp it all up.
		 */
		fseek(tmpfd, 0, SEEK_SET);
		if (fstat(fileno(tmpfd), &st) < 0)
			LogFatal("cannot stat %s for size", tmpMakefile);
		pline = buf = Emalloc((int)st.st_size+1);
		total_red = fread(buf, 1, st.st_size, tmpfd);
		if (total_red == 0 && st.st_size != 0)
			LogFatal("cannot read %s", tmpMakefile);
		end = buf + total_red;
		*end = '\0';
		fseek(tmpfd, 0, SEEK_SET);
#if defined(SYSV) || defined(WIN32) || defined(USE_FREOPEN)
		tmpfd = freopen(tmpfname, "w+", tmpfd);
# ifdef WIN32
		if (! tmpfd) /* if failed try again */
			tmpfd = freopen(tmpfname, "w+", fp);
# endif
		if (! tmpfd)
			LogFatal("cannot reopen %s\n", tmpfname);
#else	/* !SYSV */
		ftruncate(fileno(tmpfd), (off_t) 0);
#endif	/* !SYSV */
		initialized = TRUE;
	    fprintf (tmpfd, "# Makefile generated by imake - do not edit!\n");
	}

	for (p1 = pline; p1 < end; p1++) {
		if (*p1 == '@' && *(p1+1) == '@'
		    /* ignore ClearCase version-extended pathnames */
		    && !(p1 != pline && !isspace(*(p1-1)) && *(p1+2) == '/'))
		{ /* soft EOL */
			*p1++ = '\0';
			p1++; /* skip over second @ */
			break;
		}
		else if (*p1 == '\n') { /* real EOL */
#if defined CROSSCOMPILE || defined WIN32
# if defined CROSSCOMPILE
		  if (sys == win32)
# endif
		    {
			if (p1 > pline && p1[-1] == '\r')
				p1[-1] = '\0';
		    }
#endif
			*p1++ = '\0';
			break;
		}
	}

	/*
	 * return NULL at the end of the file.
	 */
	p2 = (pline == p1 ? NULL : pline);
	pline = p1;
	return(p2);
}

void
writetmpfile(FILE *fd, const char *buf, int cnt, const char *fname)
{
	if (fwrite(buf, sizeof(char), cnt, fd) == -1)
		LogFatal("Cannot write to %s.", fname);
}

char *
Emalloc(int size)
{
	char	*p;

	if ((p = malloc(size)) == NULL)
		LogFatal("Cannot allocate %d bytes", size);
	return(p);
}

#if defined CROSSCOMPILE || defined FIXUP_CPP_WHITESPACE
void
KludgeOutputLine(char **pline)
{
	char	*p = *pline;
	char	quotechar = '\0';

	switch (*p) {
	    case '#':	/*Comment - ignore*/
		break;
	    case '\t':	/*Already tabbed - ignore it*/
		break;
	    case ' ':	/*May need a tab*/
	    default:
# ifdef CROSSCOMPILE
		if (inline_syntax)
# endif
# if defined CROSSCOMPILE || defined INLINE_SYNTAX
		{
		    if (*p == '<' && p[1] == '<') { /* inline file close */
			InInline--;
			InRule = TRUE;
			break;
		    }
		}
# endif
		/*
		 * The following cases should not be treated as beginning of
		 * rules:
		 * variable := name (GNU make)
		 * variable = .*:.* (':' should be allowed as value)
		 * sed 's:/a:/b:'   (: used in quoted values)
		 */
		for (; *p; p++) {
		    if (quotechar) {
			if (quotechar == '\\' ||
			    (*p == quotechar &&
# if defined CROSSCOMPILE || defined WIN32
			     (
#  if defined CROSSCOMPILE
			      (sys == win32) &&
#  endif
			      quotechar != ')') &&
# endif
			     p[-1] != '\\'))
			    quotechar = '\0';
			continue;
		    }
		    switch (*p) {
		    case '\\':
		    case '"':
		    case '\'':
			quotechar = *p;
			break;
		    case '(':
			quotechar = ')';
			break;
		    case '{':
			quotechar = '}';
			break;
		    case '[':
			quotechar = ']';
			break;
		    case '=':
# ifdef CROSSCOMPILE
			if (remove_cpp_leadspace)
# endif
# if defined CROSSCOMPILE || defined REMOVE_CPP_LEADSPACE
			{
			    if (!InRule && **pline == ' ') {
				while (**pline == ' ')
				    (*pline)++;
			    }
			}
# endif
			goto breakfor;
# if defined CROSSCOMPILE || defined INLINE_SYNTAX
		    case '<':
			if (inline_syntax) {
			    if (p[1] == '<') /* inline file start */
				InInline++;
			}
			break;
# endif
		    case ':':
			if (p[1] == '=')
			    goto breakfor;
			while (**pline == ' ')
			    (*pline)++;
			InRule = TRUE;
			return;
		    }
		}
breakfor:
		if (InRule && **pline == ' ')
		    **pline = '\t';
		break;
	}
}

void
KludgeResetRule(void)
{
	InRule = FALSE;
}
#endif
char *
Strdup(const char *cp)
{
	char *new = Emalloc(strlen(cp) + 1);

	strcpy(new, cp);
	return new;
}

#ifdef CROSSCOMPILE
char*
CrossCompileCPP(void)
{
    char *cpp, *c;
    int len ;
    if (crosscompile_use_cc_e)
	AddCppArg("-E");

    cpp = strrchr(crosscompile_cpp,'/');
    if (!cpp)
	cpp = crosscompile_cpp;
    else
	cpp++;

    len = strlen(cpp) + strlen(CrossCompileDir) + 2;
    c = Emalloc(len);

    (void)snprintf(c, len,"%s/%s",CrossCompileDir,cpp);

    return c;
}

#endif

#ifdef CROSSCOMPILE
static void
get_cross_compile_dir(FILE *inFile)
{
	fprintf(inFile, "#define CrossCompileDir %s\n",
		CrossCompileDir);
	fprintf(inFile, "#define CrossCompiling YES\n");
}
#endif
