diff lib/swig/swigwin-2.0.11/CCache/ccache.c @ 1899:b3009adc0e2f

Adding swig, gitignore, hgignore
author Nomad
date Mon, 21 Oct 2013 10:42:27 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/swig/swigwin-2.0.11/CCache/ccache.c	Mon Oct 21 10:42:27 2013 +0200
@@ -0,0 +1,1389 @@
+/*
+  a re-implementation of the compilercache scripts in C
+
+  The idea is based on the shell-script compilercache by Erik Thiele <erikyyy@erikyyy.de>
+
+   Copyright (C) Andrew Tridgell 2002
+   Copyright (C) Martin Pool 2003
+   
+   This program 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 2 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, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "ccache.h"
+
+/* verbose mode */
+int ccache_verbose = 0;
+
+/* the base cache directory */
+char *cache_dir = NULL;
+
+/* the directory for temporary files */
+static char *temp_dir = NULL;
+
+/* the debug logfile name, if set */
+char *cache_logfile = NULL;
+
+/* the argument list after processing */
+static ARGS *stripped_args;
+
+/* the original argument list */
+static ARGS *orig_args;
+
+/* the output filename being compiled to */
+static char *output_file;
+
+/* the source file */
+static char *input_file;
+
+/* the name of the file containing the cached object code */
+static char *hashname;
+
+/* the extension of the file after pre-processing */
+static const char *i_extension;
+
+/* the name of the temporary pre-processor file */
+static char *i_tmpfile;
+
+/* are we compiling a .i or .ii file directly? */
+static int direct_i_file;
+
+/* the name of the cpp stderr file */
+static char *cpp_stderr;
+
+/* the name of the statistics file */
+char *stats_file = NULL;
+
+/* can we safely use the unification hashing backend? */
+static int enable_unify;
+
+/* should we strip -c when running the preprocessor only? */
+static int strip_c_option;
+
+/* customisation for using the SWIG compiler */
+static int swig;
+
+/* a list of supported file extensions, and the equivalent
+   extension for code that has been through the pre-processor
+*/
+static struct {
+	char *extension;
+	char *i_extension;
+} extensions[] = {
+	{"c", "i"},
+	{"C", "ii"},
+	{"m", "mi"},
+	{"cc", "ii"},
+	{"CC", "ii"},
+	{"cpp", "ii"},
+	{"CPP", "ii"},
+	{"cxx", "ii"},
+	{"CXX", "ii"},
+	{"c++", "ii"},
+	{"C++", "ii"},
+	{"i", "i"},
+	{"ii", "ii"},
+	{NULL, NULL}};
+
+/*
+  something went badly wrong - just execute the real compiler
+*/
+static void failed(void)
+{
+	char *e;
+
+	/* delete intermediate pre-processor file if needed */
+	if (i_tmpfile) {
+		if (!direct_i_file) {
+			unlink(i_tmpfile);
+		}
+		free(i_tmpfile);
+		i_tmpfile = NULL;
+	}
+
+	/* delete the cpp stderr file if necessary */
+	if (cpp_stderr) {
+		unlink(cpp_stderr);
+		free(cpp_stderr);
+		cpp_stderr = NULL;
+	}
+
+	/* strip any local args */
+	args_strip(orig_args, "--ccache-");
+
+	if ((e=getenv("CCACHE_PREFIX"))) {
+		char *p = find_executable(e, MYNAME);
+		if (!p) {
+			cc_log("could not find executable (%s)\n", e);
+			perror(e);
+			exit(1);
+		}
+		args_add_prefix(orig_args, p);
+	}
+
+	if (ccache_verbose) {
+		display_execute_args(orig_args->argv);
+	}
+
+	if (swig) {
+		putenv("CCACHE_OUTFILES");
+	}
+
+#ifndef _WIN32
+	execv(orig_args->argv[0], orig_args->argv);
+	cc_log("execv returned (%s)!\n", strerror(errno));
+	perror(orig_args->argv[0]);
+	exit(1);
+#else
+	/* execv on Windows causes the 'non-regular' testcase to fail, so use Win32 API instead */
+	{
+		PROCESS_INFORMATION pinfo; 
+		STARTUPINFO sinfo;
+		BOOL ret; 
+		DWORD exitcode;
+		char *args;
+
+		ZeroMemory(&pinfo, sizeof(PROCESS_INFORMATION));
+		ZeroMemory(&sinfo, sizeof(STARTUPINFO));
+		sinfo.cb = sizeof(STARTUPINFO); 
+		args = argvtos(orig_args->argv);
+		ret = CreateProcessA(orig_args->argv[0], args, NULL, NULL, TRUE, 0, NULL, NULL,
+				     &sinfo, &pinfo);
+		if (!ret) {
+			exitcode = 1;
+			cc_log("CreateProcessA failed starting %s\n", orig_args->argv[0]);
+			perror_win32(orig_args->argv[0]);
+		} else {
+			WaitForSingleObject(pinfo.hProcess, INFINITE);
+			GetExitCodeProcess(pinfo.hProcess, &exitcode);
+			CloseHandle(pinfo.hProcess);
+			CloseHandle(pinfo.hThread);
+		}
+		free(args);
+		exit(exitcode);
+	}
+#endif
+}
+
+
+/* return a string to be used to distinguish temporary files 
+   this also tries to cope with NFS by adding the local hostname 
+*/
+static const char *tmp_string(void)
+{
+	static char *ret;
+
+	if (!ret) {
+		char hostname[200];
+		strcpy(hostname, "unknown");
+#if HAVE_GETHOSTNAME
+		gethostname(hostname, sizeof(hostname)-1);
+#endif
+		hostname[sizeof(hostname)-1] = 0;
+		if (asprintf(&ret, "%s.%u", hostname, (unsigned)getpid()) == -1) {
+			fatal("could not allocate tmp_string");
+		}
+	}
+
+	return ret;
+}
+
+/* update cached file sizes and count helper function for to_cache() */
+static void to_cache_stats_helper(struct stat *pstat, char *cached_filename, char *tmp_outfiles, int *files_size, int *cached_files_count)
+{
+#if ENABLE_ZLIB
+	/* do an extra stat on the cache file for the size statistics */
+	if (stat(cached_filename, pstat) != 0) {
+		cc_log("failed to stat cache files - %s\n", strerror(errno));
+		stats_update(STATS_ERROR);
+		if (tmp_outfiles) {
+			unlink(tmp_outfiles);
+		}
+		failed();
+	}
+#else
+        (void)cached_filename;
+        (void)tmp_outfiles;
+#endif
+	(*files_size) += file_size(pstat);
+	(*cached_files_count)++;
+}
+
+/* run the real compiler and put the result in cache */
+static void to_cache(ARGS *args)
+{
+	char *path_stderr;
+	char *tmp_stdout, *tmp_stderr, *tmp_outfiles;
+	struct stat st1;
+	int status;
+	int cached_files_count = 0;
+	int files_size = 0;
+
+	x_asprintf(&tmp_stdout, "%s/tmp.stdout.%s", temp_dir, tmp_string());
+	x_asprintf(&tmp_stderr, "%s/tmp.stderr.%s", temp_dir, tmp_string());
+	x_asprintf(&tmp_outfiles, "%s/tmp.outfiles.%s", temp_dir, tmp_string());
+
+	if (strip_c_option && !swig) {
+		args_add(stripped_args, "-c");
+	}
+
+	if (output_file) {
+		args_add(args, "-o");
+		args_add(args, output_file);
+	}
+
+	/* Turn off DEPENDENCIES_OUTPUT when running cc1, because
+	 * otherwise it will emit a line like
+	 *
+	 *  tmp.stdout.vexed.732.o: /home/mbp/.ccache/tmp.stdout.vexed.732.i
+	 *
+	 * unsetenv() is on BSD and Linux but not portable. */
+	putenv("DEPENDENCIES_OUTPUT");
+
+	/* Give SWIG a filename for it to create and populate with a list of files that it generates */
+	if (swig) {
+		char *ccache_outfiles;
+		x_asprintf(&ccache_outfiles, "CCACHE_OUTFILES=%s", tmp_outfiles);
+		unlink(tmp_outfiles);
+		if (getenv("CCACHE_OUTFILES") || putenv(ccache_outfiles) == -1) {
+			cc_log("CCACHE_OUTFILES env variable already set or could not be set\n");
+			stats_update(STATS_ERROR);
+			failed();
+		}
+	}
+	
+	if (getenv("CCACHE_CPP2")) {
+		args_add(args, input_file);
+	} else {
+		if (swig) {
+			args_add(args, "-nopreprocess");
+		}
+		args_add(args, i_tmpfile);
+	}
+	status = execute(args->argv, tmp_stdout, tmp_stderr);
+	args_pop(args, 3);
+
+	if (stat(tmp_stdout, &st1) != 0 || st1.st_size != 0) {
+		cc_log("compiler produced stdout for %s\n", input_file);
+		stats_update(STATS_STDOUT);
+		unlink(tmp_stdout);
+		unlink(tmp_stderr);
+		unlink(tmp_outfiles);
+		if (!swig) unlink(output_file);
+		failed();
+	}
+	unlink(tmp_stdout);
+
+	if (status != 0) {
+		int fd;
+		cc_log("compile of %s gave status = %d\n", input_file, status);
+		stats_update(STATS_STATUS);
+
+		fd = open(tmp_stderr, O_RDONLY | O_BINARY);
+		if (fd != -1) {
+			if (cpp_stderr) {
+				/* we might have some stderr from cpp */
+				int fd2 = open(cpp_stderr, O_RDONLY | O_BINARY);
+				if (fd2 != -1) {
+					copy_fd(fd2, 2);
+					close(fd2);
+					unlink(cpp_stderr);
+					cpp_stderr = NULL;
+				}
+			}
+
+			/* we can use a quick method of
+			   getting the failed output */
+			copy_fd(fd, 2);
+			close(fd);
+			unlink(tmp_stderr);
+			if (i_tmpfile && !direct_i_file) {
+				unlink(i_tmpfile);
+			}
+			exit(status);
+		}
+		
+		unlink(tmp_stderr);
+		unlink(tmp_outfiles);
+		if (!swig) unlink(output_file);
+		failed();
+	} else {
+		int hardlink = (getenv("CCACHE_NOCOMPRESS") != 0) && (getenv("CCACHE_HARDLINK") != 0);
+		if (swig) {
+			/* read the list of generated files and copy each of them into the cache */
+			FILE *file;
+			file = fopen(tmp_outfiles, "r");
+			if (file) {
+				char out_filename[FILENAME_MAX + 1];
+				char out_filename_cache[FILENAME_MAX + 1];
+				while (fgets(out_filename, FILENAME_MAX, file)) {
+					char *linefeed = strchr(out_filename, '\n');
+					if (linefeed) {
+						char *potential_cr = linefeed - 1;
+						if (potential_cr >= out_filename && *potential_cr == '\r')
+						  *potential_cr = 0;
+						*linefeed = 0;
+
+						if (cached_files_count == 0) {
+							strcpy(out_filename_cache, hashname);
+						} else {
+							sprintf(out_filename_cache, "%s.%d", hashname, cached_files_count);
+						}
+
+						if (commit_to_cache(out_filename, out_filename_cache, hardlink) != 0) {
+							fclose(file);
+							unlink(tmp_outfiles);
+							failed();
+						}
+						to_cache_stats_helper(&st1, out_filename_cache, tmp_outfiles, &files_size, &cached_files_count);
+					} else {
+						cached_files_count = 0;
+						break;
+					}
+				}
+				fclose(file);
+				if (cached_files_count == 0) {
+					cc_log("failed to copy output files to cache - internal error\n");
+					stats_update(STATS_ERROR);
+					unlink(tmp_outfiles);
+					failed();
+				}
+
+				/* also copy the (uncompressed) file containing the list of generated files into the cache */
+				sprintf(out_filename_cache, "%s.outfiles", hashname);
+				if (stat(tmp_outfiles, &st1) != 0 ||
+					safe_rename(tmp_outfiles, out_filename_cache) != 0) {
+					cc_log("failed to copy outfiles file to cache - %s\n", strerror(errno));
+					stats_update(STATS_ERROR);
+					unlink(tmp_outfiles);
+					failed();
+				}
+				to_cache_stats_helper(&st1, out_filename_cache, tmp_outfiles, &files_size, &cached_files_count);
+				unlink(tmp_outfiles);
+			} else {
+				cc_log("failed to open temp outfiles file - %s\n", strerror(errno));
+				stats_update(STATS_ERROR);
+				failed();
+			}
+		} else {
+			if (commit_to_cache(output_file, hashname, hardlink) != 0) {
+				failed();
+			}
+			to_cache_stats_helper(&st1, hashname, 0, &files_size, &cached_files_count);
+		}
+	}
+
+	x_asprintf(&path_stderr, "%s.stderr", hashname);
+
+	if (stat(tmp_stderr, &st1) != 0 ||
+		move_file(tmp_stderr, path_stderr) != 0) {
+		cc_log("failed to rename tmp files - %s\n", strerror(errno));
+		stats_update(STATS_ERROR);
+		failed();
+	}
+
+	to_cache_stats_helper(&st1, path_stderr, 0, &files_size, &cached_files_count);
+
+	cc_log("Placed %d files for %s into cache\n", cached_files_count, input_file);
+	stats_tocache(files_size, cached_files_count);
+
+	free(tmp_stderr);
+	free(tmp_stdout);
+	free(tmp_outfiles);
+	free(path_stderr);
+}
+
+/* find the hash for a command. The hash includes all argument lists,
+   plus the output from running the compiler with -E */
+static void find_hash(ARGS *args)
+{
+	int i;
+	char *path_stdout, *path_stderr;
+	char *hash_dir;
+	char *s;
+	struct stat st;
+	int status;
+	int nlevels = 2;
+	char *input_base;
+	char *tmp;
+	
+	if ((s = getenv("CCACHE_NLEVELS"))) {
+		nlevels = atoi(s);
+		if (nlevels < 1) nlevels = 1;
+		if (nlevels > 8) nlevels = 8;
+	}
+
+	hash_start();
+
+	/* when we are doing the unifying tricks we need to include
+           the input file name in the hash to get the warnings right */
+	if (enable_unify || swig) {
+		hash_string(input_file);
+	}
+
+	if (swig) {
+		if (output_file) {
+			hash_string(output_file);
+		}
+	} else {
+		/* we have to hash the extension, as a .i file isn't treated the same
+		   by the compiler as a .ii file */
+		hash_string(i_extension);
+	}
+
+	/* first the arguments */
+	for (i=1;i<args->argc;i++) {
+		/* some arguments don't contribute to the hash. The
+		   theory is that these arguments will change the
+		   output of -E if they are going to have any effect
+		   at all, or they only affect linking */
+		if (i < args->argc-1) {
+			if (strcmp(args->argv[i], "-I") == 0 ||
+			    strcmp(args->argv[i], "-include") == 0 ||
+			    strcmp(args->argv[i], "-L") == 0 ||
+			    strcmp(args->argv[i], "-D") == 0 ||
+			    strcmp(args->argv[i], "-idirafter") == 0 ||
+			    strcmp(args->argv[i], "-isystem") == 0) {
+				i++;
+				continue;
+			}
+		}
+		if (strncmp(args->argv[i], "-I", 2) == 0 ||
+		    strncmp(args->argv[i], "-L", 2) == 0 ||
+		    strncmp(args->argv[i], "-D", 2) == 0 ||
+		    strncmp(args->argv[i], "-idirafter", 10) == 0 ||
+		    strncmp(args->argv[i], "-isystem", 8) == 0) {
+			continue;
+		}
+
+		if (strncmp(args->argv[i], "--specs=", 8) == 0 &&
+		    stat(args->argv[i]+8, &st) == 0) {
+			/* if given a explicit specs file, then hash that file, but
+			   don't include the path to it in the hash */
+			hash_file(args->argv[i]+8);
+			continue;
+		}
+
+		/* all other arguments are included in the hash */
+		hash_string(args->argv[i]);
+	}
+
+	/* the compiler driver size and date. This is a simple minded way
+	   to try and detect compiler upgrades. It is not 100% reliable */
+	if (stat(args->argv[0], &st) != 0) {
+		cc_log("Couldn't stat the compiler!? (argv[0]='%s')\n", args->argv[0]);
+		stats_update(STATS_COMPILER);
+		failed();
+	}
+
+	/* also include the hash of the compiler name - as some compilers
+	   use hard links and behave differently depending on the real name */
+	if (st.st_nlink > 1) {
+		hash_string(str_basename(args->argv[0]));
+	}
+
+	hash_int(st.st_size);
+	hash_int(st.st_mtime);
+
+	/* possibly hash the current working directory */
+	if (getenv("CCACHE_HASHDIR")) {
+		char *cwd = gnu_getcwd();
+		if (cwd) {
+			hash_string(cwd);
+			free(cwd);
+		}
+	}
+
+	/* ~/hello.c -> tmp.hello.123.i 
+	   limit the basename to 10
+	   characters in order to cope with filesystem with small
+	   maximum filename length limits */
+	input_base = str_basename(input_file);
+	tmp = strchr(input_base, '.');
+	if (tmp != NULL) {
+		*tmp = 0;
+	}
+	if (strlen(input_base) > 10) {
+		input_base[10] = 0;
+	}
+
+	/* now the run */
+	x_asprintf(&path_stdout, "%s/%s.tmp.%s.%s", temp_dir,
+		   input_base, tmp_string(), 
+		   i_extension);
+	x_asprintf(&path_stderr, "%s/tmp.cpp_stderr.%s", temp_dir, tmp_string());
+
+	if (!direct_i_file) {
+		/* run cpp on the input file to obtain the .i */
+		args_add(args, "-E");
+		args_add(args, input_file);
+		status = execute(args->argv, path_stdout, path_stderr);
+		args_pop(args, 2);
+	} else {
+		/* we are compiling a .i or .ii file - that means we
+		   can skip the cpp stage and directly form the
+		   correct i_tmpfile */
+		path_stdout = x_strdup(input_file);
+		if (create_empty_file(path_stderr) != 0) {
+			cc_log("failed to create empty stderr file\n");
+			stats_update(STATS_ERROR);
+			failed();
+		}
+		status = 0;
+	}
+
+	if (status != 0) {
+		if (!direct_i_file) {
+			unlink(path_stdout);
+		}
+		unlink(path_stderr);
+		cc_log("the preprocessor gave %d\n", status);
+		stats_update(STATS_PREPROCESSOR);
+		failed();
+	}
+
+	/* if the compilation is with -g then we have to include the whole of the
+	   preprocessor output, which means we are sensitive to line number
+	   information. Otherwise we can discard line number info, which makes
+	   us less sensitive to reformatting changes 
+
+	   Note! I have now disabled the unification code by default
+	   as it gives the wrong line numbers for warnings. Pity.
+	*/
+	if (!enable_unify) {
+		hash_file(path_stdout);
+	} else {
+		if (unify_hash(path_stdout) != 0) {
+			stats_update(STATS_ERROR);
+			failed();
+		}
+	}
+	hash_file(path_stderr);
+
+	i_tmpfile = path_stdout;
+
+	if (!getenv("CCACHE_CPP2")) {
+		/* if we are using the CPP trick then we need to remember this stderr
+		   data and output it just before the main stderr from the compiler
+		   pass */
+		cpp_stderr = path_stderr;
+	} else {	
+		unlink(path_stderr);
+		free(path_stderr);
+	}
+
+	/* we use a N level subdir for the cache path to reduce the impact
+	   on filesystems which are slow for large directories
+	*/
+	s = hash_result();
+	x_asprintf(&hash_dir, "%s/%c", cache_dir, s[0]);
+	x_asprintf(&stats_file, "%s/stats", hash_dir);
+	for (i=1; i<nlevels; i++) {
+		char *p;
+		if (create_dir(hash_dir) != 0) {
+			cc_log("failed to create %s\n", hash_dir);
+			stats_update(STATS_ERROR);
+			failed();
+		}
+		x_asprintf(&p, "%s/%c", hash_dir, s[i]);
+		free(hash_dir);
+		hash_dir = p;
+	}
+	if (create_dir(hash_dir) != 0) {
+		cc_log("failed to create %s\n", hash_dir);
+		stats_update(STATS_ERROR);
+		failed();
+	}
+	x_asprintf(&hashname, "%s/%s", hash_dir, s+nlevels);
+	free(hash_dir);
+}
+
+/* 
+   try to return the compile result from cache. If we can return from
+   cache then this function exits with the correct status code,
+   otherwise it returns */
+static void from_cache(int first)
+{
+	int fd_stderr, fd_cpp_stderr;
+	char *stderr_file;
+	struct stat st;
+
+	x_asprintf(&stderr_file, "%s.stderr", hashname);
+	fd_stderr = open(stderr_file, O_RDONLY | O_BINARY);
+	if (fd_stderr == -1) {
+		/* it isn't in cache ... */
+		free(stderr_file);
+		return;
+	}
+
+	/* make sure the output is there too */
+	if (stat(hashname, &st) != 0) {
+		close(fd_stderr);
+		unlink(stderr_file);
+		free(stderr_file);
+		return;
+	}
+
+	/* the user might be disabling cache hits */
+#ifndef ENABLE_ZLIB
+	/* if the cache file is compressed we must recache */
+	if ((first && getenv("CCACHE_RECACHE")) ||
+		test_if_compressed(hashname) == 1)
+#else
+	if (first && getenv("CCACHE_RECACHE"))
+#endif
+	{
+		close(fd_stderr);
+		unlink(stderr_file);
+		free(stderr_file);
+		return;
+	}
+
+	if (first) {
+		int hardlink;
+		int passfail = -1;
+
+		/* update timestamps for LRU cleanup
+		   also gives output_file a sensible mtime when hard-linking (for make) */
+		x_utimes(stderr_file);
+
+		hardlink = (getenv("CCACHE_HARDLINK") != 0);
+
+		if (swig) {
+			/* read the list of generated files and copy each of them out of the cache */
+			FILE *file;
+			char *outfiles;
+			x_asprintf(&outfiles, "%s.outfiles", hashname);
+			file = fopen(outfiles, "r");
+			if (file) {
+				char out_filename[FILENAME_MAX + 1];
+				char out_filename_cache[FILENAME_MAX + 1];
+				int retrieved_files_count = 0;
+				x_utimes(outfiles);
+				while (fgets(out_filename, FILENAME_MAX, file)) {
+					char *linefeed = strchr(out_filename, '\n');
+					if (linefeed) {
+						char *potential_cr = linefeed - 1;
+						if (potential_cr >= out_filename && *potential_cr == '\r')
+						  *potential_cr = 0;
+						*linefeed = 0;
+
+						if (retrieved_files_count == 0) {
+							strcpy(out_filename_cache, hashname);
+						} else {
+							sprintf(out_filename_cache, "%s.%d", hashname, retrieved_files_count);
+						}
+
+						passfail = retrieve_from_cache(out_filename_cache, out_filename, hardlink);
+						if (passfail == -1) {
+							break;
+						}
+
+						retrieved_files_count++;
+					} else {
+						cc_log("failed to copy output files from cache - internal error\n");
+						stats_update(STATS_ERROR);
+						passfail = -1;
+						break;
+					}
+				}
+				if (retrieved_files_count == 0) {
+					cc_log("failed to copy output files from cache - internal error\n");
+					stats_update(STATS_ERROR);
+					passfail = -1;
+				}
+				fclose(file);
+			} else {
+				cc_log("failed to open cached outfiles file - %s\n", strerror(errno));
+				stats_update(STATS_ERROR);
+			}
+		} else {
+			passfail = retrieve_from_cache(hashname, output_file, hardlink);
+		}
+
+		if (passfail == -1) {
+			close(fd_stderr);
+			unlink(stderr_file);
+			free(stderr_file);
+			return;
+		}
+		free(stderr_file);
+	}
+
+	/* get rid of the intermediate preprocessor file */
+	if (i_tmpfile) {
+		if (!direct_i_file) {
+			unlink(i_tmpfile);
+		}
+		free(i_tmpfile);
+		i_tmpfile = NULL;
+	}
+
+	/* send the cpp stderr, if applicable */
+	fd_cpp_stderr = open(cpp_stderr, O_RDONLY | O_BINARY);
+	if (fd_cpp_stderr != -1) {
+		copy_fd(fd_cpp_stderr, 2);
+		close(fd_cpp_stderr);
+		unlink(cpp_stderr);
+		free(cpp_stderr);
+		cpp_stderr = NULL;
+	}
+
+	/* send the stderr */
+	copy_fd(fd_stderr, 2);
+	close(fd_stderr);
+
+	/* and exit with the right status code */
+	if (first) {
+		cc_log("got cached result for %s\n", input_file);
+		stats_update(STATS_CACHED);
+	}
+
+	exit(0);
+}
+
+/* find the real compiler. We just search the PATH to find a executable of the 
+   same name that isn't a link to ourselves */
+static void find_compiler(int argc, char **argv)
+{
+	char *base;
+	char *path;
+
+	orig_args = args_init(argc, argv);
+
+	base = str_basename(argv[0]);
+
+	/* we might be being invoked like "ccache gcc -c foo.c" */
+	if (strcmp(base, MYNAME) == 0) {
+		args_remove_first(orig_args);
+		free(base);
+		if (strchr(argv[1],'/')
+#ifdef _WIN32
+                    || strchr(argv[1],'\\')
+#endif
+                    ) {
+			/* a full path was given */
+			return;
+		}
+		base = str_basename(argv[1]);
+	}
+
+	/* support user override of the compiler */
+	if ((path=getenv("CCACHE_CC"))) {
+		base = x_strdup(path);
+	}
+
+	orig_args->argv[0] = find_executable(base, MYNAME);
+
+	/* can't find the compiler! */
+	if (!orig_args->argv[0]) {
+		stats_update(STATS_COMPILER);
+		cc_log("could not find compiler (%s)\n", base);
+		perror(base);
+		exit(1);
+	}
+}
+
+
+/* check a filename for C/C++ extension. Return the pre-processor
+   extension */
+static const char *check_extension(const char *fname, int *direct_i)
+{
+	int i;
+	const char *p;
+
+	if (direct_i) {
+		*direct_i = 0;
+	}
+
+	if (swig) return "ii"; /* any file extension is acceptable as input for SWIG */
+
+	p = strrchr(fname, '.');
+	if (!p) return NULL;
+	p++;
+	for (i=0; extensions[i].extension; i++) {
+		if (strcmp(p, extensions[i].extension) == 0) {
+			if (direct_i && strcmp(p, extensions[i].i_extension) == 0) {
+				*direct_i = 1;
+			}
+			p = getenv("CCACHE_EXTENSION");
+			if (p) return p;
+			return extensions[i].i_extension;
+		}
+	}
+	return NULL;
+}
+
+
+/* 
+   process the compiler options to form the correct set of options 
+   for obtaining the preprocessor output
+*/
+static void process_args(int argc, char **argv)
+{
+	int i;
+	int found_c_opt = 0;
+	int found_S_opt = 0;
+	struct stat st;
+	char *e;
+	/* is gcc being asked to output dependencies? */
+	int generating_dependencies = 0;
+	/* is the dependency makefile name overridden with -MF? */
+	int dependency_filename_specified = 0;
+	/* is the dependency makefile target name specified with -MQ or -MF? */
+	int dependency_target_specified = 0;
+
+
+	stripped_args = args_init(0, NULL);
+
+	args_add(stripped_args, argv[0]);
+
+	/* -c not required for SWIG */
+	if (swig) {
+		found_c_opt = 1;
+	}
+
+	for (i=1; i<argc; i++) {
+		/* some options will never work ... */
+		if (strcmp(argv[i], "-E") == 0) {
+			failed();
+		}
+
+		/* these are too hard */
+		if (strcmp(argv[i], "-fbranch-probabilities")==0 ||
+		    strcmp(argv[i], "-fprofile-arcs") == 0 ||
+		    strcmp(argv[i], "-ftest-coverage") == 0 ||
+		    strcmp(argv[i], "--coverage") == 0 ||
+		    strcmp(argv[i], "-M") == 0 ||
+		    strcmp(argv[i], "-MM") == 0 ||
+		    strcmp(argv[i], "-x") == 0) {
+			cc_log("argument %s is unsupported\n", argv[i]);
+			stats_update(STATS_UNSUPPORTED);
+			failed();
+			continue;
+		}
+
+		/* we must have -c */
+		if (strcmp(argv[i], "-c") == 0) {
+			if (!strip_c_option) {
+				args_add(stripped_args, argv[i]);
+			}
+			found_c_opt = 1;
+			continue;
+		}
+
+		/* -S changes the default extension */
+		if (strcmp(argv[i], "-S") == 0) {
+			args_add(stripped_args, argv[i]);
+			found_S_opt = 1;
+			continue;
+		}
+		
+		/* we need to work out where the output was meant to go */
+		if (strcmp(argv[i], "-o") == 0) {
+			if (i == argc-1) {
+				cc_log("missing argument to %s\n", argv[i]);
+				stats_update(STATS_ARGS);
+				failed();
+			}
+			output_file = argv[i+1];
+			i++;
+			continue;
+		}
+		
+		/* alternate form of -o, with no space */
+		if (!swig) { /* some of SWIG's arguments begin with -o */
+			if (strncmp(argv[i], "-o", 2) == 0) {
+				output_file = &argv[i][2];
+				continue;
+			}
+		}
+
+		/* debugging is handled specially, so that we know if we
+		   can strip line number info 
+		*/
+		if (strncmp(argv[i], "-g", 2) == 0) {
+			args_add(stripped_args, argv[i]);
+			if (strcmp(argv[i], "-g0") != 0) {
+				enable_unify = 0;
+			}
+			continue;
+		}
+
+		/* The user knows best: just swallow the next arg */
+		if (strcmp(argv[i], "--ccache-skip") == 0) {
+			i++;
+			if (i == argc) {
+				failed();
+			}
+			args_add(stripped_args, argv[i]);
+			continue;
+		}
+
+		/* These options require special handling, because they
+		   behave differently with gcc -E, when the output
+		   file is not specified. */
+
+		if (strcmp(argv[i], "-MD") == 0 || strcmp(argv[i], "-MMD") == 0) {
+			generating_dependencies = 1;
+		} else if (strcmp(argv[i], "-MF") == 0) {
+			dependency_filename_specified = 1;
+		} else if (strcmp(argv[i], "-MQ") == 0 || strcmp(argv[i], "-MT") == 0) {
+			dependency_target_specified = 1;
+		}
+
+		/* the input file is already preprocessed */
+		if (swig && strcmp(argv[i], "-nopreprocess") == 0) {
+			direct_i_file = 1;
+			continue;
+		}
+
+		/* options that take an argument */
+		{
+			const char *opts[] = {"-I", "-include", "-imacros", "-iprefix",
+					      "-iwithprefix", "-iwithprefixbefore",
+					      "-L", "-D", "-U", "-x", "-MF", 
+					      "-MT", "-MQ", "-isystem", "-aux-info",
+					      "--param", "-A", "-Xlinker", "-u",
+					      "-idirafter", 
+					      NULL};
+			int j;
+			for (j=0;opts[j];j++) {
+				if (strcmp(argv[i], opts[j]) == 0) {
+					if (i == argc-1) {
+						cc_log("missing argument to %s\n", 
+						       argv[i]);
+						stats_update(STATS_ARGS);
+						failed();
+					}
+						
+					args_add(stripped_args, argv[i]);
+					args_add(stripped_args, argv[i+1]);
+					i++;
+					break;
+				}
+			}
+			if (opts[j]) continue;
+		}
+
+		/* other options */
+		if (argv[i][0] == '-') {
+			args_add(stripped_args, argv[i]);
+			continue;
+		}
+
+		/* if an argument isn't a plain file then assume its
+		   an option, not an input file. This allows us to
+		   cope better with unusual compiler options */
+		if (stat(argv[i], &st) != 0 || !S_ISREG(st.st_mode)) {
+			args_add(stripped_args, argv[i]);
+			continue;			
+		}
+
+		if (input_file) {
+			if (check_extension(argv[i], NULL)) {
+				cc_log("multiple input files (%s and %s)\n",
+				       input_file, argv[i]);
+				stats_update(STATS_MULTIPLE);
+			} else if (!found_c_opt) {
+				cc_log("called for link with %s\n", argv[i]);
+				if (strstr(argv[i], "conftest.")) {
+					stats_update(STATS_CONFTEST);
+				} else {
+					stats_update(STATS_LINK);
+				}
+			} else {
+				cc_log("non C/C++ file %s\n", argv[i]);
+				stats_update(STATS_NOTC);
+			}
+			failed();
+		}
+
+		input_file = argv[i];
+	}
+
+	if (!input_file) {
+		cc_log("No input file found\n");
+		stats_update(STATS_NOINPUT);
+		failed();
+	}
+
+	if (swig) {
+		i_extension = check_extension(input_file, NULL);
+	} else {
+		i_extension = check_extension(input_file, &direct_i_file);
+	}
+	if (i_extension == NULL) {
+		cc_log("Not a C/C++ file - %s\n", input_file);
+		stats_update(STATS_NOTC);
+		failed();
+	}
+
+	if (!found_c_opt) {
+		cc_log("No -c option found for %s\n", input_file);
+		/* I find that having a separate statistic for autoconf tests is useful,
+		   as they are the dominant form of "called for link" in many cases */
+		if (strstr(input_file, "conftest.")) {
+			stats_update(STATS_CONFTEST);
+		} else {
+			stats_update(STATS_LINK);
+		}
+		failed();
+	}
+
+
+	/* don't try to second guess the compilers heuristics for stdout handling */
+	if (output_file && strcmp(output_file, "-") == 0) {
+		stats_update(STATS_OUTSTDOUT);
+		failed();
+	}
+
+	if (!swig && !output_file) {
+		char *p;
+		output_file = x_strdup(input_file);
+		if ((p = strrchr(output_file, '/'))) {
+			output_file = p+1;
+		}
+		p = strrchr(output_file, '.');
+		if (!p || !p[1]) {
+			cc_log("badly formed output_file %s\n", output_file);
+			stats_update(STATS_ARGS);
+			failed();
+		}
+		p[1] = found_S_opt ? 's' : 'o';
+		p[2] = 0;
+	}
+
+	/* If dependencies are generated, configure the preprocessor */
+
+	if (generating_dependencies && output_file) {
+		if (!dependency_filename_specified) {
+			char *default_depfile_name = x_strdup(output_file);
+			char *p = strrchr(default_depfile_name, '.');
+
+			if (p) {
+				if (strlen(p) < 2) {
+					cc_log("badly formed dependency file %s\n", output_file);
+					stats_update(STATS_ARGS);
+					failed();
+					return;
+				}
+				*p = 0;
+			}
+			else  {
+				int len = p - default_depfile_name;
+				
+				p = x_malloc(len + 3);
+				strncpy(default_depfile_name, p, len - 1);
+				free(default_depfile_name);
+				default_depfile_name = p;
+			}
+
+			strcat(default_depfile_name, ".d");
+			args_add(stripped_args, "-MF");
+			args_add(stripped_args, default_depfile_name);
+		}
+
+		if (!dependency_target_specified) {
+			args_add(stripped_args, "-MT");
+			args_add(stripped_args, output_file);
+		}
+	}
+
+	/* cope with -o /dev/null */
+	if (output_file && strcmp(output_file,"/dev/null") != 0 && stat(output_file, &st) == 0 && !S_ISREG(st.st_mode)) {
+		cc_log("Not a regular file %s\n", output_file);
+		stats_update(STATS_DEVICE);
+		failed();
+	}
+
+	if ((e=getenv("CCACHE_PREFIX"))) {
+		char *p = find_executable(e, MYNAME);
+		if (!p) {
+			cc_log("could not find executable (%s)\n", e);
+			stats_update(STATS_ENVIRONMMENT);
+			perror(e);
+			exit(1);
+		}
+		args_add_prefix(stripped_args, p);
+	}
+}
+
+static void detect_swig()
+{
+	char *basename = str_basename(orig_args->argv[0]);
+	if (strstr(basename, "swig") || getenv("CCACHE_SWIG")) {
+		swig = 1;
+	}
+	free(basename);
+}
+
+/* the main ccache driver function */
+static void ccache(int argc, char *argv[])
+{
+	/* find the real compiler */
+	find_compiler(argc, argv);
+
+	/* use the real compiler if HOME is not set */
+	if (!cache_dir) {
+		cc_log("Unable to determine home directory\n");
+		cc_log("ccache is disabled\n");
+		failed();
+	}
+	
+	/* we might be disabled */
+	if (getenv("CCACHE_DISABLE")) {
+		cc_log("ccache is disabled\n");
+		failed();
+	}
+
+	if (getenv("CCACHE_STRIPC")) {
+		strip_c_option = 1;
+	}
+
+	if (getenv("CCACHE_UNIFY")) {
+		enable_unify = 1;
+	}
+
+	detect_swig();
+
+	/* process argument list, returning a new set of arguments for pre-processing */
+	process_args(orig_args->argc, orig_args->argv);
+
+	/* run with -E to find the hash */
+	find_hash(stripped_args);
+
+	/* if we can return from cache at this point then do */
+	from_cache(1);
+
+	if (getenv("CCACHE_READONLY")) {
+		cc_log("read-only set - doing real compile\n");
+		failed();
+	}
+	
+	/* run real compiler, sending output to cache */
+	to_cache(stripped_args);
+
+	/* return from cache */
+	from_cache(0);
+
+	/* oh oh! */
+	cc_log("secondary from_cache failed!\n");
+	stats_update(STATS_ERROR);
+	failed();
+}
+
+
+static void usage(void)
+{
+	printf("%s, a compiler cache including support for SWIG. Version %s\n", MYNAME, CCACHE_VERSION);
+	printf("Copyright Andrew Tridgell, 2002\n\n");
+	
+	printf("Usage:\n");
+	printf("\t" MYNAME " [options]\n");
+	printf("\t" MYNAME " compiler [compile options]\n");
+	printf("\tcompiler [compile options]    (via symbolic link)\n");
+	printf("\nOptions:\n");
+
+	printf("-s                      show statistics summary\n");
+	printf("-z                      zero statistics\n");
+	printf("-c                      run a cache cleanup\n");
+	printf("-C                      clear the cache completely\n");
+	printf("-F <maxfiles>           set maximum files in cache\n");
+	printf("-M <maxsize>            set maximum size of cache (use G, M or K)\n");
+	printf("-h                      this help page\n");
+	printf("-V                      print version number\n");
+}
+
+static void check_cache_dir(void)
+{
+	if (!cache_dir) {
+		fatal("Unable to determine home directory");
+	}
+}
+
+/* the main program when not doing a compile */
+static int ccache_main(int argc, char *argv[])
+{
+	int c;
+	size_t v;
+
+	while ((c = getopt(argc, argv, "hszcCF:M:V")) != -1) {
+		switch (c) {
+		case 'V':
+			printf("%s version %s\n", MYNAME, CCACHE_VERSION);
+			printf("Copyright Andrew Tridgell 2002\n");
+			printf("Released under the GNU GPL v2 or later\n");
+			exit(0);
+
+		case 'h':
+			usage();
+			exit(0);
+			
+		case 's':
+			check_cache_dir();
+			stats_summary();
+			break;
+
+		case 'c':
+			check_cache_dir();
+			cleanup_all(cache_dir);
+			printf("Cleaned cache\n");
+			break;
+
+		case 'C':
+			check_cache_dir();
+			wipe_all(cache_dir);
+			printf("Cleared cache\n");
+			break;
+
+		case 'z':
+			check_cache_dir();
+			stats_zero();
+			printf("Statistics cleared\n");
+			break;
+
+		case 'F':
+			check_cache_dir();
+			v = atoi(optarg);
+			if (stats_set_limits(v, -1) == 0) {
+				printf("Set cache file limit to %u\n", (unsigned)v);
+			} else {
+				printf("Could not set cache file limit.\n");
+				exit(1);
+			}
+			break;
+
+		case 'M':
+			check_cache_dir();
+			v = value_units(optarg);
+			if (stats_set_limits(-1, v) == 0) {
+				printf("Set cache size limit to %uk\n", (unsigned)v);
+			} else {
+				printf("Could not set cache size limit.\n");
+				exit(1);
+			}
+			break;
+
+		default:
+			usage();
+			exit(1);
+		}
+	}
+
+	return 0;
+}
+
+
+/* Make a copy of stderr that will not be cached, so things like
+   distcc can send networking errors to it. */
+static void setup_uncached_err(void)
+{
+	char *buf;
+	int uncached_fd;
+	
+	uncached_fd = dup(2);
+	if (uncached_fd == -1) {
+		cc_log("dup(2) failed\n");
+		stats_update(STATS_ERROR);
+		failed();
+	}
+
+	/* leak a pointer to the environment */
+	x_asprintf(&buf, "UNCACHED_ERR_FD=%d", uncached_fd);
+
+	if (putenv(buf) == -1) {
+		cc_log("putenv failed\n");
+		stats_update(STATS_ERROR);
+		failed();
+	}
+}
+
+
+int main(int argc, char *argv[])
+{
+	char *p;
+
+	cache_dir = getenv("CCACHE_DIR");
+	if (!cache_dir) {
+		const char *home_directory = get_home_directory();
+		if (home_directory) {
+			x_asprintf(&cache_dir, "%s/.ccache", home_directory);
+		}
+	}
+
+	cache_logfile = getenv("CCACHE_LOGFILE");
+
+	if (getenv("CCACHE_VERBOSE")) {
+		ccache_verbose = 1;
+	}
+
+	setup_uncached_err();
+	
+
+	/* the user might have set CCACHE_UMASK */
+	p = getenv("CCACHE_UMASK");
+	if (p) {
+		mode_t mask;
+		errno = 0;
+		mask = strtol(p, NULL, 8);
+		if (errno == 0) {
+			umask(mask);
+		}
+	}
+
+
+	/* check if we are being invoked as "ccache" */
+	if (strlen(argv[0]) >= strlen(MYNAME) &&
+	    strcmp(argv[0] + strlen(argv[0]) - strlen(MYNAME), MYNAME) == 0) {
+		if (argc < 2) {
+			usage();
+			exit(1);
+		}
+		/* if the first argument isn't an option, then assume we are
+		   being passed a compiler name and options */
+		if (argv[1][0] == '-') {
+			return ccache_main(argc, argv);
+		}
+	}
+
+	/* make sure the cache dir exists */
+	if (cache_dir && (create_dir(cache_dir) != 0)) {
+		fprintf(stderr,"ccache: failed to create %s (%s)\n", 
+			cache_dir, strerror(errno));
+		exit(1);
+	}
+
+	temp_dir = getenv("CCACHE_TEMPDIR");
+	if (!temp_dir) {
+		x_asprintf(&temp_dir, "%s/temp", cache_dir);
+		/* make sure temp dir exists if not supplied by user */
+		if (temp_dir && create_dir(temp_dir) != 0) {
+			fprintf(stderr,"ccache: failed to create %s (%s)\n", 
+				temp_dir, strerror(errno));
+			exit(1);
+		}
+	}
+
+	if (!getenv("CCACHE_READONLY")) {
+		if (create_cachedirtag(cache_dir) != 0) {
+			fprintf(stderr,"ccache: failed to create %s/CACHEDIR.TAG (%s)\n",
+				cache_dir, strerror(errno));
+			exit(1);
+		}
+	}
+
+	ccache(argc, argv);
+	return 1;
+}