Merge pull request #184 from nathanrossi/nrossi/portable-chipdb

icetime: Add support for searching for chipdb relative to binary
diff --git a/icetime/Makefile b/icetime/Makefile
index d260681..b1cd18d 100644
--- a/icetime/Makefile
+++ b/icetime/Makefile
@@ -26,10 +26,11 @@
 override LDFLAGS += --embed-file share
 endif
 
-icetime$(EXE): icetime.o
+icetime$(EXE): icetime.o iceutil.o
 	$(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS)
 
 icetime.o: icetime.cc timings.inc
+iceutil.o: iceutil.cc
 
 timings.inc: timings.py ../icefuzz/timings_*.txt
 	python3 timings.py > timings.inc.new
diff --git a/icetime/icetime.cc b/icetime/icetime.cc
index c49e2e2..a5a27a0 100644
--- a/icetime/icetime.cc
+++ b/icetime/icetime.cc
@@ -38,6 +38,8 @@
 #include <emscripten.h>
 #endif
 
+std::string find_chipdb(std::string config_device);
+
 // add this number of ns as estimate for clock distribution mismatch
 #define GLOBAL_CLK_DIST_JITTER 0.1
 
@@ -322,35 +324,18 @@
 void read_chipdb()
 {
 	char buffer[1024];
+	std::string filepath = chipdbfile;
 
-	if (!chipdbfile.empty()) {
-		snprintf(buffer, 1024, "%s", chipdbfile.c_str());
-	} else
-	if (PREFIX[0] == '~' && PREFIX[1] == '/') {
-		std::string homedir;
-#ifdef _WIN32
-		if (getenv("USERPROFILE") != nullptr) {
-			homedir += getenv("USERPROFILE");
-		}
-		else {
-			if (getenv("HOMEDRIVE") != nullptr &&
-			    getenv("HOMEPATH") != nullptr) {
-				homedir += getenv("HOMEDRIVE");
-				homedir += getenv("HOMEPATH");
-			}
-		}
-#else
-		homedir += getenv("HOME");
-#endif
-		snprintf(buffer, 1024, "%s%s/share/" CHIPDB_SUBDIR "/chipdb-%s.txt", homedir.c_str(), PREFIX+1, config_device.c_str());
-	} else {
-		snprintf(buffer, 1024, PREFIX "/share/" CHIPDB_SUBDIR "/chipdb-%s.txt", config_device.c_str());
+	if (filepath.empty())
+		filepath = find_chipdb(config_device);
+	if (filepath.empty()) {
+		fprintf(stderr, "Can't find chipdb file for device %s\n", config_device.c_str());
+		exit(1);
 	}
 
-	FILE *fdb = fopen(buffer, "r");
+	FILE *fdb = fopen(filepath.c_str(), "r");
 	if (fdb == nullptr) {
 		perror("Can't open chipdb file");
-		fprintf(stderr, "  %s\n", buffer);
 		exit(1);
 	}
 
diff --git a/icetime/iceutil.cc b/icetime/iceutil.cc
new file mode 100644
index 0000000..ad8d662
--- /dev/null
+++ b/icetime/iceutil.cc
@@ -0,0 +1,179 @@
+/*
+ *  yosys -- Yosys Open SYnthesis Suite
+ *
+ *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at>
+ *
+ *  Permission to use, copy, modify, and/or distribute this software for any
+ *  purpose with or without fee is hereby granted, provided that the above
+ *  copyright notice and this permission notice appear in all copies.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include <errno.h>
+#include <string>
+#include <sstream>
+#include <cstring>
+
+#ifdef _WIN32
+#  include <windows.h>
+#  include <io.h>
+#elif defined(__APPLE__)
+#  include <mach-o/dyld.h>
+#  include <unistd.h>
+#else
+#  include <unistd.h>
+#endif
+
+#ifdef __FreeBSD__
+#  include <sys/sysctl.h>
+#endif
+
+#include <limits.h>
+
+#if defined(__linux__) || defined(__CYGWIN__)
+std::string proc_self_dirname()
+{
+	char path[PATH_MAX];
+	ssize_t buflen = readlink("/proc/self/exe", path, sizeof(path));
+	if (buflen < 0) {
+		fprintf(stderr, "fatal error: readlink(\"/proc/self/exe\") failed: %s\n", strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+	while (buflen > 0 && path[buflen-1] != '/')
+		buflen--;
+	return std::string(path, buflen);
+}
+#elif defined(__FreeBSD__)
+std::string proc_self_dirname()
+{
+	int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
+	size_t buflen;
+	char *buffer;
+	std::string path;
+	if (sysctl(mib, 4, NULL, &buflen, NULL, 0) != 0) {
+		fprintf(stderr, "fatal error: sysctl failed: %s\n", strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+	buffer = (char*)malloc(buflen);
+	if (buffer == NULL) {
+		fprintf(stderr, "fatal error: malloc failed: %s\n", strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+	if (sysctl(mib, 4, buffer, &buflen, NULL, 0) != 0) {
+		fprintf(stderr, "fatal error: sysctl failed: %s\n", strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+	while (buflen > 0 && buffer[buflen-1] != '/')
+		buflen--;
+	path.assign(buffer, buflen);
+	free(buffer);
+	return path;
+}
+#elif defined(__APPLE__)
+std::string proc_self_dirname()
+{
+	char *path = NULL;
+	uint32_t buflen = 0;
+	while (_NSGetExecutablePath(path, &buflen) != 0)
+		path = (char *) realloc((void *) path, buflen);
+	while (buflen > 0 && path[buflen-1] != '/')
+		buflen--;
+	return std::string(path, buflen);
+}
+#elif defined(_WIN32)
+std::string proc_self_dirname()
+{
+	int i = 0;
+#  ifdef __MINGW32__
+	char longpath[MAX_PATH + 1];
+	char shortpath[MAX_PATH + 1];
+#  else
+	WCHAR longpath[MAX_PATH + 1];
+	TCHAR shortpath[MAX_PATH + 1];
+#  endif
+	if (!GetModuleFileName(0, longpath, MAX_PATH+1)) {
+		fprintf(stderr, "fatal error: GetModuleFileName() failed.\n");
+		exit(EXIT_FAILURE);
+	}
+	if (!GetShortPathName(longpath, shortpath, MAX_PATH+1)) {
+		fprintf(stderr, "fatal error: GetShortPathName() failed.\n");
+		exit(EXIT_FAILURE);
+	}
+	while (shortpath[i] != 0)
+		i++;
+	while (i > 0 && shortpath[i-1] != '/' && shortpath[i-1] != '\\')
+		shortpath[--i] = 0;
+	std::string path;
+	for (i = 0; shortpath[i]; i++)
+		path += char(shortpath[i]);
+	return path;
+}
+#elif defined(EMSCRIPTEN)
+std::string proc_self_dirname()
+{
+	return "/";
+}
+#else
+	#error Dont know how to determine process executable base path!
+#endif
+
+bool file_test_open(std::string path)
+{
+	FILE *fdb = fopen(path.c_str(), "r");
+	if (fdb == nullptr) {
+		return false;
+	}
+	fclose(fdb);
+	return true;
+}
+
+extern bool verbose;
+
+std::string find_chipdb(std::string config_device)
+{
+	if (PREFIX[0] == '~' && PREFIX[1] == '/') {
+		std::string homepath;
+#ifdef _WIN32
+		if (getenv("USERPROFILE") != nullptr) {
+			homepath += getenv("USERPROFILE");
+		}
+		else {
+			if (getenv("HOMEDRIVE") != nullptr &&
+			    getenv("HOMEPATH") != nullptr) {
+				homepath += getenv("HOMEDRIVE");
+				homepath += getenv("HOMEPATH");
+			}
+		}
+#else
+		homepath += getenv("HOME");
+#endif
+		homepath += std::string(PREFIX + 1) + "/" CHIPDB_SUBDIR "/chipdb-" + config_device + ".txt";
+		if (verbose)
+			fprintf(stderr, "Looking for chipdb '%s' at %s\n", config_device.c_str(), homepath.c_str());
+		if (file_test_open(homepath))
+			return homepath;
+	}
+
+	std::string prefixpath = PREFIX "/share/" CHIPDB_SUBDIR "/chipdb-" + config_device + ".txt";
+	if (verbose)
+		fprintf(stderr, "Looking for chipdb '%s' at %s\n", config_device.c_str(), prefixpath.c_str());
+	if (file_test_open(prefixpath))
+		return prefixpath;
+
+	std::string relbinarypath = proc_self_dirname() + "../share/" CHIPDB_SUBDIR "/chipdb-" + config_device + ".txt";
+	if (verbose)
+		fprintf(stderr, "Looking for chipdb '%s' at %s\n", config_device.c_str(), relbinarypath.c_str());
+	if (file_test_open(relbinarypath))
+		return relbinarypath;
+
+	return "";
+}
+