diff lwcc/driver-main.c @ 495:5b8871fd7503

Merged previous lwcc development branch into mainline.
author William Astle <lost@l-w.ca>
date Mon, 05 Aug 2019 21:27:09 -0600
parents 4b17780f2777
children a38542cf4cc6
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lwcc/driver-main.c	Mon Aug 05 21:27:09 2019 -0600
@@ -0,0 +1,1072 @@
+/*
+lwcc/driver/main.c
+
+Copyright © 2013 William Astle
+
+This file is part of LWTOOLS.
+
+LWTOOLS is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <lw_alloc.h>
+#include <lw_string.h>
+#include <lw_stringlist.h>
+
+#define VERSTRING "lwcc from " PACKAGE_STRING
+#define S(x) S2(x)
+#define S2(x) #x
+
+#define BASEDIR S(LWCC_LIBDIR)
+
+/* list of compilation phases */
+enum phase_t {
+	PHASE_DEFAULT = 0,
+	PHASE_PREPROCESS,
+	PHASE_COMPILE,
+	PHASE_ASSEMBLE,
+	PHASE_LINK
+};
+
+/* these are the names of various programs the compiler calls */
+const char *linker_program_name = "lwlink";
+const char *compiler_program_name = "lwcc1";
+const char *assembler_program_name = "lwasm";
+const char *preprocessor_program_name = "lwcpp";
+
+/* this will be set to the directory where temporary files get created */
+const char *temp_directory = NULL;
+
+/* these are for book keeping if we get interrupted - the volatile and atomic
+   types are needed because they are accessed in a signal handler */
+static volatile sig_atomic_t sigterm_received = 0;
+static volatile sig_atomic_t child_pid = 0;
+
+/* path specified with --sysroot */
+const char *sysroot = "";
+/* path specified with -isysroot */
+const char *isysroot = NULL;
+
+/* record which phase to stop after for -c, -E, and -S */
+/* default is to stop after PHASE_LINK */
+static int stop_after = PHASE_DEFAULT;
+
+int nostdinc = 0;				// set if -nostdinc is specified
+int nostartfiles = 0;			// set if -nostartfiles is specified
+int nostdlib = 0;				// set if -nostdlib is specified
+int verbose_mode = 0;			// set to number of --verbose arguments
+int save_temps = 0;				// set if -save-temps is specified
+int debug_mode = 0;				// set if -g specified
+int pic_mode = 0;				// set to 1 if -fpic, 2 if -fPIC; last one specified wins
+const char *output_file;		// set to the value of the -o option (output file)
+
+/* compiler base directory  - from -B */
+const char *basedir = BASEDIR;
+
+/* used to ensure a unique temporary file at every stage */
+static int file_counter = 0;
+
+/* these are various string lists used to keep track of things, mostly
+   command line arguments. */
+
+lw_stringlist_t input_files;		// input files from command line
+lw_stringlist_t runtime_dirs;		// directories to search for runtime files
+lw_stringlist_t lib_dirs;			// directories to search for library files
+lw_stringlist_t program_dirs;		// directories to search for compiler program components
+lw_stringlist_t preproc_args;		// recorded arguments to pass through to the preprocessor
+lw_stringlist_t include_dirs;		// include paths specified with -I
+lw_stringlist_t includes;			// include paths specified with -include
+lw_stringlist_t user_sysincdirs;	// include paths specified with -isystem
+lw_stringlist_t asm_args;			// recorded arguments to pass through to the assembler
+lw_stringlist_t linker_args;		// recorded arguments to pass through to the linker 
+lw_stringlist_t sysincdirs;			// the standard system include directories
+lw_stringlist_t tempfiles;			// a list of temporary files created which need to be cleaned up
+lw_stringlist_t compiler_args;		// recorded arguments to pass through to the compiler
+lw_stringlist_t priv_sysincdirs;	// system include directories for lwcc itself
+
+/* forward delcarations */
+static void parse_command_line(int, char **);
+
+/* signal handler for SIGTERM - all it does is record the fact that
+   SIGTERM happened and propagate the signal to whatever child process
+   might currently be running */
+static void exit_on_signal(int sig)
+{
+	sigterm_received = 1;
+	if (child_pid)
+		kill(child_pid, SIGTERM);
+}
+
+/* utility function to carp about an error condition and bail */
+void do_error(const char *f, ...)
+{
+	va_list arg;
+	va_start(arg, f);
+	fprintf(stderr, "ERROR: ");
+	vfprintf(stderr, f, arg);
+	putc('\n', stderr);
+	va_end(arg);
+	exit(1);
+}
+
+/* utility function to carp about some condition; do not bail */
+void do_warning(const char *f, ...)
+{
+	va_list arg;
+	va_start(arg, f);
+	fprintf(stderr, "WARNING: ");
+	vfprintf(stderr, f, arg);
+	putc('\n', stderr);
+	va_end(arg);
+}
+
+/* utility function to print out an array of strings - stops at the first
+   NULL string pointer. */
+static void print_array(char **arr)
+{
+	int c = 0;
+	while (*arr)
+	{
+		if (c)
+			printf(" ");
+		printf("%s", *arr);
+		arr++;
+		c = 1;
+	}
+}
+
+/* expand any search path entries to reflect the sysroot and
+   isysroot settings. Note that it does NOT apply to the compiler
+   program search path */
+static void expand_sysroot(void)
+{
+	/* list of path lists to process for replacements of = */
+	lw_stringlist_t *lists[] = { &sysincdirs, &include_dirs, &user_sysincdirs, &lib_dirs, NULL };
+	/* list of replacement strings for = in the same order */
+	const char *sysroots[] = { isysroot, isysroot, isysroot, sysroot, NULL };
+	size_t i, sysroot_len, value_len;
+	char *path;
+	lw_stringlist_t newlist;
+	lw_stringlist_t working;
+	char *s;
+	
+	/* for each list, run through entry by entry, do any needed replacement
+	   and add the entry to a new list. Then replace the old list with the
+	   new one. */
+	for (i = 0; lists[i] != NULL; i++)
+	{
+		working = *lists[i];
+		newlist = lw_stringlist_create();
+		
+		lw_stringlist_reset(working);
+		for (s = lw_stringlist_current(working); s; s = lw_stringlist_next(working))
+		{
+			if (s[0] == '=')
+			{
+				sysroot_len = strlen(sysroots[i]);
+				value_len = strlen(s);
+				/* note that the skipped = will make up for the trailing NUL */
+				path = lw_alloc(sysroot_len + value_len);
+				memcpy(path, sysroots[i], sysroot_len);
+				/* the +1 here will copy the trailing NUL */
+				memcpy(path + sysroot_len, s + 1, value_len);
+				lw_stringlist_addstring(newlist, path);
+				lw_free(path);
+			}
+			else
+			{
+				lw_stringlist_addstring(newlist, s);
+			}
+		}
+		lw_stringlist_destroy(working);
+		*lists[i] = newlist;
+	}
+}
+
+/* look for file fn in path list p which is okay for access mode mode.
+   Return a string allocated by lw_alloc. */
+static char *find_file(const char *fn, lw_stringlist_t p, int mode)
+{
+	char *s;
+	char *f;
+	size_t lf, lp;
+	int need_slash;
+	
+	lf = strlen(fn);
+	lw_stringlist_reset(p);
+	for (s = lw_stringlist_current(p); s; s = lw_stringlist_next(p))
+	{
+		lp = strlen(s);
+		need_slash = 0;
+		if (lp && s[lp - 1] == '/')
+			need_slash = 1;
+		f = lw_alloc(lp + lf + need_slash + 1);
+		memcpy(f, s, lp);
+		if (need_slash)
+			f[lp] = '/';
+		/* +1 gets the NUL */
+		memcpy(f + lp + need_slash, fn, lf + 1);
+		if (access(f, mode) == 0)
+			return f;
+		lw_free(f);
+	}
+	/* if not found anywhere, try the bare filename - it might work */
+	return lw_strdup(fn);
+}
+
+/* take a string list which contains an argv and execute the specified
+   program */
+static int execute_program(lw_stringlist_t args)
+{
+	int argc;
+	char **argv;
+	int result;
+	char *s;
+	
+	argc = lw_stringlist_nstrings(args);
+	argv = lw_alloc(sizeof(char *) * (argc + 1));
+	lw_stringlist_reset(args);
+	for (result = 0, s = lw_stringlist_current(args); s; s = lw_stringlist_next(args))
+	{
+		argv[result] = s;
+	}
+	argv[result] = NULL;
+
+	if (verbose_mode)
+	{
+		printf("Executing ");
+		print_array(argv);
+		printf("\n");
+	}
+	
+	/* bail now if a signal happened */
+	if (sigterm_received)
+	{
+		lw_free(argv);
+		return 1;
+	}
+
+	/* make sure stdio has flushed everything so that output from the
+	   child process doesn't get intermingled */
+	fflush(NULL);
+	
+	/* now make the child process */
+	child_pid = fork();
+	if (child_pid == 0)
+	{
+		/* child process */
+		/* try executing program */
+		execvp(argv[0], argv);
+		/* only way to get here is if execvp() failed so carp about it and exit */
+		fprintf(stderr, "Exec of %s failed: %s", argv[0], strerror(errno));
+		/* exit with failure but don't call any atexit(), etc., functions */
+		_exit(127);
+	}
+	else if (child_pid == -1)
+	{
+		/* failure to make child process */
+		do_error("Failed to execute program %s: %s", argv[0], strerror(errno));
+	}
+	/* clean up argv */
+	lw_free(argv);
+	
+	/* parent process - wait for child to exit */
+	while (waitpid(child_pid, &result, 0) == -1 && errno == EINTR)
+		/* do nothing */;
+	/* fetch actual return status */
+	result = WEXITSTATUS(result);
+	if (result)
+	{
+		/* carp about non-zero return status */
+		do_error("%s terminated with status %d", argv[0], result);
+	}
+	/* return nonzero if signalled to exit */
+	return sigterm_received;
+}
+
+/*
+construct an output file name as follows:
+
+1. if it is the last phase of compilation and an output file name is
+   specified, use that if not specified
+2. if it is the last phase or we are saving temporary files, any suffix
+   on f is removed and replaced with nsuffix
+3. otherwise, a temporary file is created. If necessary, a temporary
+   directory is created to hold the temporary file. The name of the temporary
+   file is recorded in the tempfiles string list for later cleanup. The name
+   of the temporary directory is recorded in temp_directory for later cleanup.
+*/
+static char *output_name(const char *f, const char *nsuffix, int last)
+{
+	const char *osuffix;
+	char *name;
+	size_t lf, ls, len;
+	int counter_len;
+	
+	/* get a new file counter */
+	file_counter++;
+	
+	/* if the output was specified, use it */
+	if (last && output_file)
+	{
+		return lw_strdup(output_file);
+	}
+
+	/* find the start of the old suffix */	
+	osuffix = strrchr(f, '.');
+	if (osuffix != NULL && strchr(osuffix, '/') != NULL)
+		osuffix = NULL;
+	if (osuffix == NULL)
+		osuffix = f + strlen(f);
+	
+	ls = strlen(nsuffix);
+	
+	/* if this is the last stage or we're saving temps, use a name derived
+	   from the original file name by replacing the suffix with nsuffix */
+	if (save_temps || last)
+	{
+		lf = osuffix - f;
+		name = lw_alloc(lf + ls + 1);
+		memcpy(name, f, lf);
+		/* note that the +1 will copy the trailing NUL */
+		memcpy(name + lf, nsuffix, ls + 1);
+		return name;
+	}
+
+	/* finally, use a temporary file */
+	if (temp_directory == NULL)
+	{
+		/* if we haven't already made a temporary directory, do so */
+		const char *dirtempl;
+		char *path;
+		size_t dirtempl_len;
+		int need_slash;
+		
+		/* look for a TMPFIR environment variable and use that if present
+		   but use /tmp as a fallback */
+		dirtempl = getenv("TMPDIR");
+		if (dirtempl == NULL)
+			dirtempl = "/tmp";
+		dirtempl_len = strlen(dirtempl);
+		/* work out if we need to add a slash on the end of the directory */
+		if (dirtempl_len && dirtempl[dirtempl_len - 1] == '/')
+			need_slash = 0;
+		else
+			need_slash = 1;
+		/* make a string of the form <tempdir>/lwcc-XXXXXX */
+		path = lw_alloc(dirtempl_len + need_slash + 11 + 1);
+		memcpy(path, dirtempl, dirtempl_len);
+		if (need_slash)
+			path[dirtempl_len] = '/';
+		memcpy(path + dirtempl_len + need_slash, "lwcc-XXXXXX", 12);
+		/* now make a temporary directory */
+		if (mkdtemp(path) == NULL)
+			do_error("mkdtemp failed: %s", strerror(errno));
+		/* record the temporary directory name */
+		temp_directory = path;
+	}
+	/* now create a file name in the temporary directory. The strategy here
+	   uses a counter that is passed along and is guaranteed to be unique for
+	   every file requested. */
+	lf = strlen(temp_directory);
+	/* this gets the length of the counter as a string but doesn't actually
+	   allocate anything so we can make a string long enough */
+	counter_len = snprintf(NULL, 0, "%d", file_counter);
+	if (counter_len < 1)
+		do_error("snprintf failure: %s", strerror(errno));
+	len = lf + 1 + (size_t)counter_len + ls + 1;
+	name = lw_alloc(len);
+	/* it should be impossible for ths snprintf call to fail */
+	snprintf(name, len, "%s/%d%s", temp_directory, file_counter, nsuffix);
+	
+	/* record the temporary file name for later */
+	lw_stringlist_addstring(tempfiles, name);
+	return name;
+}
+
+/* this calls the actual compiler, passing the contents of compiler_args
+   as arguments. It also adds the input file and output file. */
+static int compile_file(const char *file, char *input, char **output, const char *suffix)
+{
+	lw_stringlist_t args;
+	char *out;
+	int retval;
+	char *s;
+	
+	args = lw_stringlist_create();
+	
+	/* find the compiler executable and make that argv[0] */
+	s = find_file(compiler_program_name, program_dirs, X_OK);
+	lw_stringlist_addstring(args, s);
+	lw_free(s);
+	
+	/* add all the saved compiler arguments to argv */
+	lw_stringlist_reset(compiler_args);
+	for (s = lw_stringlist_current(compiler_args); s; s = lw_stringlist_next(compiler_args))
+	{
+		lw_stringlist_addstring(args, s);
+	}
+	/* work out the output file name and add that to argv */
+	out = output_name(file, suffix, stop_after == PHASE_COMPILE);
+	lw_stringlist_addstring(args, "-o");
+	lw_stringlist_addstring(args, out);
+	/* add the input file to argv */
+	lw_stringlist_addstring(args, input);
+	/* if the input file name and the output file name pointers are the same
+	   free the input one */
+	if (*output == input) 
+		lw_free(input);
+	/* tell the caller what the output name is */
+	*output = out;
+	/* actually run the compiler */
+	retval = execute_program(args);
+
+	lw_stringlist_destroy(args);
+	return retval;
+}
+
+/* this calls the actual assembler, passing the contents of asm_args
+   as arguments. It also adds the input file and output file. */
+static int assemble_file(const char *file, char *input, char **output, const char *suffix)
+{
+	lw_stringlist_t args;
+	char *out;
+	int retval;
+	char *s;
+	
+	args = lw_stringlist_create();
+	
+	/* find the assembler binary and add that as argv[0] */
+	s = find_file(assembler_program_name, program_dirs, X_OK);
+	lw_stringlist_addstring(args, s);
+	lw_free(s);
+	
+	/* add asm_args to argv */
+	lw_stringlist_reset(asm_args);
+	for (s = lw_stringlist_current(asm_args); s; s = lw_stringlist_next(asm_args))
+	{
+		lw_stringlist_addstring(args, s);
+	}
+	/* get an output file name and add that to argv */
+	out = output_name(file, ".o", stop_after == PHASE_ASSEMBLE);
+	lw_stringlist_addstring(args, "-o");
+	lw_stringlist_addstring(args, out);
+	/* finally, add the input file */
+	lw_stringlist_addstring(args, input);
+	/* clean up input file name if same as output pointer */
+	if (*output == input)
+		lw_free(input);
+	/* tell caller what file we made */
+	*output = out;
+	/* actually run the assembler */
+	retval = execute_program(args);
+	
+	lw_stringlist_destroy(args);
+	return retval;
+}
+
+/* run the preprocessor. Pass along preproc_args and appropriate options
+   for all the include directories */
+static int preprocess_file(const char *file, char *input, char **output, const char *suffix)
+{
+	lw_stringlist_t args;
+	char *s;
+	char *out;
+	int retval;
+	
+	args = lw_stringlist_create();
+
+	/* find the linker binary and make that argv[0] */	
+	s = find_file(preprocessor_program_name, program_dirs, X_OK);
+	lw_stringlist_addstring(args, s);
+	lw_free(s);
+	
+	/* add preproc_args to argv */
+	lw_stringlist_reset(preproc_args);
+	for (s = lw_stringlist_current(preproc_args); s; s = lw_stringlist_next(preproc_args))
+	{
+		lw_stringlist_addstring(args, s);
+	}
+	
+	/* add the include files specified by -i */
+	lw_stringlist_reset(includes);
+	for (s = lw_stringlist_current(includes); s; s = lw_stringlist_next(includes))
+	{
+		lw_stringlist_addstring(args, "-i");
+		lw_stringlist_addstring(args, s);
+	}
+
+	/* add the include directories specified by -I */
+	lw_stringlist_reset(include_dirs);
+	for (s = lw_stringlist_current(include_dirs); s; s = lw_stringlist_next(include_dirs))
+	{
+		lw_stringlist_addstring(args, "-I");
+		lw_stringlist_addstring(args, s);
+	}
+
+	/* add the user specified system include directories (-isystem) */
+	lw_stringlist_reset(user_sysincdirs);
+	for (s = lw_stringlist_current(user_sysincdirs); s; s = lw_stringlist_next(user_sysincdirs))
+	{
+		lw_stringlist_addstring(args, "-S");
+		lw_stringlist_addstring(args, s);
+	}
+
+	/* and, if not -nostdinc, the standard system include directories */
+	if (!nostdinc)
+	{
+		lw_stringlist_reset(priv_sysincdirs);
+		for (s = lw_stringlist_current(priv_sysincdirs); s; s = lw_stringlist_next(priv_sysincdirs))
+		{	
+			lw_stringlist_addstring(args, "-S");
+			lw_stringlist_addstring(args, s);
+		}
+		lw_stringlist_reset(sysincdirs);
+		for (s = lw_stringlist_current(sysincdirs); s; s = lw_stringlist_next(sysincdirs))
+		{	
+			lw_stringlist_addstring(args, "-S");
+			lw_stringlist_addstring(args, s);
+		}
+	}
+	
+	/* if we stop after preprocessing, output to stdout if no output file */
+	if (stop_after == PHASE_PREPROCESS && output_file == NULL)
+	{
+		out = lw_strdup("-");
+	}
+	else
+	{
+		/* otherwise, make an output file */
+		out = output_name(file, suffix, stop_after == PHASE_PREPROCESS);
+	}
+	/* if not stdout, add the output file to argv */
+	if (strcmp(out, "-") != 0)
+	{
+		lw_stringlist_addstring(args, "-o");
+		lw_stringlist_addstring(args, out);
+	}
+	/* add the input file name to argv */
+	lw_stringlist_addstring(args, input);
+
+	/* if input and output pointers are same, clean up input */	
+	if (*output == input)
+		lw_free(input);
+	/* tell caller what our output file is */
+	*output = out;
+	/* finally, actually run the preprocessor */
+	retval = execute_program(args);
+	
+	lw_stringlist_destroy(args);
+	return retval;
+}
+
+/*
+handle an input file through the various stages of compilation. If any
+stage decides to handle an input file, that fact is recorded. If control
+reaches the end of the function without a file being handled, that
+fact is mentioned to the user. Unknown files are passed to the linker
+if nothing handles them and linking is to be done. It's possible the linker
+will actually know what to do with them.
+*/
+static int handle_input_file(const char *f)
+{
+	const char *suffix;
+	char *src;
+	int handled, retval;
+	
+	/* note: this needs to handle -x but for now, assume c for stdin */	
+	if (strcmp(f, "-") == 0)
+	{
+		suffix = ".c";
+	}
+	else
+	{
+		/* work out the suffix on the file */
+		suffix = strrchr(f, '.');
+		if (suffix != NULL && strchr(suffix, '/') != NULL)
+			suffix = NULL;
+		if (suffix == NULL)
+			suffix = "";
+	}
+	
+	/* make a copy of the file */
+	src = lw_strdup(f);
+	
+	/* preprocess if appropriate */
+	if (strcmp(suffix, ".c") == 0)
+	{
+		/* preprocessed c input source goes to .i */
+		suffix = ".i";
+		retval = preprocess_file(f, src, &src, suffix);
+		if (retval)
+			goto done;
+		handled = 1;
+	}
+	else if (strcmp(suffix, ".S") == 0)
+	{
+		/* preprocessed asm source goes to .s */
+		suffix = ".s";
+		retval = preprocess_file(f, src, &src, suffix);
+		if (retval)
+			goto done;
+		handled = 1;
+	}
+	/* if we're only preprocessing, bail */
+	if (stop_after == PHASE_PREPROCESS)
+		goto done;
+	
+	/* now on to compile if appropriate */
+	if (strcmp(suffix, ".i") == 0)
+	{
+		/* preprocessed c source goes to .s after compiling */
+		suffix = ".s";
+		retval = compile_file(f, src, &src, suffix);
+		if (retval)
+			goto done;
+		handled = 1;
+	}
+	/* bail if we're only compiling, not assembling */
+	if (stop_after == PHASE_COMPILE)
+		goto done;
+	
+	/* assemble if appropriate */
+	if (strcmp(suffix, ".s") == 0)
+	{
+		/* assembler output is an object file */
+		suffix = ".o";
+		retval = assemble_file(f, src, &src, suffix);
+		if (retval)
+			goto done;
+		handled = 1;
+	}
+	/* bail if we're not linking */
+	if (stop_after == PHASE_ASSEMBLE)
+		goto done;
+
+	/* if we get here with a .o unhandled, pretend it is handled */	
+	if (strcmp(suffix, ".o") == 0)
+		handled = 1;
+	
+	/* add the final file name to the linker args */
+	lw_stringlist_addstring(linker_args, src);
+done:
+	if (!handled && !retval)
+	{
+		/* carp about unhandled files if there is no error */
+		if (stop_after == PHASE_LINK)
+		{
+			do_warning("unknown suffix %s; passing file down to linker", suffix);
+		}
+		else
+		{
+			do_warning("unknown suffix %s; skipped", suffix);
+		}
+	}
+	/* clean up the file name */
+	lw_free(src);
+	
+	return retval;
+}
+
+/*
+This actually runs the linker. Along the way, all the files the linker
+is supposed to handle will have been added to linker_args.
+*/
+static int handle_linking(void)
+{
+	lw_stringlist_t linker_flags;
+	char *s;
+	int retval;
+	
+	linker_flags = lw_stringlist_create();
+	
+	/* find the linker binary and make that argv[0] */
+	s = find_file(linker_program_name, program_dirs, X_OK);
+	lw_stringlist_addstring(linker_flags, s);
+	lw_free(s);
+	
+	/* tell the linker about the output file name, if specified */
+	if (output_file)
+	{
+		lw_stringlist_addstring(linker_flags, "-o");
+		lw_stringlist_addstring(linker_flags, (char *)output_file);
+	}
+	
+	/* add the standard library options if not -nostdlib */
+	if (!nostdlib)
+	{
+	}
+	
+	/* add the standard startup files if not -nostartfiles */
+	if (!nostartfiles)
+	{
+	}
+	
+	/* pass along the various input files, etc., to the linker */
+	lw_stringlist_reset(linker_args);
+	for (s = lw_stringlist_current(linker_args); s; s = lw_stringlist_next(linker_args))
+	{
+		lw_stringlist_addstring(linker_flags, s);
+	}
+	
+	/* actually run the linker */
+	retval = execute_program(linker_flags);
+	
+	lw_stringlist_destroy(linker_flags);
+	return retval;
+}
+
+/*
+Do various setup tasks, process the command line, handle the input files,
+and clean up.
+*/
+int main(int argc, char **argv)
+{
+	char *ap;
+	int retval;
+	
+	input_files = lw_stringlist_create();
+	runtime_dirs = lw_stringlist_create();
+	lib_dirs = lw_stringlist_create();
+	program_dirs = lw_stringlist_create();
+	preproc_args = lw_stringlist_create();
+	include_dirs = lw_stringlist_create();
+	user_sysincdirs = lw_stringlist_create();
+	asm_args = lw_stringlist_create();
+	linker_args = lw_stringlist_create();
+	sysincdirs = lw_stringlist_create();
+	includes = lw_stringlist_create();
+	tempfiles = lw_stringlist_create();
+	compiler_args = lw_stringlist_create();
+	priv_sysincdirs = lw_stringlist_create();
+		
+	parse_command_line(argc, argv);
+	if (stop_after == PHASE_DEFAULT)
+		stop_after = PHASE_LINK;
+
+	if (verbose_mode)
+		printf("%s\n", VERSTRING);
+
+	if (isysroot == NULL)
+		isysroot = sysroot;
+	expand_sysroot();
+	
+	if (stop_after != PHASE_LINK && output_file && lw_stringlist_nstrings(input_files) > 1)
+	{
+		do_error("-o cannot be specified with multiple inputs unless linking");
+	}
+	
+	// default to stdout for preprocessing
+	if (stop_after == PHASE_PREPROCESS && output_file == NULL)
+		output_file = "-";
+	
+	if (lw_stringlist_nstrings(input_files) == 0)
+		do_error("No input files specified");
+
+	/* handle -B here */
+	ap = lw_alloc(strlen(basedir) + 10);
+	strcpy(ap, basedir);
+	strcat(ap, "/bin");
+	lw_stringlist_addstring(program_dirs, ap);
+	strcpy(ap, basedir);
+	strcat(ap, "/lib");
+	lw_stringlist_addstring(runtime_dirs, ap);
+	strcpy(ap, basedir);
+	strcat(ap, "/include");
+	lw_stringlist_addstring(priv_sysincdirs, ap);
+	lw_free(ap);
+	
+	retval = 0;
+	/* make sure we exit if interrupted */
+	signal(SIGTERM, exit_on_signal);
+	
+	/* handle input files */
+	lw_stringlist_reset(input_files);
+	for (ap = lw_stringlist_current(input_files); ap; ap = lw_stringlist_next(input_files))
+	{
+		if (handle_input_file(ap))
+			retval = 1;
+	}
+
+	if (!retval && stop_after >= PHASE_LINK)
+	{
+		retval = handle_linking();
+	}
+
+	/* if a signal nixed us, mention the fact */
+	if (sigterm_received)
+		do_warning("Terminating on signal");
+
+	/* clean up temporary files */
+	if (!save_temps)
+	{
+		lw_stringlist_reset(tempfiles);
+		for (ap = lw_stringlist_current(tempfiles); ap; ap = lw_stringlist_next(tempfiles))
+		{
+			if (unlink(ap) == -1)
+			{
+				do_warning("Removal of %s failed: %s", ap, strerror(errno));
+			}
+		}
+		if (temp_directory)
+		{
+			if (rmdir(temp_directory) == -1)
+			{
+				do_warning("Removal of temporary directory %s failed: %s", temp_directory, strerror(errno));
+			}
+		}
+	}
+
+	/* be polite and clean up all the string lists */
+	lw_stringlist_destroy(input_files);
+	lw_stringlist_destroy(runtime_dirs);
+	lw_stringlist_destroy(lib_dirs);
+	lw_stringlist_destroy(program_dirs);
+	lw_stringlist_destroy(preproc_args);
+	lw_stringlist_destroy(include_dirs);
+	lw_stringlist_destroy(user_sysincdirs);
+	lw_stringlist_destroy(asm_args);
+	lw_stringlist_destroy(linker_args);
+	lw_stringlist_destroy(sysincdirs);
+	lw_stringlist_destroy(includes);
+	lw_stringlist_destroy(tempfiles);
+	lw_stringlist_destroy(compiler_args);
+	lw_stringlist_destroy(priv_sysincdirs);
+	return retval;	
+}
+
+struct option_e
+{
+	char *optbase;				// base name of option, with -
+	int needarg;				// nonzero if option needs argument
+	int noextra;				// nonzero if there must not be anything after optbase
+	int optcode;				// option code (passed to fn)
+	void *optptr;				// pointer for opt (passed to fn)
+	int (*fn)(char *, char *, int, void *); // function to handle argument, NULL to ignore it
+};
+
+enum CMD_MISC {
+	CMD_MISC_VERSION,
+	CMD_MISC_OPTIMIZE,
+};
+
+enum OPT_ARG {
+	OPT_ARG_OPT = 0,			// argument is optional
+	OPT_ARG_SEP = 1,			// argument may be separate
+	OPT_ARG_INC = 2,			// argument must not be separate
+};
+
+/* set an integer at *optptr to optcode */
+static int cmdline_set_int(char *opt, char *optarg, int optcode, void *optptr)
+{
+	*((int *)optptr) = optcode;
+	return 0;
+}
+
+/* set a string at *optptr to optarg */
+static int cmdline_set_string(char *opt, char *optarg, int optcode, void *optptr)
+{
+	char **s = (char **)optptr;
+	*s = optarg;
+	
+	return 0;
+}
+
+/* set a string at *optptr to optarg */
+static int cmdline_set_stringifnull(char *opt, char *optarg, int optcode, void *optptr)
+{
+	char **s = (char **)optptr;
+	
+	if (*s)
+		do_error("Multiple %.*s options specified", optcode ? optcode : strlen(opt), opt);
+	*s = optarg;
+	
+	return 0;
+}
+
+/* split arg on commas and add the results to string list *optptr */
+static int cmdline_argsplit(char *opt, char *arg, int optcode, void *optptr)
+{
+	lw_stringlist_t l = *(lw_stringlist_t *)optptr;
+	char *next;
+	
+	for (; arg != NULL; arg = next)
+	{
+		next = strchr(arg, ',');
+		if (next != NULL)
+			*next++ = '\0';
+		lw_stringlist_addstring(l, arg);
+	}
+	return 0;
+}
+
+/* add opt to string list *optptr */
+static int cmdline_arglist(char *opt, char *arg, int optcode, void *optptr)
+{
+	lw_stringlist_t l = *(lw_stringlist_t *)optptr;
+
+	lw_stringlist_addstring(l, opt);
+	return 0;
+}
+
+/* add optarg to string list *optptr */
+static int cmdline_optarglist(char *opt, char *optarg, int optcode, void *optptr)
+{
+	lw_stringlist_t l = *(lw_stringlist_t *)optptr;
+
+	lw_stringlist_addstring(l, optarg);
+	return 0;
+}
+
+static int cmdline_misc(char *opt, char *optarg, int optcode, void *optptr)
+{
+	switch (optcode)
+	{
+	case CMD_MISC_VERSION:
+		printf("%s\n", VERSTRING);
+		exit(0);
+
+	case CMD_MISC_OPTIMIZE:
+		if (!optarg)
+			return 0;
+		switch (*optarg)
+		{
+		case '0':
+		case '1':
+		case '2':
+		case '3':
+		case 's':
+			return 0;
+		}
+		return -1;
+
+	default:
+		return -1;
+	}
+	return 0;
+}
+
+static int cmdline_set_intifzero(char *opt, char *optarg, int optcode, void *optptr)
+{
+	int *iv = (int *)optptr;
+	
+	if (*iv && *iv != optcode)
+	{
+		do_error("conflicting compiler option specified: %s", opt);
+	}
+	*iv = optcode;
+	return 0;
+}
+
+struct option_e optionlist[] =
+{
+	{ "--version",			OPT_ARG_OPT, 	1,	CMD_MISC_VERSION, 	NULL,			cmdline_misc },
+	{ "--sysroot=",			OPT_ARG_INC,	0,	0,					&sysroot,		cmdline_set_string },
+	{ "-B",					OPT_ARG_INC,	0,	0,					&basedir,		cmdline_set_string },
+	{ "-C",					OPT_ARG_OPT,	1,	0,					&preproc_args,	cmdline_arglist },
+	{ "-c",					OPT_ARG_OPT,	1,	PHASE_COMPILE,		&stop_after,	cmdline_set_intifzero },
+	{ "-D",					OPT_ARG_INC,	0,	0,					&preproc_args,	cmdline_arglist },
+	{ "-E",					OPT_ARG_OPT,	1,	PHASE_PREPROCESS,	&stop_after,	cmdline_set_intifzero },
+	{ "-fPIC",				OPT_ARG_OPT,	1,	2,					&pic_mode,		cmdline_set_int },
+	{ "-fpic",				OPT_ARG_OPT,	1,	1,					&pic_mode,		cmdline_set_int },
+	{ "-g",					OPT_ARG_OPT,	1,	1,					&debug_mode,	cmdline_set_int },
+	{ "-I",					OPT_ARG_SEP,	0,	0,					&include_dirs,	cmdline_optarglist },
+	{ "-include",			OPT_ARG_SEP,	1,	0,					&includes,		cmdline_optarglist },
+	{ "-isysroot",			OPT_ARG_SEP,	1,	0,					&isysroot,		cmdline_set_string },
+	{ "-isystem",			OPT_ARG_SEP,	1,	0,					&user_sysincdirs, cmdline_optarglist },
+	{ "-M",					OPT_ARG_OPT,	1,	0,					&preproc_args,	cmdline_arglist },
+	{ "-nostartfiles",		OPT_ARG_OPT,	1,	1,					&nostartfiles,	cmdline_set_int },
+	{ "-nostdinc",			OPT_ARG_OPT,	1,	1,					&nostdinc,		cmdline_set_int },
+	{ "-nostdlib",			OPT_ARG_OPT,	1,	1,					&nostdlib,		cmdline_set_int },
+	{ "-O",					OPT_ARG_OPT,	0,	CMD_MISC_OPTIMIZE,	NULL,			cmdline_misc },
+	{ "-o",					OPT_ARG_SEP,	0,	2,					&output_file,	cmdline_set_stringifnull },
+	{ "-S",					OPT_ARG_OPT,	1,	PHASE_ASSEMBLE,		&stop_after,	cmdline_set_intifzero },
+	{ "-save-temps",		OPT_ARG_OPT,	1,	1,					&save_temps,	cmdline_set_int },
+	{ "-trigraphs",			OPT_ARG_OPT,	1,	0,					&preproc_args,	cmdline_arglist },
+	{ "-U",					OPT_ARG_INC,	0,	0,					&preproc_args,	cmdline_arglist },
+	{ "-v",					OPT_ARG_OPT,	1,	1,					&verbose_mode,	cmdline_set_int },
+	{ "-Wp,",				OPT_ARG_INC,	0,	0,					&preproc_args,	cmdline_argsplit },
+	{ "-Wa,",				OPT_ARG_INC,	0,	0,					&asm_args,		cmdline_argsplit },
+	{ "-Wl,",				OPT_ARG_INC,	0,	0,					&linker_args,	cmdline_argsplit },
+	{ "-W",					OPT_ARG_INC,	0,	0,					NULL,			NULL }, /* warning options */
+	{ "-x",					OPT_ARG_SEP,	1,	0,					NULL,			NULL }, /* language options */
+	{ NULL, 0, 0 }
+};
+
+static void parse_command_line(int argc, char **argv)
+{
+	int i, j, olen, ilen;
+	char *optarg;
+	
+	for (i = 1; i < argc; i++)
+	{
+		if (argv[i][0] != '-' || argv[i][1] == '\0')
+		{
+			/* we have a non-option argument */
+			lw_stringlist_addstring(input_files, argv[i]);
+			continue;
+		}
+		olen = strlen(argv[i]);
+		for (j = 0; optionlist[j].optbase; j++)
+		{
+			ilen = strlen(optionlist[j].optbase);
+			/* if length of optbase is longer than argv[i], it can't match */
+			if (ilen > olen)
+				continue;
+			/* does the base match? */
+			if (strncmp(optionlist[j].optbase, argv[i], ilen) == 0)
+				break;
+		}
+		if (optionlist[j].optbase == NULL)
+		{
+			do_error("Unsupported option %s", argv[i]);
+		}
+		/* is the option supposed to be exact? */
+		if (optionlist[j].noextra && argv[i][ilen] != '\0')
+		{
+			do_error("Unsupported option %s", argv[i]);
+		}
+		/* is there an argument? */
+		optarg = NULL;
+		if (argv[i][ilen])
+			optarg = argv[i] + ilen;
+		if (!optarg && optionlist[j].needarg == 1)
+		{
+			if (i == argc)
+			{
+				do_error("Option %s requires an argument", argv[i]);
+			}
+			optarg = argv[++i];
+		}
+		if (!optarg && optionlist[j].needarg == 2)
+		{
+			do_error("Option %s requires an argument", argv[i]);
+		}
+		/* handle the option */
+		if (optionlist[j].fn)
+		{
+			if ((*(optionlist[j].fn))(argv[i], optarg, optionlist[j].optcode, optionlist[j].optptr) != 0)
+				do_error("Unsupported option %s %s", argv[i], optarg ? optarg : "");
+		}
+	}
+}