/*
 * Copyright (c) 2003 - 2007, Nils R. Weller
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Functions to execute cpp/NASM/ncc
 */
#include "cc1_main.h"
#include "exectools.h"
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <sys/stat.h>
#include "defs.h"
#include "analyze.h"
#include "error.h"
#include "lex.h"
#include "misc.h"
#include "functions.h"
#include "scope.h"
#include "backend.h"
#include "typemap.h"
#include "debug.h"
#include "config.h"
#include "n_libc.h"


int	stackprotectflag;
int	ansiflag;
int	standard;
int	pedanticflag;
int	gnuheadersflag = 1;
int	gflag;
int	Eflag;
int	assembler; /* XXX unused by nwcc1, necessary for exectools linking */
int	abiflag;
int	archflag;
char	*input_file;
char	*asmflag;
char	*gnuc_version;


#define REM_EXIT(n1, n2) do { \
	if (is_tmpfile) (void) remove(n1); \
	(void) remove(n2); \
	return EXIT_FAILURE; \
} while (0)

static char *
do_cpp(char *file, char **args, int cppind) {
	/* XXX FILENAME_MAX is broken on HP-UX */
	static char	output_path[FILENAME_MAX + 1];
	char		buf[FILENAME_MAX + 1];
	char		tmpbuf[128] = "/var/tmp/cpp";
	char		*arch;
	char		*progname = "gcc -E";
	char		*p;
	char		*gnooh = NULL;
	int		using_nwcpp = 0;
	int		i;
	FILE		*fd;
	FILE		*fd2;

	/*
	 * The GNU cpp on OpenBSD looks pretty broken. This raised the
	 * point that gcc's builtin cpp is more likely to be correct,
	 * and that we really want GNU cpp and not a random other cpp.
	 * So now we prefer ``gcc -E'', and if that fails, ``cpp'',
	 * and if that fails, nwcpp. Perhaps nwcpp should come before
	 * cpp.
	 */
	if (find_cmd("gcc", NULL, 0) != 0) {
		if (find_cmd("cpp", NULL, 0) == 0) {
			progname = "cpp";
		} else {
			progname = INSTALLDIR "/nwcc/bin/nwcpp";
			using_nwcpp = 1;
		}
	}
	if ((p = getenv("NWCC_CPP")) != NULL) {
		progname = p;
		if ((p = strrchr(p, '/')) != NULL) {
			++p;
		} else {
			p = progname;
		}
		if (strcmp(p, "nwcpp") == 0) {
			using_nwcpp = 1;
		}	
	}

	if (using_nwcpp) {
		/*
		 * If we're cross-compiling, we have to pass on the
		 * architecture and ABI to nwcpp
		 */
		if (archflag != 0) {
			args[cppind++] = arch_to_option(archflag);
		}
		if (abiflag != 0) {
			args[cppind++] = abi_to_option(abiflag);
		}	
		args[cppind] = NULL;
	} else {
		int	host_arch;
		int	host_abi;

		get_host_arch(&host_arch, &host_abi);
		if (host_arch == ARCH_MIPS) {
			if (abiflag == ABI_MIPS_N64) {
				args[cppind++] = "-mabi=64";
				args[cppind] = NULL;
			} 
		} else if (host_arch == ARCH_POWER) {
			if (abiflag == ABI_POWER64) {
				args[cppind++] = "-maix64";
				args[cppind] = NULL;
			}
		}
	}

#if 0
	tunit_name = n_xstrdup(file);
	if (stat(tunit_name, &sbuf) != -1) {
		tunit_size = sbuf.st_size;
	} else {
		/* !?!? */
		tunit_size = rand();
	}
#endif
	tunit_name = n_xmalloc(strlen(file) + 1);
	for (p = file, i = 0; *p != 0; ++p) {
		if (isalnum((unsigned char)*p) || *p == '_') {
			tunit_name[i++] = *p;
		}
	}
	tunit_name[i] = 0;

	if ((p = strrchr(tunit_name, '.')) != NULL) {
		*p = 0;
	}
	
	if (Eflag) {
		fd = stdout;
	} else {
		fd = get_tmp_file(tmpbuf, output_path, "cpp");
		if (fd == NULL) {
			return NULL;
		}
	}
	input_file = n_xstrdup(file);

#if 0
#ifdef __i386__
	/*arch = "-D__i386__";*/
#elif defined __sgi
	/*arch = "-D__sgi";*/
#elif defined __amd64__
	/*arch = "-D__amd64__";*/
#elif defined _AIX
	/*arch = "-D_AIX";*/
#else
#error "Unsupported host architecture"
#endif
#endif	
	arch = "";

	if (gnuheadersflag && standard != C89) {
#ifdef linux 
#ifdef GNUBYDEFAULT
		/*
		 * 07/01/07: Because the major()/minor() macros on my
		 * old SuSE system do not work with a GNU C version of
		 * less than 2, now is probably the time we have to
		 * bump it up. This will probably open a can of worms,
		 * but has to be done some time
		 */

		/*
		 * 07/22/07: That goddamn GNU make tests for version
		 * 2.5... and if that's not present, it #defines
		 * __attribute__ to expand to an empty body. That
		 * breaks stuff like wait() which uses a transparent_
		 * union attribute. So let's set the rest on fire as
		 * well by bumping straight to 3
		 */
		gnooh = " -U__GNUC__ -D__GNUC__=3 "; /* was 1 , then 2 !!! */
#else
		gnooh = " -D__GNUC__=3 "; /* was 1, then 2 !!! */
#endif
		if (using_nwcpp) gnooh = ""; /* XXX */
		fd2 = exec_cmd(progname, " -D__NWCC__=1 %s%s%[] %s",
			arch, gnooh, args, file);
#else			
		fd2 = exec_cmd(progname, " -D__NWCC__=1 %s %[] %s",
			arch, args, file);
#endif
#if 0
#endif
	} else {
#if defined __FreeBSD__ || defined __OpenBSD__ || defined __DragonFly__
		/*
		 * The FreeBSD headers are worthless without __GNUC__, so
		 * let's invoke cpp with default settings (enables __GNUC__)
		 */
		fd2 = exec_cmd(progname, " %s -D__NWCC__=1 %s %s %[] %s",
			ansiflag? "-D'__aligned\\(x\\)='": "",
			ansiflag? "-U__GNUC__": "",
			arch, args, file);
#else
		fd2 = exec_cmd(progname, " -D__NWCC__=1 %s -U__GNUC__ %[] %s",
			arch, args, file);
#endif
	}

	if (fd2 == NULL) {
		perror("popen");
		fclose(fd);
		remove(output_path);
		return NULL;
	}

	while (fgets(buf, sizeof buf, fd2)) {
		fputs(buf, fd);
	}
	if (((unsigned)pclose(fd2) >> 8) != 0) {
		fprintf(stderr, "*** cpp returned nonzero exit-status.\n");
		fclose(fd);
		remove(output_path);
		return NULL;
	}
	fclose(fd);
	return output_path;
}


