/* $Id: faucc.c,v 1.40 2012/02/17 14:25:56 vrsieh Exp $ 
 *
 * Copyright (C) 2008-2009 FAUcc Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include "config.h"

#include "fix_execvp_warnings.h"

#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>

const char *progname;
const char *opt_B = FAUCCDIR;
int opt_E;
int opt_S;
int opt_c;
int opt_o;
const char *opt_o_arg;
const char *input[256];
unsigned int ninputs;
const char *output[256];
unsigned int noutputs;

const char *cpp_param[256];
unsigned int ncpp_params;
const char *cc1_param[256];
unsigned int ncc1_params;
const char *as_param[256];
unsigned int nas_params;
const char *ld_param[256];
unsigned int nld_params;

/*
 * Possible race condition!!! FIXME
 */
static void
tmpname(char *buf, const char *suffix)
{
	struct stat st;
	unsigned int i;
	int ret;

	for (i = 0; ; i++) {
		sprintf(buf, "/tmp/faucc.%u.%u.%s",
				(unsigned int) getpid(), i, suffix);
		ret = lstat(buf, &st);
		if (ret < 0
		 && errno == ENOENT) {
			break;
		}
	}
}

static void
debug(const char *file, const char **param)
{
#if 0
	fprintf(stderr, "%s", file);
	while (*param) {
		fprintf(stderr, " %s", *param++);
	}
	fprintf(stderr, "\n");
#endif
}

static void
exit_status(const char *prog, int status)
{
	if (WIFEXITED(status)) {
		if (WEXITSTATUS(status) != 0) {
			fprintf(stderr, "%s: %s: exit status %u.\n",
					progname, prog, WEXITSTATUS(status));
		}
	} else if (WIFSIGNALED(status)) {
		fprintf(stderr, "%s: %s: %s", progname, prog,
				strsignal(WTERMSIG(status)));
		if (WCOREDUMP(status)) {
			fprintf(stderr, " (core dumped)");
		}
		fprintf(stderr, "\n");
	}
}

static int
cpp(const char *in, const char *out)
{
	int pid;
	int status;
	int ret;

	cpp_param[ncpp_params++] = in;
	cpp_param[ncpp_params] = NULL;

	switch (pid = fork()) {
	case -1:
		assert(0);

	case 0:
		if (strcmp(out, "") != 0) {
			ret = close(1);
			assert(0 <= ret);

			ret = open(out, O_CREAT | O_TRUNC | O_WRONLY, 0666);
			assert(0 <= ret);
			assert(ret == 1);
		}

		debug("cpp", cpp_param);
		custom_execvp("cpp", cpp_param);
		assert(0);

	default:
		ret = wait(&status);
		assert(0 <= ret);
		exit_status("cpp", status);
		break;
	}

	return 0;
}

static int
cc1(const char *in, const char *out)
{
	int pid;
	char path[1024];
	int status;
	int ret;

	cc1_param[ncc1_params++] = in;
	cc1_param[ncc1_params++] = out;
	cc1_param[ncc1_params] = NULL;

	switch (pid = fork()) {
	case -1:
		assert(0);

	case 0:
		strcpy(path, opt_B);
		strcat(path, "/cc1");
		debug(path, cc1_param);
		custom_execvp(path, cc1_param);
		assert(0);

	default:
		ret = wait(&status);
		assert(0 <= ret);
		exit_status("cc1", status);
		break;
	}
	
	return 0;
}

static int
as(const char *in, const char *out)
{
	int pid;
	int status;
	int ret;

	as_param[nas_params++] = "-c";
	as_param[nas_params++] = "-o";
	as_param[nas_params++] = out;
	as_param[nas_params++] = in;
	as_param[nas_params] = NULL;

	switch (pid = fork()) {
	case -1:
		assert(0);

	case 0:
		ret = close(0);
		assert(0 <= ret);

		ret = open(in, O_RDONLY);
		assert(0 <= ret);
		assert(ret == 0);

		ret = close(1);
		assert(0 <= ret);

		ret = open(out, O_CREAT | O_TRUNC | O_WRONLY, 0666);
		assert(0 <= ret);
		assert(ret == 1);

		debug("gcc", as_param);
		custom_execvp("gcc", as_param);
		assert(0);

	default:
		ret = wait(&status);
		assert(0 <= ret);
		exit_status("as", status);
		break;
	}
	
	return 0;
}

static int
ld(void)
{
	int pid;
	int status;
	int ret;

	ld_param[nld_params] = NULL;

	switch (pid = fork()) {
	case -1:
		assert(0);

	case 0:
		debug("ld", ld_param);
		custom_execvp("gcc", ld_param);
		assert(0);

	default:
		ret = wait(&status);
		assert(0 <= ret);
		exit_status("ld", status);
		break;
	}
	
	return 0;
}

