diff common/path.cpp @ 0:1158cffe5a5e

Initial import
author David Demelier <markand@malikania.fr>
date Mon, 08 Feb 2016 16:43:14 +0100
parents
children 03068f5ed79d
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/path.cpp	Mon Feb 08 16:43:14 2016 +0100
@@ -0,0 +1,555 @@
+/*
+ * path.cpp -- special paths inside irccd
+ *
+ * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
+ *
+ * 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 <algorithm>
+#include <cassert>
+#include <sstream>
+#include <stdexcept>
+
+#include <irccd-config.h>
+
+#if defined(IRCCD_SYSTEM_WINDOWS)
+#  include <Windows.h>
+#  include <Shlobj.h>
+#else
+#  if defined(IRCCD_SYSTEM_LINUX)
+#    include <limits.h>
+#    include <unistd.h>
+#    include <cerrno>
+#    include <cstring>
+#    include <stdexcept>
+#  endif
+
+#  if defined(IRCCD_SYSTEM_FREEBSD)
+#    include <sys/types.h>
+#    include <sys/sysctl.h>
+#    include <limits.h>
+
+#    include <array>
+#    include <cerrno>
+#    include <cstring>
+#    include <stdexcept>
+#  endif
+
+#  if defined(IRCCD_SYSTEM_MAC)
+#    include <cerrno>
+#    include <cstring>
+#    include <unistd.h>
+#    include <libproc.h>
+#  endif
+
+#  include <xdg.h>
+#endif
+
+#include "filesystem.h"
+#include "path.h"
+#include "system.h"
+#include "util.h"
+
+namespace irccd {
+
+namespace path {
+
+namespace {
+
+/*
+ * Base program directory
+ * ------------------------------------------------------------------
+ *
+ * This variable stores the program base directory. It is only enabled when irccd is relocatable because we can
+ * retrieve the base directory by removing WITH_BINDIR.
+ *
+ * If it is empty, the program was not able to detect it (e.g. error, not supported).
+ */
+
+#if defined(IRCCD_RELOCATABLE)
+
+std::string base;
+
+#if defined(IRCCD_SYSTEM_WINDOWS)
+
+std::string executablePath()
+{
+	std::string result;
+	std::size_t size = PATH_MAX;
+	
+	result.resize(size);
+	
+	if (!(size = GetModuleFileNameA(nullptr, &result[0], size)))
+		throw std::runtime_error{"GetModuleFileName error"};
+	
+	result.resize(size);
+	
+	return result;
+}
+
+#elif defined(IRCCD_SYSTEM_LINUX)
+
+std::string executablePath()
+{
+	std::string result;
+	
+	result.resize(2048);
+	
+	auto size = readlink("/proc/self/exe", &result[0], 2048);
+	
+	if (size < 0)
+		throw std::invalid_argument{std::strerror(errno)};
+	
+	result.resize(size);
+	
+	return result;
+}
+
+#elif defined(IRCCD_SYSTEM_FREEBSD)
+
+std::string executablePath()
+{
+	std::array<int, 4> mib{ { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 } };
+	std::string result;
+	std::size_t size = PATH_MAX + 1;
+	
+	result.resize(size);
+	
+	if (sysctl(mib.data(), 4, &result[0], &size, nullptr, 0) < 0)
+		throw std::runtime_error{std::strerror(errno)};
+	
+	result.resize(size);
+	
+	return result;
+}
+
+#elif defined(IRCCD_SYSTEM_MAC)
+
+std::string executablePath()
+{
+	std::string result;
+	std::size_t size = PROC_PIDPATHINFO_MAXSIZE;
+	
+	result.resize(size);
+	
+	if ((size = proc_pidpath(getpid(), &result[0], size)) == 0)
+		throw std::runtime_error{std::strerror(errno)};
+	
+	result.resize(size);
+	
+	return result;
+}
+
+#else
+
+/*
+ * TODO: add support for more systems here.
+ *
+ *  - NetBSD
+ *  - OpenBSD
+ */
+
+std::string executablePath()
+{
+	return "";
+}
+
+#endif
+
+#endif // !IRCCD_RELOCATABLE
+
+/*
+ * System paths
+ * ------------------------------------------------------------------
+ *
+ * Compute system paths.
+ *
+ * Do not call any of these functions if irccd is relocatable and base is unset.
+ */
+
+std::string systemConfig()
+{
+#if defined(IRCCD_RELOCATABLE)
+	assert(!base.empty());
+
+	return base + WITH_CONFDIR;
+#else
+	return fs::isAbsolute(WITH_CONFDIR) ? WITH_CONFDIR : std::string(PREFIX) + fs::Separator + WITH_CONFDIR;
+#endif
+}
+
+std::string systemData()
+{
+#if defined(IRCCD_RELOCATABLE)
+	assert(!base.empty());
+
+	return base + WITH_DATADIR;
+#else
+	return fs::isAbsolute(WITH_DATADIR) ? WITH_CONFDIR : std::string(PREFIX) + fs::Separator + WITH_DATADIR;
+#endif
+}
+
+std::string systemCache()
+{
+#if defined(IRCCD_RELOCATABLE)
+	assert(!base.empty());
+
+	return base + WITH_CACHEDIR;
+#else
+	return fs::isAbsolute(WITH_CACHEDIR) ? WITH_CACHEDIR : std::string(PREFIX) + fs::Separator + WITH_CACHEDIR;
+#endif
+}
+
+std::string systemPlugins()
+{
+#if defined(IRCCD_RELOCATABLE)
+	assert(!base.empty());
+
+	return base + WITH_PLUGINDIR;
+#else
+	return fs::isAbsolute(WITH_PLUGINDIR) ? WITH_PLUGINDIR : std::string(PREFIX) + fs::Separator + WITH_PLUGINDIR;
+#endif
+}
+
+/*
+ * User paths
+ * ------------------------------------------------------------------
+ *
+ * Compute user paths.
+ */
+
+/*
+ * userConfig
+ * ---------------------------------------------------------
+ *
+ * Get the path directory to the user configuration. Example:
+ *
+ * Unix:
+ *
+ * XDG_CONFIG_HOME/irccd
+ * HOME/.config/irccd
+ *
+ * Windows:
+ *
+ * CSIDL_LOCAL_APPDATA/irccd/config
+ */
+std::string userConfig()
+{
+	std::ostringstream oss;
+
+#if defined(IRCCD_SYSTEM_WINDOWS)
+	char path[MAX_PATH];
+
+	if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path) != S_OK) {
+		oss << "";
+	} else {
+		oss << path;
+		oss << "\\irccd\\config\\";
+	}
+#else
+	try {
+		Xdg xdg;
+
+		oss << xdg.configHome();
+		oss << "/irccd/";
+	} catch (const std::exception &) {
+		const char *home = getenv("HOME");
+
+		if (home != nullptr)
+			oss << home;
+
+		oss << "/.config/irccd/";
+	}
+#endif
+
+	return oss.str();
+}
+
+/*
+ * userData
+ * --------------------------------------------------------
+ *
+ * Get the path to the data application.
+ *
+ * Unix:
+ *
+ * XDG_DATA_HOME/irccd
+ * HOME/.local/share/irccd
+ *
+ * Windows:
+ *
+ * CSIDL_LOCAL_APPDATA
+ */
+std::string userData()
+{
+	std::ostringstream oss;
+
+#if defined(IRCCD_SYSTEM_WINDOWS)
+	char path[MAX_PATH];
+
+	if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path) != S_OK)
+		oss << "";
+	else {
+		oss << path;
+		oss << "\\irccd\\share";
+	}
+#else
+	try {
+		Xdg xdg;
+
+		oss << xdg.dataHome();
+		oss << "/irccd/";
+	} catch (const std::exception &) {
+		const char *home = getenv("HOME");
+
+		if (home != nullptr)
+			oss << home;
+
+		oss << "/.local/share/irccd/";
+	}
+#endif
+
+	return oss.str();
+}
+
+/*
+ * userCache
+ * --------------------------------------------------------
+ *
+ * Directory for cache files.
+ *
+ * Unix:
+ *
+ * XDG_CACHE_HOME/irccd
+ * HOME/.cache/irccd
+ *
+ * Windows:
+ *
+ * %TEMP% (e.g. C:\Users\<user>\AppData\Local\Temp)
+ */
+std::string userCache()
+{
+	std::ostringstream oss;
+
+#if defined(IRCCD_SYSTEM_WINDOWS)
+	char path[MAX_PATH + 1];
+
+	GetTempPathA(sizeof (path), path);
+
+	oss << path << "\\irccd\\";
+#else
+	try {
+		Xdg xdg;
+
+		oss << xdg.cacheHome();
+		oss << "/irccd/";
+	} catch (const std::exception &) {
+		const char *home = getenv("HOME");
+
+		if (home != nullptr)
+			oss << home;
+
+		oss << "/.cache/irccd/";
+	}
+#endif
+
+	return oss.str();
+}
+
+/*
+ * userPlugins
+ * --------------------------------------------------------
+ *
+ * Path to the data + plugins.
+ */
+std::string userPlugins()
+{
+	return userData() + "/plugins/";
+}
+
+} // !namespace
+
+#if defined(IRCCD_SYSTEM_WINDOWS)
+const char Separator{';'};
+#else
+const char Separator{':'};
+#endif
+
+void setApplicationPath(const std::string &argv0)
+{
+#if defined(IRCCD_RELOCATABLE)
+	try {
+		base = executablePath();
+	} catch (const std::exception &) {
+		/*
+		 * If an exception is thrown, that means the operatin system supports a function to get the executable
+		 * path but it failed.
+		 *
+		 * TODO: show a waning
+		 */
+	}
+
+	/*
+	 * If we could not get the application path from the native function, check if argv[0] is an absolute path
+	 * and use that from there.
+	 *
+	 * Otherwise, search from the PATH.
+	 *
+	 * In the worst case use current working directory.
+	 */
+	if (base.empty()) {
+		if (fs::isAbsolute(argv0)) {
+			base = argv0;
+		} else {
+			std::string name = fs::baseName(argv0);
+
+			for (const auto &dir : util::split(sys::env("PATH"), std::string(1, Separator))) {
+				std::string path = dir + fs::Separator + name;
+
+				if (fs::exists(path)) {
+					base = path;
+					break;
+				}
+			}
+
+			/* Not found in PATH? add dummy value */
+			if (base.empty()) {
+				base = std::string(".") + fs::Separator + WITH_BINDIR + fs::Separator + "dummy";
+			}
+		}
+	}
+
+	/* Find bin/<progname> */
+	auto pos = base.rfind(std::string(WITH_BINDIR) + fs::Separator + fs::baseName(base));
+
+	if (pos != std::string::npos)
+		base.erase(pos);
+
+	/* Add trailing / or \\ for convenience */
+	base = clean(base);
+
+	assert(!base.empty());
+#else
+	(void)argv0;
+#endif
+}
+
+std::string clean(std::string input)
+{
+	if (input.empty())
+		return input;
+
+	/* First, remove any duplicates */
+	input.erase(std::unique(input.begin(), input.end(), [&] (char c1, char c2) {
+		return c1 == c2 && (c1 == '/' || c1 == '\\');
+	}), input.end());
+
+	/* Add a trailing / or \\ */
+	char c = input[input.length() - 1];
+	if (c != '/' && c != '\\')
+		input += fs::Separator;
+
+	/* Now converts all / to \\ for Windows and the opposite for Unix */
+#if defined(IRCCD_SYSTEM_WINDOWS)
+	std::replace(input.begin(), input.end(), '/', '\\');
+#else
+	std::replace(input.begin(), input.end(), '\\', '/');
+#endif
+
+	return input;
+}
+
+std::string get(Path path, Owner owner)
+{
+	assert(path >= PathConfig && path <= PathPlugins);
+	assert(owner >= OwnerSystem && owner <= OwnerUser);
+
+	std::string result;
+
+	switch (owner) {
+	case OwnerSystem:
+		switch (path) {
+		case PathCache:
+			result = clean(systemCache());
+			break;
+		case PathConfig:
+			result = clean(systemConfig());
+			break;
+		case PathData:
+			result = clean(systemData());
+			break;
+		case PathPlugins:
+			result = clean(systemPlugins());
+			break;
+		default:
+			break;
+		}
+	case OwnerUser:
+		switch (path) {
+		case PathCache:
+			result = clean(userCache());
+			break;
+		case PathConfig:
+			result = clean(userConfig());
+			break;
+		case PathData:
+			result = clean(userData());
+			break;
+		case PathPlugins:
+			result = clean(userPlugins());
+			break;
+		default:
+			break;
+		}
+	default:
+		break;
+	}
+
+	return result;
+}
+
+std::vector<std::string> list(Path path)
+{
+	assert(path >= PathConfig && path <= PathPlugins);
+
+	std::vector<std::string> list;
+
+	switch (path) {
+	case PathCache:
+		list.push_back(clean(userCache()));
+		list.push_back(clean(systemCache()));
+		break;
+	case PathConfig:
+		list.push_back(clean(userConfig()));
+		list.push_back(clean(systemConfig()));
+		break;
+	case PathData:
+		list.push_back(clean(userData()));
+		list.push_back(clean(systemData()));
+		break;
+	case PathPlugins:
+		list.push_back(clean(fs::cwd()));
+		list.push_back(clean(userPlugins()));
+		list.push_back(clean(systemPlugins()));
+		break;
+	default:
+		break;
+	}
+
+	return list;
+}
+
+} // !path
+
+} // !irccd