static char *garbage;

static void
remove_garbage(void) {
	if (errors) {
		remove(garbage);
	}
}


static int
do_ncc(char *cppfile, char *nccfile, int is_tmpfile) {
	static int	inits_done;
	int		fildes;
	FILE		*input;
	FILE		*fd;
	char		*p;

	/* Generate in CWD */
	if ((p = strrchr(nccfile, '/')) != NULL) {
		nccfile = p+1;
	}

	garbage = nccfile;
	atexit(remove_garbage);
	if (inits_done == 0) {
		init_keylookup();
		init_oplookup();
		inits_done = 1;
	}
	toklist = NULL;
	funclist = NULL;

	input = fopen(cppfile, "r");
	if (input == NULL) {
		perror(cppfile);
		return EXIT_FAILURE;
	}

	(void) unlink(nccfile); /* trash stale .asm file */
	if ((fildes = open(nccfile, O_CREAT | O_EXCL | O_RDWR, S_IRWXU))
		== -1) {
		perror(nccfile);
		if (is_tmpfile) remove(cppfile);
		return EXIT_FAILURE;
	}
	if ((fd = fdopen(fildes, "r+")) == NULL) {
		perror(nccfile);
		REM_EXIT(cppfile, nccfile);
		return EXIT_FAILURE;
	}

	/*
	 * It is important to initialize the backend before doing
	 * lexical analysis because architecture and ABI information
	 * are needed
	 */
	if (init_backend(fd, &global_scope) != 0) {
		REM_EXIT(cppfile, nccfile);
	}
	if (lex(input) != 0) {
		REM_EXIT(cppfile, nccfile);
	}
	if (analyze(NULL) != 0) {
		REM_EXIT(cppfile, nccfile);
	}

	if (errors || backend->generate_program() != 0) {
		;
	}

	/* destroy_toklist(&toklist); */
	fclose(input);
	if (is_tmpfile) remove(cppfile);
	(void) fprintf(stderr, "%s - %u error(s), %u warning(s)\n",
		cppfile, (unsigned)errors, (unsigned)warnings);
	if (errors) {
		remove(nccfile);
		return EXIT_FAILURE;
	}
	return 0;
}