static int
rm(const char *file)
{
	int ret;

	ret = unlink(file);
	assert(0 <= ret);

	return 0;
}

static __attribute__((__noreturn__)) void
usage(int retval)
{
	fprintf(stderr, "Usage: %s [-D macro[=def]] [-E] [-I dir] [-O level] [-U macro] [-S] [-c] -b arch [-o output] [-print-libfaucc-file-name] file...\n", progname);
	exit(retval);
}

int
main(int argc, char **argv)
{
	unsigned int i;

#if 0
	for (i = 0; i < argc; i++) {
		fprintf(stderr, "%u: '%s'\n", i, argv[i]);
	}
#endif

	progname = *argv;

	ncpp_params = 0;
	cpp_param[ncpp_params++] = "cpp";
	ncc1_params = 0;
	cc1_param[ncc1_params++] = "cc1";
	nas_params = 0;
	as_param[nas_params++] = "gcc";
	as_param[nas_params++] = "-m32";
	nld_params = 0;
	ld_param[nld_params++] = "gcc";
	ld_param[nld_params++] = "-m32";

	for (i = 1; i < argc; i++) {
		if (strcmp(argv[i], "-B") == 0) {
			opt_B = argv[++i];
		} else if (strncmp(argv[i], "-B", 2) == 0) {
			opt_B = &argv[i][2];

		} else if (strcmp(argv[i], "-D") == 0) {
			cpp_param[ncpp_params++] = "-D";
			cpp_param[ncpp_params++] = argv[++i];
		} else if (strncmp(argv[i], "-D", 2) == 0) {
			cpp_param[ncpp_params++] = "-D";
			cpp_param[ncpp_params++] = &argv[i][2];

		} else if (strcmp(argv[i], "-E") == 0) {
			opt_E = 1;

		} else if (strcmp(argv[i], "-I") == 0) {
			cpp_param[ncpp_params++] = "-I";
			cpp_param[ncpp_params++] = argv[++i];
		} else if (strncmp(argv[i], "-I", 2) == 0) {
			cpp_param[ncpp_params++] = "-I";
			cpp_param[ncpp_params++] = &argv[i][2];

		} else if (strcmp(argv[i], "-L") == 0) {
			ld_param[nld_params++] = "-L";
			ld_param[nld_params++] = argv[++i];
		} else if (strncmp(argv[i], "-L", 2) == 0) {
			ld_param[nld_params++] = "-L";
			ld_param[nld_params++] = &argv[i][2];

		} else if (strcmp(argv[i], "-MD") == 0) {
			cpp_param[ncpp_params++] = "-MD";

		} else if (strcmp(argv[i], "-MF") == 0) {
			cpp_param[ncpp_params++] = "-MF";
			cpp_param[ncpp_params++] = argv[++i];
		} else if (strncmp(argv[i], "-MF", 3) == 0) {
			cpp_param[ncpp_params++] = "-MF";
			cpp_param[ncpp_params++] = &argv[i][2];

		} else if (strcmp(argv[i], "-MP") == 0) {
			cpp_param[ncpp_params++] = "-MP";

		} else if (strcmp(argv[i], "-MT") == 0) {
			cpp_param[ncpp_params++] = "-MT";
			cpp_param[ncpp_params++] = argv[++i];
		} else if (strncmp(argv[i], "-MT", 3) == 0) {
			cpp_param[ncpp_params++] = "-MT";
			cpp_param[ncpp_params++] = &argv[i][2];

		} else if (strcmp(argv[i], "-O") == 0) {
			cc1_param[ncc1_params++] = "-O";
			cc1_param[ncc1_params++] = argv[++i];
		} else if (strncmp(argv[i], "-O", 2) == 0) {
			cc1_param[ncc1_params++] = "-O";
			cc1_param[ncc1_params++] = &argv[i][2];

		} else if (strcmp(argv[i], "-S") == 0) {
			opt_S = 1;

		} else if (strcmp(argv[i], "-T") == 0) {
			ld_param[nld_params++] = "-T";
			ld_param[nld_params++] = argv[++i];

		} else if (strcmp(argv[i], "-U") == 0) {
			cpp_param[ncpp_params++] = "-U";
			cpp_param[ncpp_params++] = argv[++i];
		} else if (strncmp(argv[i], "-U", 2) == 0) {
			cpp_param[ncpp_params++] = "-U";
			cpp_param[ncpp_params++] = &argv[i][2];

		} else if (strncmp(argv[i], "-Wl,", 4) == 0) {
			ld_param[nld_params++] = &argv[i][4];

		} else if (strcmp(argv[i], "-Xlinker") == 0) {
			ld_param[nld_params++] = "-Xlinker";
			ld_param[nld_params++] = argv[++i];

		} else if (strcmp(argv[i], "-b") == 0) {
			cc1_param[ncc1_params++] = "-b";
			cc1_param[ncc1_params++] = argv[++i];
		} else if (strncmp(argv[i], "-b", 2) == 0) {
			cc1_param[ncc1_params++] = "-b";
			cc1_param[ncc1_params++] = &argv[i][2];

		} else if (strcmp(argv[i], "-c") == 0) {
			opt_c = 1;

		} else if (strcmp(argv[i], "-f") == 0) {
			cc1_param[ncc1_params++] = "-f";
			cc1_param[ncc1_params++] = argv[++i];
		} else if (strncmp(argv[i], "-f", 2) == 0) {
			cc1_param[ncc1_params++] = "-f";
			cc1_param[ncc1_params++] = &argv[i][2];

		} else if (strcmp(argv[i], "-m") == 0) {
			cc1_param[ncc1_params++] = "-m";
			cc1_param[ncc1_params++] = argv[++i];
		} else if (strncmp(argv[i], "-m", 2) == 0) {
			cc1_param[ncc1_params++] = "-m";
			cc1_param[ncc1_params++] = &argv[i][2];

		} else if (strcmp(argv[i], "-nostdlib") == 0) {
			ld_param[nld_params++] = "-nostdlib";

		} else if (strcmp(argv[i], "-o") == 0) {
			opt_o = 1;
			opt_o_arg = argv[++i];
			ld_param[nld_params++] = "-o";
			ld_param[nld_params++] = opt_o_arg;
		} else if (strncmp(argv[i], "-o", 2) == 0) {
			opt_o = 1;
			opt_o_arg = &argv[i][2];
			ld_param[nld_params++] = "-o";
			ld_param[nld_params++] = opt_o_arg;

		} else if (strcmp(argv[i], "-print-libfaucc-file-name") == 0) {
			printf("%s/libfaucc.a\n", opt_B);
			exit(0);

		} else if (strcmp(argv[i], "-static") == 0) {
			ld_param[nld_params++] = "-static";

		} else if (strcmp(argv[i], "-no-pie") == 0) {
			ld_param[nld_params++] = "-no-pie";

		} else if (strcmp(argv[i], "-static") == 0) {
			ld_param[nld_params++] = "-static";

		} else if (strcmp(argv[i], "--freestanding") == 0) {
			/* FIXME */

		} else if (strcmp(argv[i], "--no-stack-protector") == 0) {
			/* FIXME */

		} else if (argv[i][0] == '-') {
			fprintf(stderr, "%s: Unknown option %s.\n",
					progname, argv[i]);
			usage(1);

		} else {
			char file[256];
			char *suffix;
			char file_o[256];

			strcpy(file, argv[i]);
			if (strrchr(file, '.')) {
				*strrchr(file, '.') = '\0';
			}
			suffix = strrchr(argv[i], '.');
			assert(suffix);

			input[ninputs++] = argv[i];
			if (strcmp(suffix, ".a") == 0
			 || strcmp(suffix, ".o") == 0) {
				output[noutputs] = argv[i];
			} else {
				tmpname(file_o, "o");
				output[noutputs] = strdup(file_o);
			}

			ld_param[nld_params++] = output[noutputs++];
		}
	}

	/*
	 * Check options.
	 */
	if (1 < opt_E + opt_S + opt_c) {
		fprintf(stderr, "%s: Only one of -E/-S/-c allowed.\n", progname);
		exit(1);
	}
	if (opt_o
	 && 1 < ninputs
	 && (opt_E
	  || opt_S
	  || opt_c)) {
		fprintf(stderr, "%s: bad usage of -o.\n", progname);
		exit(1);
	}

	/*
	 * Check parameter.
	 */
	if (ninputs == 0) {
		fprintf(stderr, "%s: no input files.\n", progname);
		exit(1);
	}

	if (opt_E + opt_S + opt_c == 0) {
		opt_o = 0; /* -o part of ld_param. */
	}

	for (i = 0; i < ninputs; i++) {
		char file[256];
		const char *suffix;
		char file_c[256];
		char file_i[256];
		char file_S[256];
		char file_s[256];
		char file_o[256];

		strcpy(file, input[i]);
		if (strrchr(file, '.')) {
			*strrchr(file, '.') = '\0';
		}
		suffix = strrchr(input[i], '.');
		assert(suffix);

		if (strcmp(suffix, ".c") == 0) {
			if (opt_E) {
				sprintf(file_c, "%s.c", file);
				if (opt_o) {
					sprintf(file_i, "%s", opt_o_arg);
				} else {
					strcpy(file_i, "");
				}

				cpp(file_c, file_i);

			} else if (opt_S) {
				sprintf(file_c, "%s.c", file);
				tmpname(file_i, "i");
				if (opt_o) {
					sprintf(file_s, "%s", opt_o_arg);
				} else {
					sprintf(file_s, "%s.s", file);
				}

				cpp(file_c, file_i);
				cc1(file_i, file_s);

				rm(file_i);

			} else if (opt_c) {
				sprintf(file_c, "%s.c", file);
				tmpname(file_i, "i");
				tmpname(file_s, "s");
				if (opt_o) {
					sprintf(file_o, "%s", opt_o_arg);
				} else {
					sprintf(file_o, "%s.o", file);
				}

				cpp(file_c, file_i);
				cc1(file_i, file_s);
				as(file_s, file_o);

				rm(file_i);
				rm(file_s);

			} else {
				sprintf(file_c, "%s.c", file);
				tmpname(file_i, "i");
				tmpname(file_s, "s");
				if (opt_o) {
					sprintf(file_o, "%s", opt_o_arg);
				} else {
					strcpy(file_o, output[i]);
				}

				cpp(file_c, file_i);
				cc1(file_i, file_s);
				as(file_s, file_o);

				rm(file_i);
				rm(file_s);
			}

		} else if (strcmp(suffix, ".i") == 0) {
			if (opt_E) {
				/* Nothing to do... */

			} else if (opt_S) {
				sprintf(file_i, "%s.i", file);
				if (opt_o) {
					sprintf(file_s, "%s", opt_o_arg);
				} else {
					sprintf(file_s, "%s.s", file);
				}

				cc1(file_i, file_s);

			} else if (opt_c) {
				sprintf(file_i, "%s.i", file);
				tmpname(file_s, "s");
				if (opt_o) {
					sprintf(file_o, "%s", opt_o_arg);
				} else {
					sprintf(file_o, "%s.o", file);
				}

				cc1(file_i, file_s);
				as(file_s, file_o);

				rm(file_s);

			} else {
				sprintf(file_i, "%s.i", file);
				tmpname(file_s, "s");
				if (opt_o) {
					sprintf(file_o, "%s", opt_o_arg);
				} else {
					strcpy(file_o, output[i]);
				}

				cc1(file_i, file_s);
				as(file_s, file_o);

				rm(file_s);
			}

		} else if (strcmp(suffix, ".S") == 0) {
			if (opt_E) {
				sprintf(file_S, "%s.S", file);
				if (opt_o) {
					sprintf(file_i, "%s", opt_o_arg);
				} else {
					strcpy(file_i, "");
				}

				cpp(file_S, file_i);

			} else if (opt_S) {
				sprintf(file_S, "%s.S", file);
				sprintf(file_s, "%s.s", file);
				if (opt_o) {
					sprintf(file_s, "%s", opt_o_arg);
				} else {
					sprintf(file_s, "%s.s", file);
				}

				cpp(file_S, file_s);

			} else if (opt_c) {
				sprintf(file_S, "%s.S", file);
				tmpname(file_s, "s");
				if (opt_o) {
					sprintf(file_o, "%s", opt_o_arg);
				} else {
					sprintf(file_o, "%s.o", file);
				}

				cpp(file_S, file_s);
				as(file_s, file_o);

				rm(file_s);

			} else {
				sprintf(file_S, "%s.S", file);
				tmpname(file_s, "s");
				if (opt_o) {
					sprintf(file_o, "%s", opt_o_arg);
				} else {
					strcpy(file_o, output[i]);
				}

				cpp(file_S, file_s);
				as(file_s, file_o);

				rm(file_s);
			}

		} else if (strcmp(suffix, ".s") == 0) {
			if (opt_E) {
				/* Nothing to do... */

			} else if (opt_S) {
				/* Nothing to do... */

			} else if (opt_c) {
				sprintf(file_s, "%s.s", file);
				if (opt_o) {
					sprintf(file_o, "%s", opt_o_arg);
				} else {
					sprintf(file_o, "%s.o", file);
				}

				as(file_s, file_o);

			} else {
				sprintf(file_s, "%s.s", file);
				if (opt_o) {
					sprintf(file_o, "%s", opt_o_arg);
				} else {
					strcpy(file_o, output[i]);
				}

				as(file_s, file_o);
			}

		} else if (strcmp(suffix, ".o") == 0
			|| strcmp(suffix, ".a") == 0) {
			/* Nothing to do... */

		} else {
			fprintf(stderr, "%s: ERROR: Unknown input %s.\n",
					progname, input[i]);
			assert(0); /* FIXME */
		}
	}
	if (opt_E + opt_S + opt_c == 0) {
		/*
		 * Do linking.
		 */
		ld();
	}

	return 0;
}