static void 
segv_handler(int s) {
	char	*p;
	(void) s;
	p = "Segmentation fault\n";
	write(1, p, strlen(p));
	exit(EXIT_FAILURE); /* Dangerous, but necessary for debugging */
}

static void
usage(void) {
	/* XXX add useful stuff here */
	puts("You invoked nwcc1 incorrectly. Please refer to the README file");
	puts("for supported command line arguments");
	exit(EXIT_FAILURE);
}



int
main(int argc, char *argv[]) {
	int			ch;
	char			*p;
	char			*nccfile = NULL;
	char			*tmp;
	char			*target_str = NULL;
	char			*abi_str = NULL;
	char			*cpp_args[128];
	int			cppind = 0;
	int			is_tmpfile = 0;
	struct ga_option	options[] = {
		{ 'D', NULL, 1 },
		{ 'U', NULL, 1 },
		{ 'I', NULL, 1 },
		{ 'E', NULL, 0 },
		{ 'g', NULL, 0 },
		{ 0, "stackprotect", 0 },
		{ 0, "nostdinc", 0 },
		{ 0, "arch", 1 },
		{ 0, "gnuc", 1 },
#ifdef __sun
		{ 0, "xarch", 1 },
#endif
		{ 0, "mabi", 1 },
		{ 0, "abi", 1 },
		{ 0, "pedantic", 0 },
		{ 0, "asm", 1 },
		{ 0, "ansi", 0 }
	};	
	int			nopts = N_OPTIONS(options);
	int			idx;

	(void) segv_handler;
#if 0
	(void) signal(SIGSEGV, segv_handler);
	get_host_arch(&archflag, &abiflag);
#endif
	if (argc <= 1) {
		usage();
	}	

	while ((ch = nw_get_arg(argc-1, argv+1, options, nopts, &idx)) != -1) {
		switch (ch) {
		case 'D':
		case 'U':
		case 'I':
			if (cppind == 126) {
				(void) fprintf(stderr, "Too many stderr "
					     "arguments\n");
				return EXIT_FAILURE;
			}

			/* OpenBSD's cpp chokes on -D arg, needs -Darg ... */
			cpp_args[cppind] = n_xmalloc(strlen(n_optarg) + 3);
			sprintf(cpp_args[cppind++], "-%c%s", ch, n_optarg);
			break;
		case 'g':
			gflag = 1;
			/* XXX */gflag = 0;
			break;
		case 'E':
			Eflag = 1;
			break;
		case '!':
			if (nccfile != NULL) {
				(void) fprintf(stderr, "Error: More than one "
					      "input file specified\n");
				usage();
			}
			nccfile = n_xmalloc(strlen(n_optarg) + 16);
			strcpy(nccfile, n_optarg);
			break;
		case '?':
			if (idx) {
				if (strcmp(options[idx].name, "stackprotect")
					== 0) {
					stackprotectflag = 1;
				} else if (strcmp(options[idx].name, "ansi")
					== 0) {
					ansiflag = standard = C89;
				} else if (strcmp(options[idx].name, "pedantic")
					== 0) {
					pedanticflag = 1;
				} else if (strcmp(options[idx].name, "nostdinc")
					== 0) {
					cpp_args[cppind++] = "-nostdinc";
				} else if (strcmp(options[idx].name, "asm")
					== 0) {
					asmflag = n_xstrdup(n_optarg);
				} else if (strcmp(options[idx].name, "arch")
					== 0) {	
					if (target_str != NULL) {
						(void) fprintf(stderr, "-arch "
						"used more than once\n");
						exit(EXIT_FAILURE);
					}	
					target_str = n_xstrdup(n_optarg);
				} else if (strcmp(options[idx].name, "gnuc")	
					== 0) {	
					if (gnuc_version != NULL) {
						(void) fprintf(stderr, "-gnuc "
						"used more than once\n");
						exit(EXIT_FAILURE);
					}	
					gnuc_version = n_xstrdup(n_optarg);
				} else if (strcmp(options[idx].name, "xarch")
					== 0) {
					/* SunCC compatibility */
					;
				} else if (strcmp(options[idx].name, "mabi")
					== 0
					|| strcmp(options[idx].name, "abi")
					== 0) {
					if (abi_str != NULL) {
						(void) fprintf(stderr, "-%s "
						"used more than once\n",
						options[idx].name);
						exit(EXIT_FAILURE);
					}	
					abi_str = n_xstrdup(n_optarg);
#if 0
					abiflag = ascii_abi_to_value(
						n_optarg, archflag);
#endif
				} else {
					usage();
				}
			} else {
				usage();
			}
			break;
		default:
			return EXIT_FAILURE;
		}
	}

	if (gnuc_version == NULL) {
		gnuc_version = "2";
	}

	set_target_arch_and_abi(&archflag, &abiflag, target_str, abi_str);
	
	if (abiflag == ABI_POWER64) {
#if 0
		cpp_args[cppind++] = "-D__64BIT__=1";
		cpp_args[cppind++] = "-U__LONG_MAX__";
#endif
		/*
		 *  XXX -maix64 doesn't work with GNU cpp on non-AIX
		 * platforms...
		 */
#ifdef _AIX
		cpp_args[cppind++] = "-maix64";
#endif
#if 0
		if (sizeof(long) != 8) {
			(void) fprintf(stderr, "ERROR: This compiler was not "
				"built with 64bit support\n");
			exit(EXIT_FAILURE);
		}
#endif
	} else if (abiflag == ABI_POWER32) {
#if 0
		if (sizeof(long) == 8) {
			/* Built for 64bit */
			abiflag = ABI_POWER64;
		}
#endif
	} else if (abiflag == ABI_MIPS_N64) {
#ifdef __sgi
		cpp_args[cppind++] = "-mabi=n64";
#endif
	} else if (abiflag == ABI_SPARC64) {
#if !defined __i386__ && (defined __sun || defined __sparc)
		cpp_args[cppind++] = "-m64";
#endif
	}

	cpp_args[cppind] = NULL;

	cross_initialize_type_map(archflag, abiflag);

#if 0
optind = 0;
argv[0] = "new.c";
#endif
	if ((p = strrchr(nccfile, '.')) == NULL
		|| (strcmp(++p, "c") != 0
		&& strcmp(p, "i") != 0)) {
		fprintf(stderr, "%s: Invalid input file name\n", nccfile);
		return EXIT_FAILURE;
	}
	if (strcmp(p, "i") == 0) {
		/* Already preprocessed */
		tmp = n_xstrdup(nccfile);
	} else {
		is_tmpfile = 1;
		if ((tmp = do_cpp(nccfile, cpp_args, cppind)) == NULL) {
			return EXIT_FAILURE;
		}
	}	
	if (Eflag) {
		/* Done! */
		return 0;
	}
	strcpy(p, "asm");

	return do_ncc(tmp, nccfile, is_tmpfile);
}



syntax highlighted by Code2HTML, v. 0.9.1